From 9f364a2a733f1f97324de9c675983e862092274d Mon Sep 17 00:00:00 2001 From: Derek Bruneau Date: Tue, 4 Feb 2020 15:06:21 -0500 Subject: [PATCH 001/125] Adding Procfile for Heroku and updating Go package --- Procfile | 1 + go.mod | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 Procfile diff --git a/Procfile b/Procfile new file mode 100644 index 00000000000..7a7dfbb28e9 --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: PBS_PORT=$PORT bin/prebid-server diff --git a/go.mod b/go.mod index 5c837c2ee7b..a6d21a10491 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/prebid/prebid-server +module github.com/intergi/prebid-server go 1.12 From 640e0e547aa55c392882d10da083920f0062db32 Mon Sep 17 00:00:00 2001 From: Derek Bruneau Date: Mon, 13 Apr 2020 15:21:46 -0400 Subject: [PATCH 002/125] Reverting module name to prevent Go from downloading local packages from GitHub --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index a6d21a10491..5c837c2ee7b 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/intergi/prebid-server +module github.com/prebid/prebid-server go 1.12 From a4dab0995aa302f079555d6c99caa4aa95c72ae6 Mon Sep 17 00:00:00 2001 From: Derek Bruneau Date: Tue, 14 Apr 2020 09:58:41 -0400 Subject: [PATCH 003/125] Adding magic comment so that Heroku uses Go 1.12 --- go.mod | 3 +++ 1 file changed, 3 insertions(+) diff --git a/go.mod b/go.mod index 5c837c2ee7b..cd9491aa6ec 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,9 @@ module github.com/prebid/prebid-server go 1.12 +// Magic comment that determines which Go version Heroku uses. +// +heroku goVersion go1.12 + require ( github.com/BurntSushi/toml v0.3.1 // indirect github.com/DATA-DOG/go-sqlmock v1.3.0 From 23aa5f88446ae15e65614265a268faed71564189 Mon Sep 17 00:00:00 2001 From: Derek Bruneau Date: Tue, 28 Apr 2020 11:15:09 -0400 Subject: [PATCH 004/125] Configuring instance to log more info on Heroku --- Procfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Procfile b/Procfile index 7a7dfbb28e9..1944727d5e8 100644 --- a/Procfile +++ b/Procfile @@ -1 +1 @@ -web: PBS_PORT=$PORT bin/prebid-server +web: PBS_PORT=$PORT bin/prebid-server -stderrthreshold=INFO From d3f583d5fffef7fe0957996f4d39513b9a1dec7d Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Mon, 24 Aug 2020 15:08:05 -0400 Subject: [PATCH 005/125] custom adapter attempts --- adapters/gumgum/gumgum.go | 59 ++++-- adapters/pulsepoint/pulsepoint.go | 52 +++--- adapters/s2s_gumgum/params_test.go | 52 ++++++ adapters/s2s_gumgum/s2s_gumgum.go | 172 ++++++++++++++++++ adapters/s2s_gumgum/s2s_gumgum_test.go | 10 + .../s2s_gumgumtest/exemplary/banner.json | 99 ++++++++++ .../s2s_gumgumtest/params/race/banner.json | 3 + adapters/s2s_gumgum/usersync.go | 12 ++ adapters/s2s_gumgum/usersync_test.go | 35 ++++ adapters/s2s_pulsepoint/params_test.go | 52 ++++++ adapters/s2s_pulsepoint/s2s_pulsepoint.go | 172 ++++++++++++++++++ .../s2s_pulsepoint/s2s_pulsepoint_test.go | 10 + .../s2s_pulsepointtest/exemplary/banner.json | 99 ++++++++++ .../params/race/banner.json | 3 + adapters/s2s_pulsepoint/usersync.go | 12 ++ adapters/s2s_pulsepoint/usersync_test.go | 35 ++++ config/config.go | 2 + exchange/adapter_map.go | 5 + openrtb_ext/bidders.go | 4 + static/bidder-info/gumgum.yaml | 1 + static/bidder-info/pulsepoint.yaml | 1 + static/bidder-info/s2s_gumgum.yaml | 7 + static/bidder-info/s2s_pulsepoint.yaml | 7 + static/bidder-params/gumgum.json | 3 +- static/bidder-params/pulsepoint.json | 3 +- static/bidder-params/s2s_gumgum.json | 9 + static/bidder-params/s2s_pulsepoint.json | 9 + 27 files changed, 888 insertions(+), 40 deletions(-) create mode 100644 adapters/s2s_gumgum/params_test.go create mode 100644 adapters/s2s_gumgum/s2s_gumgum.go create mode 100644 adapters/s2s_gumgum/s2s_gumgum_test.go create mode 100644 adapters/s2s_gumgum/s2s_gumgumtest/exemplary/banner.json create mode 100644 adapters/s2s_gumgum/s2s_gumgumtest/params/race/banner.json create mode 100644 adapters/s2s_gumgum/usersync.go create mode 100644 adapters/s2s_gumgum/usersync_test.go create mode 100644 adapters/s2s_pulsepoint/params_test.go create mode 100644 adapters/s2s_pulsepoint/s2s_pulsepoint.go create mode 100644 adapters/s2s_pulsepoint/s2s_pulsepoint_test.go create mode 100644 adapters/s2s_pulsepoint/s2s_pulsepointtest/exemplary/banner.json create mode 100644 adapters/s2s_pulsepoint/s2s_pulsepointtest/params/race/banner.json create mode 100644 adapters/s2s_pulsepoint/usersync.go create mode 100644 adapters/s2s_pulsepoint/usersync_test.go create mode 100644 static/bidder-info/s2s_gumgum.yaml create mode 100644 static/bidder-info/s2s_pulsepoint.yaml create mode 100644 static/bidder-params/s2s_gumgum.json create mode 100644 static/bidder-params/s2s_pulsepoint.json diff --git a/adapters/gumgum/gumgum.go b/adapters/gumgum/gumgum.go index 84a008d1891..e351f51b4e2 100644 --- a/adapters/gumgum/gumgum.go +++ b/adapters/gumgum/gumgum.go @@ -8,12 +8,16 @@ import ( "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" "net/http" + "strconv" + "strings" ) +// GumGumAdapter implements Bidder interface. type GumGumAdapter struct { URI string } +// MakeRequests makes the HTTP requests which should be made to fetch bids. func (g *GumGumAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var validImps []openrtb.Imp var trackingId string @@ -26,15 +30,21 @@ func (g *GumGumAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapt zone, err := preprocess(&imp) if err != nil { errs = append(errs, err) - } else { - if request.Imp[i].Banner != nil { - bannerCopy := *request.Imp[i].Banner - if bannerCopy.W == nil && bannerCopy.H == nil && len(bannerCopy.Format) > 0 { - format := bannerCopy.Format[0] - bannerCopy.W = &(format.W) - bannerCopy.H = &(format.H) - } - request.Imp[i].Banner = &bannerCopy + } else if request.Imp[i].Banner != nil { + bannerCopy := *request.Imp[i].Banner + if bannerCopy.W == nil && bannerCopy.H == nil && len(bannerCopy.Format) > 0 { + format := bannerCopy.Format[0] + bannerCopy.W = &(format.W) + bannerCopy.H = &(format.H) + } + request.Imp[i].Banner = &bannerCopy + validImps = append(validImps, request.Imp[i]) + trackingId = zone + } else if request.Imp[i].Video != nil { + err := validateVideoParams(request.Imp[i].Video) + if err != nil { + errs = append(errs, err) + } else { validImps = append(validImps, request.Imp[i]) trackingId = zone } @@ -70,6 +80,7 @@ func (g *GumGumAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapt }}, errs } +// MakeBids unpacks the server's response into Bids. func (g *GumGumAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil @@ -98,12 +109,19 @@ func (g *GumGumAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRe for _, sb := range bidResp.SeatBid { for i := range sb.Bid { + mediaType := getMediaTypeForImpID(sb.Bid[i].ImpID, internalRequest.Imp) + if mediaType == openrtb_ext.BidTypeVideo { + price := strconv.FormatFloat(sb.Bid[i].Price, 'f', -1, 64) + sb.Bid[i].AdM = strings.Replace(sb.Bid[i].AdM, "${AUCTION_PRICE}", price, -1) + } + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ Bid: &sb.Bid[i], - BidType: openrtb_ext.BidTypeBanner, + BidType: mediaType, }) } } + return bidResponse, errs } @@ -128,8 +146,27 @@ func preprocess(imp *openrtb.Imp) (string, error) { return zone, nil } +func getMediaTypeForImpID(impID string, imps []openrtb.Imp) openrtb_ext.BidType { + for _, imp := range imps { + if imp.ID == impID && imp.Banner != nil { + return openrtb_ext.BidTypeBanner + } + } + return openrtb_ext.BidTypeVideo +} + +func validateVideoParams(video *openrtb.Video) (err error) { + // if video.W == 0 || video.H == 0 || video.MinDuration == 0 || video.MaxDuration == 0 || video.Placement == 0 || video.Linearity == 0 { + // return &errortypes.BadInput{ + // Message: "Invalid or missing video field(s)", + // } + // } + return nil +} + +// NewGumGumBidder configures bidder endpoint. func NewGumGumBidder(endpoint string) *GumGumAdapter { return &GumGumAdapter{ URI: endpoint, } -} +} \ No newline at end of file diff --git a/adapters/pulsepoint/pulsepoint.go b/adapters/pulsepoint/pulsepoint.go index 3a5123084ba..54ee7d48221 100644 --- a/adapters/pulsepoint/pulsepoint.go +++ b/adapters/pulsepoint/pulsepoint.go @@ -55,21 +55,21 @@ func (a *PulsePointAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidde 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", - } - } + // 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 @@ -92,23 +92,25 @@ func (a *PulsePointAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidde if err == nil { ppReq.Imp[i].Banner.W = openrtb.Uint64Ptr(uint64(width)) } else { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("Invalid Width param %s", size[0]), - } + // 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 = openrtb.Uint64Ptr(uint64(height)) } else { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("Invalid Height param %s", size[1]), - } + // 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), - } + // return nil, &errortypes.BadInput{ + // Message: fmt.Sprintf("Invalid AdSize param %s", params.AdSize), + // } } + } else if ppReq.Imp[i].Video != nil { + } } reqJSON, err := json.Marshal(ppReq) @@ -121,7 +123,7 @@ func (a *PulsePointAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidde bidder.Debug = append(bidder.Debug, debug) } - httpReq, err := http.NewRequest("POST", a.URI, bytes.NewBuffer(reqJSON)) + httpReq, err := http.NewRequest("POST", "http://rts.lga.contextweb.com/rt-seller/bid/openrtb/playwire", bytes.NewBuffer(reqJSON)) httpReq.Header.Add("Content-Type", "application/json;charset=utf-8") httpReq.Header.Add("Accept", "application/json") diff --git a/adapters/s2s_gumgum/params_test.go b/adapters/s2s_gumgum/params_test.go new file mode 100644 index 00000000000..4cb6f019197 --- /dev/null +++ b/adapters/s2s_gumgum/params_test.go @@ -0,0 +1,52 @@ +package gumgum + +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.BidderGumGum, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected gumgum 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{ + `{"zone":"dc9d6be1"}`, +} + +var invalidParams = []string{ + `null`, + `nil`, + ``, + `{}`, + `[]`, + `true`, + `2`, + `{"zone":12345678}`, + `{"zone":""}`, + `{"placementId": 1}`, + `{"zone": true}`, + `{"placementId": 1, "zone":"1234567"}`, +} diff --git a/adapters/s2s_gumgum/s2s_gumgum.go b/adapters/s2s_gumgum/s2s_gumgum.go new file mode 100644 index 00000000000..876ccd85202 --- /dev/null +++ b/adapters/s2s_gumgum/s2s_gumgum.go @@ -0,0 +1,172 @@ +package s2s_gumgum + +import ( + "encoding/json" + "fmt" + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" + "net/http" + "strconv" + "strings" +) + +// GumGumAdapter implements Bidder interface. +type GumGumAdapter struct { + URI string +} + +// MakeRequests makes the HTTP requests which should be made to fetch bids. +func (g *GumGumAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var validImps []openrtb.Imp + var trackingId string + + numRequests := len(request.Imp) + errs := make([]error, 0, numRequests) + + for i := 0; i < numRequests; i++ { + imp := request.Imp[i] + zone, err := preprocess(&imp) + if err != nil { + errs = append(errs, err) + } else if request.Imp[i].Banner != nil { + bannerCopy := *request.Imp[i].Banner + if bannerCopy.W == nil && bannerCopy.H == nil && len(bannerCopy.Format) > 0 { + format := bannerCopy.Format[0] + bannerCopy.W = &(format.W) + bannerCopy.H = &(format.H) + } + request.Imp[i].Banner = &bannerCopy + validImps = append(validImps, request.Imp[i]) + trackingId = zone + } else if request.Imp[i].Video != nil { + err := validateVideoParams(request.Imp[i].Video) + if err != nil { + errs = append(errs, err) + } else { + validImps = append(validImps, request.Imp[i]) + trackingId = zone + } + } + } + + if len(validImps) == 0 { + return nil, errs + } + + request.Imp = validImps + + if request.Site != nil { + siteCopy := *request.Site + siteCopy.ID = trackingId + request.Site = &siteCopy + } + + reqJSON, err := json.Marshal(request) + if err != nil { + errs = append(errs, err) + return nil, errs + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + return []*adapters.RequestData{{ + Method: "POST", + Uri: g.URI, + Body: reqJSON, + Headers: headers, + }}, errs +} + +// MakeBids unpacks the server's response into Bids. +func (g *GumGumAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + + if response.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Bad user input: HTTP status %d", response.StatusCode), + }} + } + + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Bad server response: HTTP status %d", response.StatusCode), + }} + } + var bidResp openrtb.BidResponse + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Bad server response: %d. ", err), + }} + } + + var errs []error + bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) + + for _, sb := range bidResp.SeatBid { + for i := range sb.Bid { + mediaType := getMediaTypeForImpID(sb.Bid[i].ImpID, internalRequest.Imp) + if mediaType == openrtb_ext.BidTypeVideo { + price := strconv.FormatFloat(sb.Bid[i].Price, 'f', -1, 64) + sb.Bid[i].AdM = strings.Replace(sb.Bid[i].AdM, "${AUCTION_PRICE}", price, -1) + } + + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &sb.Bid[i], + BidType: mediaType, + }) + } + } + + return bidResponse, errs +} + +func preprocess(imp *openrtb.Imp) (string, error) { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + err = &errortypes.BadInput{ + Message: err.Error(), + } + return "", err + } + + var gumgumExt openrtb_ext.ExtImpGumGum + if err := json.Unmarshal(bidderExt.Bidder, &gumgumExt); err != nil { + err = &errortypes.BadInput{ + Message: err.Error(), + } + return "", err + } + + zone := gumgumExt.Zone + return zone, nil +} + +func getMediaTypeForImpID(impID string, imps []openrtb.Imp) openrtb_ext.BidType { + for _, imp := range imps { + if imp.ID == impID && imp.Banner != nil { + return openrtb_ext.BidTypeBanner + } + } + return openrtb_ext.BidTypeVideo +} + +func validateVideoParams(video *openrtb.Video) (err error) { + // if video.W == 0 || video.H == 0 || video.MinDuration == 0 || video.MaxDuration == 0 || video.Placement == 0 || video.Linearity == 0 { + // return &errortypes.BadInput{ + // Message: "Invalid or missing video field(s)", + // } + // } + return nil +} + +// NewGumGumBidder configures bidder endpoint. +func NewGumGumBidder(endpoint string) *GumGumAdapter { + return &GumGumAdapter{ + URI: endpoint, + } +} \ No newline at end of file diff --git a/adapters/s2s_gumgum/s2s_gumgum_test.go b/adapters/s2s_gumgum/s2s_gumgum_test.go new file mode 100644 index 00000000000..f87c8cf6216 --- /dev/null +++ b/adapters/s2s_gumgum/s2s_gumgum_test.go @@ -0,0 +1,10 @@ +package gumgum + +import ( + "github.com/prebid/prebid-server/adapters/adapterstest" + "testing" +) + +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "gumgumtest", NewGumGumBidder("https://g2.gumgum.com/providers/prbds2s/bid")) +} diff --git a/adapters/s2s_gumgum/s2s_gumgumtest/exemplary/banner.json b/adapters/s2s_gumgum/s2s_gumgumtest/exemplary/banner.json new file mode 100644 index 00000000000..2fbd3da22da --- /dev/null +++ b/adapters/s2s_gumgum/s2s_gumgumtest/exemplary/banner.json @@ -0,0 +1,99 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 300 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "zone": "dc9d6be1" + } + } + } + ] + }, + + "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": { + "bidder": { + "zone": "dc9d6be1" + } + } + }] + } + }, + "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" + } + ] + } + ] +} diff --git a/adapters/s2s_gumgum/s2s_gumgumtest/params/race/banner.json b/adapters/s2s_gumgum/s2s_gumgumtest/params/race/banner.json new file mode 100644 index 00000000000..6e222304f36 --- /dev/null +++ b/adapters/s2s_gumgum/s2s_gumgumtest/params/race/banner.json @@ -0,0 +1,3 @@ +{ + "zone": "dc9d6be1" +} diff --git a/adapters/s2s_gumgum/usersync.go b/adapters/s2s_gumgum/usersync.go new file mode 100644 index 00000000000..5d29c7dceb2 --- /dev/null +++ b/adapters/s2s_gumgum/usersync.go @@ -0,0 +1,12 @@ +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", 61, temp, adapters.SyncTypeIframe) +} diff --git a/adapters/s2s_gumgum/usersync_test.go b/adapters/s2s_gumgum/usersync_test.go new file mode 100644 index 00000000000..3606f6ae04c --- /dev/null +++ b/adapters/s2s_gumgum/usersync_test.go @@ -0,0 +1,35 @@ +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{ + Value: "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.EqualValues(t, 61, syncer.GDPRVendorID()) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/adapters/s2s_pulsepoint/params_test.go b/adapters/s2s_pulsepoint/params_test.go new file mode 100644 index 00000000000..4cb6f019197 --- /dev/null +++ b/adapters/s2s_pulsepoint/params_test.go @@ -0,0 +1,52 @@ +package gumgum + +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.BidderGumGum, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected gumgum 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{ + `{"zone":"dc9d6be1"}`, +} + +var invalidParams = []string{ + `null`, + `nil`, + ``, + `{}`, + `[]`, + `true`, + `2`, + `{"zone":12345678}`, + `{"zone":""}`, + `{"placementId": 1}`, + `{"zone": true}`, + `{"placementId": 1, "zone":"1234567"}`, +} diff --git a/adapters/s2s_pulsepoint/s2s_pulsepoint.go b/adapters/s2s_pulsepoint/s2s_pulsepoint.go new file mode 100644 index 00000000000..69f532473c1 --- /dev/null +++ b/adapters/s2s_pulsepoint/s2s_pulsepoint.go @@ -0,0 +1,172 @@ +package s2s_pulsepoint + +import ( + "encoding/json" + "fmt" + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" + "net/http" + "strconv" + "strings" +) + +// GumGumAdapter implements Bidder interface. +type GumGumAdapter struct { + URI string +} + +// MakeRequests makes the HTTP requests which should be made to fetch bids. +func (g *GumGumAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var validImps []openrtb.Imp + var trackingId string + + numRequests := len(request.Imp) + errs := make([]error, 0, numRequests) + + for i := 0; i < numRequests; i++ { + imp := request.Imp[i] + zone, err := preprocess(&imp) + if err != nil { + errs = append(errs, err) + } else if request.Imp[i].Banner != nil { + bannerCopy := *request.Imp[i].Banner + if bannerCopy.W == nil && bannerCopy.H == nil && len(bannerCopy.Format) > 0 { + format := bannerCopy.Format[0] + bannerCopy.W = &(format.W) + bannerCopy.H = &(format.H) + } + request.Imp[i].Banner = &bannerCopy + validImps = append(validImps, request.Imp[i]) + trackingId = zone + } else if request.Imp[i].Video != nil { + err := validateVideoParams(request.Imp[i].Video) + if err != nil { + errs = append(errs, err) + } else { + validImps = append(validImps, request.Imp[i]) + trackingId = zone + } + } + } + + if len(validImps) == 0 { + return nil, errs + } + + request.Imp = validImps + + if request.Site != nil { + siteCopy := *request.Site + siteCopy.ID = trackingId + request.Site = &siteCopy + } + + reqJSON, err := json.Marshal(request) + if err != nil { + errs = append(errs, err) + return nil, errs + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + return []*adapters.RequestData{{ + Method: "POST", + Uri: g.URI, + Body: reqJSON, + Headers: headers, + }}, errs +} + +// MakeBids unpacks the server's response into Bids. +func (g *GumGumAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + + if response.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Bad user input: HTTP status %d", response.StatusCode), + }} + } + + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Bad server response: HTTP status %d", response.StatusCode), + }} + } + var bidResp openrtb.BidResponse + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Bad server response: %d. ", err), + }} + } + + var errs []error + bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) + + for _, sb := range bidResp.SeatBid { + for i := range sb.Bid { + mediaType := getMediaTypeForImpID(sb.Bid[i].ImpID, internalRequest.Imp) + if mediaType == openrtb_ext.BidTypeVideo { + price := strconv.FormatFloat(sb.Bid[i].Price, 'f', -1, 64) + sb.Bid[i].AdM = strings.Replace(sb.Bid[i].AdM, "${AUCTION_PRICE}", price, -1) + } + + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &sb.Bid[i], + BidType: mediaType, + }) + } + } + + return bidResponse, errs +} + +func preprocess(imp *openrtb.Imp) (string, error) { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + err = &errortypes.BadInput{ + Message: err.Error(), + } + return "", err + } + + var gumgumExt openrtb_ext.ExtImpGumGum + if err := json.Unmarshal(bidderExt.Bidder, &gumgumExt); err != nil { + err = &errortypes.BadInput{ + Message: err.Error(), + } + return "", err + } + + zone := gumgumExt.Zone + return zone, nil +} + +func getMediaTypeForImpID(impID string, imps []openrtb.Imp) openrtb_ext.BidType { + for _, imp := range imps { + if imp.ID == impID && imp.Banner != nil { + return openrtb_ext.BidTypeBanner + } + } + return openrtb_ext.BidTypeVideo +} + +func validateVideoParams(video *openrtb.Video) (err error) { + // if video.W == 0 || video.H == 0 || video.MinDuration == 0 || video.MaxDuration == 0 || video.Placement == 0 || video.Linearity == 0 { + // return &errortypes.BadInput{ + // Message: "Invalid or missing video field(s)", + // } + // } + return nil +} + +// NewGumGumBidder configures bidder endpoint. +func NewGumGumBidder(endpoint string) *GumGumAdapter { + return &GumGumAdapter{ + URI: endpoint, + } +} \ No newline at end of file diff --git a/adapters/s2s_pulsepoint/s2s_pulsepoint_test.go b/adapters/s2s_pulsepoint/s2s_pulsepoint_test.go new file mode 100644 index 00000000000..f87c8cf6216 --- /dev/null +++ b/adapters/s2s_pulsepoint/s2s_pulsepoint_test.go @@ -0,0 +1,10 @@ +package gumgum + +import ( + "github.com/prebid/prebid-server/adapters/adapterstest" + "testing" +) + +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "gumgumtest", NewGumGumBidder("https://g2.gumgum.com/providers/prbds2s/bid")) +} diff --git a/adapters/s2s_pulsepoint/s2s_pulsepointtest/exemplary/banner.json b/adapters/s2s_pulsepoint/s2s_pulsepointtest/exemplary/banner.json new file mode 100644 index 00000000000..2fbd3da22da --- /dev/null +++ b/adapters/s2s_pulsepoint/s2s_pulsepointtest/exemplary/banner.json @@ -0,0 +1,99 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 300 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "zone": "dc9d6be1" + } + } + } + ] + }, + + "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": { + "bidder": { + "zone": "dc9d6be1" + } + } + }] + } + }, + "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" + } + ] + } + ] +} diff --git a/adapters/s2s_pulsepoint/s2s_pulsepointtest/params/race/banner.json b/adapters/s2s_pulsepoint/s2s_pulsepointtest/params/race/banner.json new file mode 100644 index 00000000000..6e222304f36 --- /dev/null +++ b/adapters/s2s_pulsepoint/s2s_pulsepointtest/params/race/banner.json @@ -0,0 +1,3 @@ +{ + "zone": "dc9d6be1" +} diff --git a/adapters/s2s_pulsepoint/usersync.go b/adapters/s2s_pulsepoint/usersync.go new file mode 100644 index 00000000000..5d29c7dceb2 --- /dev/null +++ b/adapters/s2s_pulsepoint/usersync.go @@ -0,0 +1,12 @@ +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", 61, temp, adapters.SyncTypeIframe) +} diff --git a/adapters/s2s_pulsepoint/usersync_test.go b/adapters/s2s_pulsepoint/usersync_test.go new file mode 100644 index 00000000000..3606f6ae04c --- /dev/null +++ b/adapters/s2s_pulsepoint/usersync_test.go @@ -0,0 +1,35 @@ +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{ + Value: "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.EqualValues(t, 61, syncer.GDPRVendorID()) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/config/config.go b/config/config.go index 2f302cc6328..716eabd9c18 100644 --- a/config/config.go +++ b/config/config.go @@ -688,6 +688,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.gamoshi.endpoint", "https://rtb.gamoshi.io") v.SetDefault("adapters.grid.endpoint", "http://grid.bidswitch.net/sp_bid?sp=prebid") v.SetDefault("adapters.gumgum.endpoint", "https://g2.gumgum.com/providers/prbds2s/bid") + v.SetDefault("adapters.s2s_gumgum.endpoint", "https://g2.gumgum.com/zones/8ylgv2wd/bid") v.SetDefault("adapters.improvedigital.endpoint", "http://ad.360yield.com/pbs") v.SetDefault("adapters.ix.endpoint", "http://appnexus-us-east.lb.indexww.com/transbidder?p=184932") v.SetDefault("adapters.kubient.endpoint", "http://kbntx.ch/prebid") @@ -699,6 +700,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.pubmatic.endpoint", "https://hbopenbid.pubmatic.com/translator?source=prebid-server") v.SetDefault("adapters.pubnative.endpoint", "http://dsp.pubnative.net/bid/v1/request") v.SetDefault("adapters.pulsepoint.endpoint", "http://bid.contextweb.com/header/s/ortb/prebid-s2s") + v.SetDefault("adapters.s2s_pulsepoint.endpoint", "http://rts.lga.contextweb.com/rt-seller/bid/openrtb/playwire") v.SetDefault("adapters.rhythmone.endpoint", "http://tag.1rx.io/rmp") v.SetDefault("adapters.rtbhouse.endpoint", "http://prebidserver-s2s-ams.creativecdn.com/bidder/prebidserver/bids") v.SetDefault("adapters.rubicon.endpoint", "http://exapi-us-east.rubiconproject.com/a/api/exchange.json") diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index 95f5b7f5882..195d3e91791 100644 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -60,6 +60,9 @@ import ( "github.com/prebid/prebid-server/adapters/yieldmo" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" + + "github.com/prebid/adapters/s2s_pulsepoint" + "github.com/prebid/adapters/s2s_gumgum" ) // The newAdapterMap function is segregated to its own file to make it a simple and clean location for each Adapter @@ -124,6 +127,8 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderVisx: visx.NewVisxBidder(cfg.Adapters[string(openrtb_ext.BidderVisx)].Endpoint), openrtb_ext.BidderVrtcal: vrtcal.NewVrtcalBidder(cfg.Adapters[string(openrtb_ext.BidderVrtcal)].Endpoint), openrtb_ext.BidderYieldmo: yieldmo.NewYieldmoBidder(cfg.Adapters[string(openrtb_ext.BidderYieldmo)].Endpoint), + openrtb_ext.BidderS2SPulsepoint: s2s_pulsepoint.NewGumGumBidder(cfg.Adapters[string(openrtb_ext.BidderS2SPulsepoint)].Endpoint), + openrtb_ext.BidderS2SGumGum: s2s_gumgum.NewGumGumBidder(cfg.Adapters[string(openrtb_ext.BidderS2SGumGum)].Endpoint), } legacyBidders := map[openrtb_ext.BidderName]adapters.Adapter{ diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 7a3f24eb07f..cfc161bba31 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -71,6 +71,8 @@ const ( BidderVisx BidderName = "visx" BidderVrtcal BidderName = "vrtcal" BidderYieldmo BidderName = "yieldmo" + BidderS2SPulsepoint BidderName = "s2s_pulsepoint" + BidderS2SGumGum BidderName = "s2s_gumgum" ) // BidderMap stores all the valid OpenRTB 2.x Bidders in the project. This map *must not* be mutated. @@ -126,6 +128,8 @@ var BidderMap = map[string]BidderName{ "visx": BidderVisx, "vrtcal": BidderVrtcal, "yieldmo": BidderYieldmo, + "s2s_gumgum": BidderS2SGumGum, + "s2s_pulsepoint": BidderS2SPulsepoint, } // BidderList returns the values of the BidderMap diff --git a/static/bidder-info/gumgum.yaml b/static/bidder-info/gumgum.yaml index b8a3981c9f0..3dab19a6a1e 100644 --- a/static/bidder-info/gumgum.yaml +++ b/static/bidder-info/gumgum.yaml @@ -4,3 +4,4 @@ capabilities: site: mediaTypes: - banner + - video diff --git a/static/bidder-info/pulsepoint.yaml b/static/bidder-info/pulsepoint.yaml index b9fd32427b1..2238ca1549a 100644 --- a/static/bidder-info/pulsepoint.yaml +++ b/static/bidder-info/pulsepoint.yaml @@ -7,3 +7,4 @@ capabilities: site: mediaTypes: - banner + - video \ No newline at end of file diff --git a/static/bidder-info/s2s_gumgum.yaml b/static/bidder-info/s2s_gumgum.yaml new file mode 100644 index 00000000000..764b49023df --- /dev/null +++ b/static/bidder-info/s2s_gumgum.yaml @@ -0,0 +1,7 @@ +maintainer: + email: "pubtech@na.com" +capabilities: + site: + mediaTypes: + - banner + - video diff --git a/static/bidder-info/s2s_pulsepoint.yaml b/static/bidder-info/s2s_pulsepoint.yaml new file mode 100644 index 00000000000..764b49023df --- /dev/null +++ b/static/bidder-info/s2s_pulsepoint.yaml @@ -0,0 +1,7 @@ +maintainer: + email: "pubtech@na.com" +capabilities: + site: + mediaTypes: + - banner + - video diff --git a/static/bidder-params/gumgum.json b/static/bidder-params/gumgum.json index 5ec2b8e0cbf..43fe6867b82 100644 --- a/static/bidder-params/gumgum.json +++ b/static/bidder-params/gumgum.json @@ -9,6 +9,5 @@ "description": "A tracking id used to identify GumGum zone.", "minLength": 8 } - }, - "required": ["zone"] + } } diff --git a/static/bidder-params/pulsepoint.json b/static/bidder-params/pulsepoint.json index c4704c3b42e..6be52fc6e4a 100644 --- a/static/bidder-params/pulsepoint.json +++ b/static/bidder-params/pulsepoint.json @@ -17,6 +17,5 @@ "pattern": "^[0-9]+[xX][0-9]+$", "description": "The size of the ad slot being sold. This should be a string like 300X250" } - }, - "required": ["cp", "ct", "cf"] + } } diff --git a/static/bidder-params/s2s_gumgum.json b/static/bidder-params/s2s_gumgum.json new file mode 100644 index 00000000000..acc2d5c221b --- /dev/null +++ b/static/bidder-params/s2s_gumgum.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "GumGum Adapter Params", + "description": "A schema which validates params accepted by the GumGum adapter", + "type": "object", + "properties": { + + } +} diff --git a/static/bidder-params/s2s_pulsepoint.json b/static/bidder-params/s2s_pulsepoint.json new file mode 100644 index 00000000000..acc2d5c221b --- /dev/null +++ b/static/bidder-params/s2s_pulsepoint.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "GumGum Adapter Params", + "description": "A schema which validates params accepted by the GumGum adapter", + "type": "object", + "properties": { + + } +} From 80a3ca6f8bba6561cbedeef74ef81e1ddc8e20b9 Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Mon, 24 Aug 2020 16:01:03 -0400 Subject: [PATCH 006/125] resolve naming dupes --- adapters/s2s_gumgum/params_test.go | 2 +- adapters/s2s_gumgum/s2s_gumgum_test.go | 2 +- adapters/s2s_gumgum/usersync.go | 2 +- adapters/s2s_gumgum/usersync_test.go | 2 +- adapters/s2s_pulsepoint/params_test.go | 2 +- adapters/s2s_pulsepoint/s2s_pulsepoint_test.go | 2 +- adapters/s2s_pulsepoint/usersync.go | 2 +- adapters/s2s_pulsepoint/usersync_test.go | 2 +- exchange/adapter_map.go | 4 ++-- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/adapters/s2s_gumgum/params_test.go b/adapters/s2s_gumgum/params_test.go index 4cb6f019197..54672f37aee 100644 --- a/adapters/s2s_gumgum/params_test.go +++ b/adapters/s2s_gumgum/params_test.go @@ -1,4 +1,4 @@ -package gumgum +package s2s_gumgum import ( "encoding/json" diff --git a/adapters/s2s_gumgum/s2s_gumgum_test.go b/adapters/s2s_gumgum/s2s_gumgum_test.go index f87c8cf6216..7cfae844777 100644 --- a/adapters/s2s_gumgum/s2s_gumgum_test.go +++ b/adapters/s2s_gumgum/s2s_gumgum_test.go @@ -1,4 +1,4 @@ -package gumgum +package s2s_gumgum import ( "github.com/prebid/prebid-server/adapters/adapterstest" diff --git a/adapters/s2s_gumgum/usersync.go b/adapters/s2s_gumgum/usersync.go index 5d29c7dceb2..e516cc7d96a 100644 --- a/adapters/s2s_gumgum/usersync.go +++ b/adapters/s2s_gumgum/usersync.go @@ -1,4 +1,4 @@ -package gumgum +package s2s_gumgum import ( "text/template" diff --git a/adapters/s2s_gumgum/usersync_test.go b/adapters/s2s_gumgum/usersync_test.go index 3606f6ae04c..4f73d1cb5a9 100644 --- a/adapters/s2s_gumgum/usersync_test.go +++ b/adapters/s2s_gumgum/usersync_test.go @@ -1,4 +1,4 @@ -package gumgum +package s2s_gumgum import ( "testing" diff --git a/adapters/s2s_pulsepoint/params_test.go b/adapters/s2s_pulsepoint/params_test.go index 4cb6f019197..026ab25d244 100644 --- a/adapters/s2s_pulsepoint/params_test.go +++ b/adapters/s2s_pulsepoint/params_test.go @@ -1,4 +1,4 @@ -package gumgum +package s2s_pulsepoint import ( "encoding/json" diff --git a/adapters/s2s_pulsepoint/s2s_pulsepoint_test.go b/adapters/s2s_pulsepoint/s2s_pulsepoint_test.go index f87c8cf6216..a314d4b6022 100644 --- a/adapters/s2s_pulsepoint/s2s_pulsepoint_test.go +++ b/adapters/s2s_pulsepoint/s2s_pulsepoint_test.go @@ -1,4 +1,4 @@ -package gumgum +package s2s_pulsepoint import ( "github.com/prebid/prebid-server/adapters/adapterstest" diff --git a/adapters/s2s_pulsepoint/usersync.go b/adapters/s2s_pulsepoint/usersync.go index 5d29c7dceb2..a66437b7d83 100644 --- a/adapters/s2s_pulsepoint/usersync.go +++ b/adapters/s2s_pulsepoint/usersync.go @@ -1,4 +1,4 @@ -package gumgum +package s2s_pulsepoint import ( "text/template" diff --git a/adapters/s2s_pulsepoint/usersync_test.go b/adapters/s2s_pulsepoint/usersync_test.go index 3606f6ae04c..4316da1615b 100644 --- a/adapters/s2s_pulsepoint/usersync_test.go +++ b/adapters/s2s_pulsepoint/usersync_test.go @@ -1,4 +1,4 @@ -package gumgum +package s2s_pulsepoint import ( "testing" diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index 195d3e91791..e181506fc06 100644 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -61,8 +61,8 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/adapters/s2s_pulsepoint" - "github.com/prebid/adapters/s2s_gumgum" + "prebid-server/adapters/s2s_pulsepoint" + "prebid-server/adapters/s2s_gumgum" ) // The newAdapterMap function is segregated to its own file to make it a simple and clean location for each Adapter From 49394e79cd0e0bfcee06f3d38baf8ee0290ef331 Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Mon, 24 Aug 2020 16:21:12 -0400 Subject: [PATCH 007/125] revert original adapters --- adapters/gumgum/gumgum.go | 57 ++++++------------------------- adapters/pulsepoint/pulsepoint.go | 54 ++++++++++++++--------------- 2 files changed, 36 insertions(+), 75 deletions(-) diff --git a/adapters/gumgum/gumgum.go b/adapters/gumgum/gumgum.go index e351f51b4e2..c4732e615b0 100644 --- a/adapters/gumgum/gumgum.go +++ b/adapters/gumgum/gumgum.go @@ -8,16 +8,12 @@ import ( "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" "net/http" - "strconv" - "strings" ) -// GumGumAdapter implements Bidder interface. type GumGumAdapter struct { URI string } -// MakeRequests makes the HTTP requests which should be made to fetch bids. func (g *GumGumAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var validImps []openrtb.Imp var trackingId string @@ -30,21 +26,15 @@ func (g *GumGumAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapt zone, err := preprocess(&imp) if err != nil { errs = append(errs, err) - } else if request.Imp[i].Banner != nil { - bannerCopy := *request.Imp[i].Banner - if bannerCopy.W == nil && bannerCopy.H == nil && len(bannerCopy.Format) > 0 { - format := bannerCopy.Format[0] - bannerCopy.W = &(format.W) - bannerCopy.H = &(format.H) - } - request.Imp[i].Banner = &bannerCopy - validImps = append(validImps, request.Imp[i]) - trackingId = zone - } else if request.Imp[i].Video != nil { - err := validateVideoParams(request.Imp[i].Video) - if err != nil { - errs = append(errs, err) - } else { + } else { + if request.Imp[i].Banner != nil { + bannerCopy := *request.Imp[i].Banner + if bannerCopy.W == nil && bannerCopy.H == nil && len(bannerCopy.Format) > 0 { + format := bannerCopy.Format[0] + bannerCopy.W = &(format.W) + bannerCopy.H = &(format.H) + } + request.Imp[i].Banner = &bannerCopy validImps = append(validImps, request.Imp[i]) trackingId = zone } @@ -80,7 +70,6 @@ func (g *GumGumAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapt }}, errs } -// MakeBids unpacks the server's response into Bids. func (g *GumGumAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil @@ -109,19 +98,12 @@ func (g *GumGumAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRe for _, sb := range bidResp.SeatBid { for i := range sb.Bid { - mediaType := getMediaTypeForImpID(sb.Bid[i].ImpID, internalRequest.Imp) - if mediaType == openrtb_ext.BidTypeVideo { - price := strconv.FormatFloat(sb.Bid[i].Price, 'f', -1, 64) - sb.Bid[i].AdM = strings.Replace(sb.Bid[i].AdM, "${AUCTION_PRICE}", price, -1) - } - bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ Bid: &sb.Bid[i], - BidType: mediaType, + BidType: openrtb_ext.BidTypeBanner, }) } } - return bidResponse, errs } @@ -146,25 +128,6 @@ func preprocess(imp *openrtb.Imp) (string, error) { return zone, nil } -func getMediaTypeForImpID(impID string, imps []openrtb.Imp) openrtb_ext.BidType { - for _, imp := range imps { - if imp.ID == impID && imp.Banner != nil { - return openrtb_ext.BidTypeBanner - } - } - return openrtb_ext.BidTypeVideo -} - -func validateVideoParams(video *openrtb.Video) (err error) { - // if video.W == 0 || video.H == 0 || video.MinDuration == 0 || video.MaxDuration == 0 || video.Placement == 0 || video.Linearity == 0 { - // return &errortypes.BadInput{ - // Message: "Invalid or missing video field(s)", - // } - // } - return nil -} - -// NewGumGumBidder configures bidder endpoint. func NewGumGumBidder(endpoint string) *GumGumAdapter { return &GumGumAdapter{ URI: endpoint, diff --git a/adapters/pulsepoint/pulsepoint.go b/adapters/pulsepoint/pulsepoint.go index 54ee7d48221..a4667136ba6 100644 --- a/adapters/pulsepoint/pulsepoint.go +++ b/adapters/pulsepoint/pulsepoint.go @@ -55,21 +55,21 @@ func (a *PulsePointAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidde 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", - // } - // } + 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 @@ -92,25 +92,23 @@ func (a *PulsePointAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidde if err == nil { ppReq.Imp[i].Banner.W = openrtb.Uint64Ptr(uint64(width)) } else { - // return nil, &errortypes.BadInput{ - // Message: fmt.Sprintf("Invalid Width param %s", size[0]), - // } + 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 = openrtb.Uint64Ptr(uint64(height)) } else { - // return nil, &errortypes.BadInput{ - // Message: fmt.Sprintf("Invalid Height param %s", size[1]), - // } + 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), - // } + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("Invalid AdSize param %s", params.AdSize), + } } - } else if ppReq.Imp[i].Video != nil { - } } reqJSON, err := json.Marshal(ppReq) @@ -123,7 +121,7 @@ func (a *PulsePointAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidde bidder.Debug = append(bidder.Debug, debug) } - httpReq, err := http.NewRequest("POST", "http://rts.lga.contextweb.com/rt-seller/bid/openrtb/playwire", bytes.NewBuffer(reqJSON)) + 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") @@ -204,4 +202,4 @@ func NewPulsePointAdapter(config *adapters.HTTPAdapterConfig, uri string) *Pulse http: a, URI: uri, } -} +} \ No newline at end of file From ba3001c4251208fa8db0fa5101bb46625460b802 Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Mon, 24 Aug 2020 16:26:15 -0400 Subject: [PATCH 008/125] revert originals --- adapters/gumgum/gumgum.go | 2 +- adapters/pulsepoint/pulsepoint.go | 2 +- static/bidder-info/gumgum.yaml | 1 - static/bidder-info/pulsepoint.yaml | 3 +-- static/bidder-params/gumgum.json | 3 ++- static/bidder-params/pulsepoint.json | 3 ++- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/adapters/gumgum/gumgum.go b/adapters/gumgum/gumgum.go index c4732e615b0..84a008d1891 100644 --- a/adapters/gumgum/gumgum.go +++ b/adapters/gumgum/gumgum.go @@ -132,4 +132,4 @@ func NewGumGumBidder(endpoint string) *GumGumAdapter { return &GumGumAdapter{ URI: endpoint, } -} \ No newline at end of file +} diff --git a/adapters/pulsepoint/pulsepoint.go b/adapters/pulsepoint/pulsepoint.go index a4667136ba6..3a5123084ba 100644 --- a/adapters/pulsepoint/pulsepoint.go +++ b/adapters/pulsepoint/pulsepoint.go @@ -202,4 +202,4 @@ func NewPulsePointAdapter(config *adapters.HTTPAdapterConfig, uri string) *Pulse http: a, URI: uri, } -} \ No newline at end of file +} diff --git a/static/bidder-info/gumgum.yaml b/static/bidder-info/gumgum.yaml index 3dab19a6a1e..b8a3981c9f0 100644 --- a/static/bidder-info/gumgum.yaml +++ b/static/bidder-info/gumgum.yaml @@ -4,4 +4,3 @@ capabilities: site: mediaTypes: - banner - - video diff --git a/static/bidder-info/pulsepoint.yaml b/static/bidder-info/pulsepoint.yaml index 2238ca1549a..4bb7ea14d65 100644 --- a/static/bidder-info/pulsepoint.yaml +++ b/static/bidder-info/pulsepoint.yaml @@ -6,5 +6,4 @@ capabilities: - banner site: mediaTypes: - - banner - - video \ No newline at end of file + - banner \ No newline at end of file diff --git a/static/bidder-params/gumgum.json b/static/bidder-params/gumgum.json index 43fe6867b82..5ec2b8e0cbf 100644 --- a/static/bidder-params/gumgum.json +++ b/static/bidder-params/gumgum.json @@ -9,5 +9,6 @@ "description": "A tracking id used to identify GumGum zone.", "minLength": 8 } - } + }, + "required": ["zone"] } diff --git a/static/bidder-params/pulsepoint.json b/static/bidder-params/pulsepoint.json index 6be52fc6e4a..c4704c3b42e 100644 --- a/static/bidder-params/pulsepoint.json +++ b/static/bidder-params/pulsepoint.json @@ -17,5 +17,6 @@ "pattern": "^[0-9]+[xX][0-9]+$", "description": "The size of the ad slot being sold. This should be a string like 300X250" } - } + }, + "required": ["cp", "ct", "cf"] } From 629acb6ecd2d745d1498c9126c088ab54f92f1c8 Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Mon, 24 Aug 2020 16:27:30 -0400 Subject: [PATCH 009/125] revert originals --- static/bidder-info/pulsepoint.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/bidder-info/pulsepoint.yaml b/static/bidder-info/pulsepoint.yaml index 4bb7ea14d65..b9fd32427b1 100644 --- a/static/bidder-info/pulsepoint.yaml +++ b/static/bidder-info/pulsepoint.yaml @@ -6,4 +6,4 @@ capabilities: - banner site: mediaTypes: - - banner \ No newline at end of file + - banner From 0bdc991320c2ba695aeaa5b0adbde159979cc804 Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Tue, 25 Aug 2020 15:57:49 -0400 Subject: [PATCH 010/125] chess app stored request add units --- .../data/by_id/stored_imps/.gitignore | 3 -- .../by_id/stored_imps/content_top_ios.json | 42 +++++++++++++++++ .../data/by_id/stored_imps/game_over_ios.json | 46 +++++++++++++++++++ .../by_id/stored_imps/in_article_ios.json | 46 +++++++++++++++++++ .../by_id/stored_imps/play_screen_ios.json | 46 +++++++++++++++++++ 5 files changed, 180 insertions(+), 3 deletions(-) delete mode 100644 stored_requests/data/by_id/stored_imps/.gitignore create mode 100644 stored_requests/data/by_id/stored_imps/content_top_ios.json create mode 100644 stored_requests/data/by_id/stored_imps/game_over_ios.json create mode 100644 stored_requests/data/by_id/stored_imps/in_article_ios.json create mode 100644 stored_requests/data/by_id/stored_imps/play_screen_ios.json diff --git a/stored_requests/data/by_id/stored_imps/.gitignore b/stored_requests/data/by_id/stored_imps/.gitignore deleted file mode 100644 index 9a3be781f63..00000000000 --- a/stored_requests/data/by_id/stored_imps/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Ignore everything in this directory, except for this file -* -!.gitignore diff --git a/stored_requests/data/by_id/stored_imps/content_top_ios.json b/stored_requests/data/by_id/stored_imps/content_top_ios.json new file mode 100644 index 00000000000..f6fde708a0f --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/content_top_ios.json @@ -0,0 +1,42 @@ +{ + "id": "content_top_ios", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "appnexus": { + "placementId": "19689655" + }, + "districtm": { + "placementId": "19765947" + }, + "emx_digital": { + "tagid": "107133" + }, + "openx": { + "unit": "541169197", + "delDomain": "playwire-d.openx.net" + }, + "pubmatic": { + "publisherId": "158326" + }, + "rhythmone": { + "placementId": "213224", + "zone": "1r", + "path": "mvo" + }, + "rubicon": { + "accountId": "15526", + "siteId": "335554", + "zoneId": "1764488" + }, + "sonobi": { + "ad_unit": "12eb86143abb80d6a9c6" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/game_over_ios.json b/stored_requests/data/by_id/stored_imps/game_over_ios.json new file mode 100644 index 00000000000..75b70c15491 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/game_over_ios.json @@ -0,0 +1,46 @@ +{ + "id": "game_over_ios", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "appnexus": { + "placementId": "19689655" + }, + "districtm": { + "placementId": "19765947" + }, + "emx_digital": { + "tagid": "107133" + }, + "openx": { + "unit": "541169197", + "delDomain": "playwire-d.openx.net" + }, + "pubmatic": { + "publisherId": "158326" + }, + "rhythmone": { + "placementId": "213224", + "zone": "1r", + "path": "mvo" + }, + "rubicon": { + "accountId": "15526", + "siteId": "335554", + "zoneId": "1764488" + }, + "sonobi": { + "ad_unit": "12eb86143abb80d6a9c6" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/in_article_ios.json b/stored_requests/data/by_id/stored_imps/in_article_ios.json new file mode 100644 index 00000000000..75b70c15491 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/in_article_ios.json @@ -0,0 +1,46 @@ +{ + "id": "game_over_ios", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "appnexus": { + "placementId": "19689655" + }, + "districtm": { + "placementId": "19765947" + }, + "emx_digital": { + "tagid": "107133" + }, + "openx": { + "unit": "541169197", + "delDomain": "playwire-d.openx.net" + }, + "pubmatic": { + "publisherId": "158326" + }, + "rhythmone": { + "placementId": "213224", + "zone": "1r", + "path": "mvo" + }, + "rubicon": { + "accountId": "15526", + "siteId": "335554", + "zoneId": "1764488" + }, + "sonobi": { + "ad_unit": "12eb86143abb80d6a9c6" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/play_screen_ios.json b/stored_requests/data/by_id/stored_imps/play_screen_ios.json new file mode 100644 index 00000000000..75b70c15491 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/play_screen_ios.json @@ -0,0 +1,46 @@ +{ + "id": "game_over_ios", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "appnexus": { + "placementId": "19689655" + }, + "districtm": { + "placementId": "19765947" + }, + "emx_digital": { + "tagid": "107133" + }, + "openx": { + "unit": "541169197", + "delDomain": "playwire-d.openx.net" + }, + "pubmatic": { + "publisherId": "158326" + }, + "rhythmone": { + "placementId": "213224", + "zone": "1r", + "path": "mvo" + }, + "rubicon": { + "accountId": "15526", + "siteId": "335554", + "zoneId": "1764488" + }, + "sonobi": { + "ad_unit": "12eb86143abb80d6a9c6" + } + } +} \ No newline at end of file From 9d7c7a37a8cca8c79dcc74280beedd726d87609e Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Tue, 25 Aug 2020 16:05:01 -0400 Subject: [PATCH 011/125] updaate emx --- stored_requests/data/by_id/stored_imps/game_over_ios.json | 2 +- stored_requests/data/by_id/stored_imps/in_article_ios.json | 2 +- stored_requests/data/by_id/stored_imps/play_screen_ios.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/stored_requests/data/by_id/stored_imps/game_over_ios.json b/stored_requests/data/by_id/stored_imps/game_over_ios.json index 75b70c15491..e9b326e71a6 100644 --- a/stored_requests/data/by_id/stored_imps/game_over_ios.json +++ b/stored_requests/data/by_id/stored_imps/game_over_ios.json @@ -20,7 +20,7 @@ "placementId": "19765947" }, "emx_digital": { - "tagid": "107133" + "tagid": "105251" }, "openx": { "unit": "541169197", diff --git a/stored_requests/data/by_id/stored_imps/in_article_ios.json b/stored_requests/data/by_id/stored_imps/in_article_ios.json index 75b70c15491..e9b326e71a6 100644 --- a/stored_requests/data/by_id/stored_imps/in_article_ios.json +++ b/stored_requests/data/by_id/stored_imps/in_article_ios.json @@ -20,7 +20,7 @@ "placementId": "19765947" }, "emx_digital": { - "tagid": "107133" + "tagid": "105251" }, "openx": { "unit": "541169197", diff --git a/stored_requests/data/by_id/stored_imps/play_screen_ios.json b/stored_requests/data/by_id/stored_imps/play_screen_ios.json index 75b70c15491..e9b326e71a6 100644 --- a/stored_requests/data/by_id/stored_imps/play_screen_ios.json +++ b/stored_requests/data/by_id/stored_imps/play_screen_ios.json @@ -20,7 +20,7 @@ "placementId": "19765947" }, "emx_digital": { - "tagid": "107133" + "tagid": "105251" }, "openx": { "unit": "541169197", From ea87e098f4147ad29a0a707eaf05025b25a02c84 Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Wed, 26 Aug 2020 12:16:01 -0400 Subject: [PATCH 012/125] match id name to file --- stored_requests/data/by_id/stored_imps/in_article_ios.json | 2 +- stored_requests/data/by_id/stored_imps/play_screen_ios.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/stored_requests/data/by_id/stored_imps/in_article_ios.json b/stored_requests/data/by_id/stored_imps/in_article_ios.json index e9b326e71a6..6000306813e 100644 --- a/stored_requests/data/by_id/stored_imps/in_article_ios.json +++ b/stored_requests/data/by_id/stored_imps/in_article_ios.json @@ -1,5 +1,5 @@ { - "id": "game_over_ios", + "id": "in_article_ios", "banner": { "format": [ { diff --git a/stored_requests/data/by_id/stored_imps/play_screen_ios.json b/stored_requests/data/by_id/stored_imps/play_screen_ios.json index e9b326e71a6..185f80579f1 100644 --- a/stored_requests/data/by_id/stored_imps/play_screen_ios.json +++ b/stored_requests/data/by_id/stored_imps/play_screen_ios.json @@ -1,5 +1,5 @@ { - "id": "game_over_ios", + "id": "play_screen_ios", "banner": { "format": [ { From 6d6e2d6382c982142ae4b1d70a16e35ca43a9f1d Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Thu, 27 Aug 2020 16:48:27 -0400 Subject: [PATCH 013/125] pass correct key for sonobi --- stored_requests/data/by_id/stored_imps/content_top_ios.json | 2 +- stored_requests/data/by_id/stored_imps/game_over_ios.json | 2 +- stored_requests/data/by_id/stored_imps/in_article_ios.json | 6 +++--- stored_requests/data/by_id/stored_imps/play_screen_ios.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/stored_requests/data/by_id/stored_imps/content_top_ios.json b/stored_requests/data/by_id/stored_imps/content_top_ios.json index f6fde708a0f..21a0f9d393d 100644 --- a/stored_requests/data/by_id/stored_imps/content_top_ios.json +++ b/stored_requests/data/by_id/stored_imps/content_top_ios.json @@ -36,7 +36,7 @@ "zoneId": "1764488" }, "sonobi": { - "ad_unit": "12eb86143abb80d6a9c6" + "TagID": "12eb86143abb80d6a9c6" } } } \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/game_over_ios.json b/stored_requests/data/by_id/stored_imps/game_over_ios.json index e9b326e71a6..ebffec40040 100644 --- a/stored_requests/data/by_id/stored_imps/game_over_ios.json +++ b/stored_requests/data/by_id/stored_imps/game_over_ios.json @@ -40,7 +40,7 @@ "zoneId": "1764488" }, "sonobi": { - "ad_unit": "12eb86143abb80d6a9c6" + "TagID": "12eb86143abb80d6a9c6" } } } \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/in_article_ios.json b/stored_requests/data/by_id/stored_imps/in_article_ios.json index 6000306813e..03f08e9ae87 100644 --- a/stored_requests/data/by_id/stored_imps/in_article_ios.json +++ b/stored_requests/data/by_id/stored_imps/in_article_ios.json @@ -14,10 +14,10 @@ }, "ext": { "appnexus": { - "placementId": "19689655" + "placementId": 19689655 }, "districtm": { - "placementId": "19765947" + "placementId": 19765947 }, "emx_digital": { "tagid": "105251" @@ -40,7 +40,7 @@ "zoneId": "1764488" }, "sonobi": { - "ad_unit": "12eb86143abb80d6a9c6" + "TagID": "12eb86143abb80d6a9c6" } } } \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/play_screen_ios.json b/stored_requests/data/by_id/stored_imps/play_screen_ios.json index 185f80579f1..6f19ff2cb1d 100644 --- a/stored_requests/data/by_id/stored_imps/play_screen_ios.json +++ b/stored_requests/data/by_id/stored_imps/play_screen_ios.json @@ -40,7 +40,7 @@ "zoneId": "1764488" }, "sonobi": { - "ad_unit": "12eb86143abb80d6a9c6" + "TagID": "12eb86143abb80d6a9c6" } } } \ No newline at end of file From d398acbb14146bd89c36a08c6efb8f7fc0156b77 Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Thu, 27 Aug 2020 17:04:05 -0400 Subject: [PATCH 014/125] appnexus/districtm should be integers --- stored_requests/data/by_id/stored_imps/content_top_ios.json | 4 ++-- stored_requests/data/by_id/stored_imps/game_over_ios.json | 4 ++-- stored_requests/data/by_id/stored_imps/in_article_ios.json | 4 ++-- stored_requests/data/by_id/stored_imps/play_screen_ios.json | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/stored_requests/data/by_id/stored_imps/content_top_ios.json b/stored_requests/data/by_id/stored_imps/content_top_ios.json index 21a0f9d393d..7b10573219b 100644 --- a/stored_requests/data/by_id/stored_imps/content_top_ios.json +++ b/stored_requests/data/by_id/stored_imps/content_top_ios.json @@ -10,10 +10,10 @@ }, "ext": { "appnexus": { - "placementId": "19689655" + "placement_id": 19689655 }, "districtm": { - "placementId": "19765947" + "placement_id": 19765947 }, "emx_digital": { "tagid": "107133" diff --git a/stored_requests/data/by_id/stored_imps/game_over_ios.json b/stored_requests/data/by_id/stored_imps/game_over_ios.json index ebffec40040..d4bb3a589c5 100644 --- a/stored_requests/data/by_id/stored_imps/game_over_ios.json +++ b/stored_requests/data/by_id/stored_imps/game_over_ios.json @@ -14,10 +14,10 @@ }, "ext": { "appnexus": { - "placementId": "19689655" + "placement_id": 19689655 }, "districtm": { - "placementId": "19765947" + "placement_id": 19765947 }, "emx_digital": { "tagid": "105251" diff --git a/stored_requests/data/by_id/stored_imps/in_article_ios.json b/stored_requests/data/by_id/stored_imps/in_article_ios.json index 03f08e9ae87..4cde69db9f4 100644 --- a/stored_requests/data/by_id/stored_imps/in_article_ios.json +++ b/stored_requests/data/by_id/stored_imps/in_article_ios.json @@ -14,10 +14,10 @@ }, "ext": { "appnexus": { - "placementId": 19689655 + "placement_id": 19689655 }, "districtm": { - "placementId": 19765947 + "placement_id": 19765947 }, "emx_digital": { "tagid": "105251" diff --git a/stored_requests/data/by_id/stored_imps/play_screen_ios.json b/stored_requests/data/by_id/stored_imps/play_screen_ios.json index 6f19ff2cb1d..803e252985d 100644 --- a/stored_requests/data/by_id/stored_imps/play_screen_ios.json +++ b/stored_requests/data/by_id/stored_imps/play_screen_ios.json @@ -14,10 +14,10 @@ }, "ext": { "appnexus": { - "placementId": "19689655" + "placement_id": 19689655 }, "districtm": { - "placementId": "19765947" + "placement_id": 19765947 }, "emx_digital": { "tagid": "105251" From fa8f181c41c35a9acdecb2c2741c7213e5a869b4 Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Thu, 27 Aug 2020 17:08:30 -0400 Subject: [PATCH 015/125] rubicon should be integers --- stored_requests/data/by_id/stored_imps/content_top_ios.json | 6 +++--- stored_requests/data/by_id/stored_imps/game_over_ios.json | 6 +++--- stored_requests/data/by_id/stored_imps/in_article_ios.json | 6 +++--- stored_requests/data/by_id/stored_imps/play_screen_ios.json | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/stored_requests/data/by_id/stored_imps/content_top_ios.json b/stored_requests/data/by_id/stored_imps/content_top_ios.json index 7b10573219b..276e7bef33b 100644 --- a/stored_requests/data/by_id/stored_imps/content_top_ios.json +++ b/stored_requests/data/by_id/stored_imps/content_top_ios.json @@ -31,9 +31,9 @@ "path": "mvo" }, "rubicon": { - "accountId": "15526", - "siteId": "335554", - "zoneId": "1764488" + "accountId": 15526, + "siteId": 335554, + "zoneId": 1764488 }, "sonobi": { "TagID": "12eb86143abb80d6a9c6" diff --git a/stored_requests/data/by_id/stored_imps/game_over_ios.json b/stored_requests/data/by_id/stored_imps/game_over_ios.json index d4bb3a589c5..386915a5395 100644 --- a/stored_requests/data/by_id/stored_imps/game_over_ios.json +++ b/stored_requests/data/by_id/stored_imps/game_over_ios.json @@ -35,9 +35,9 @@ "path": "mvo" }, "rubicon": { - "accountId": "15526", - "siteId": "335554", - "zoneId": "1764488" + "accountId": 15526, + "siteId": 335554, + "zoneId": 1764488 }, "sonobi": { "TagID": "12eb86143abb80d6a9c6" diff --git a/stored_requests/data/by_id/stored_imps/in_article_ios.json b/stored_requests/data/by_id/stored_imps/in_article_ios.json index 4cde69db9f4..a00834c95bb 100644 --- a/stored_requests/data/by_id/stored_imps/in_article_ios.json +++ b/stored_requests/data/by_id/stored_imps/in_article_ios.json @@ -35,9 +35,9 @@ "path": "mvo" }, "rubicon": { - "accountId": "15526", - "siteId": "335554", - "zoneId": "1764488" + "accountId": 15526, + "siteId": 335554, + "zoneId": 1764488 }, "sonobi": { "TagID": "12eb86143abb80d6a9c6" diff --git a/stored_requests/data/by_id/stored_imps/play_screen_ios.json b/stored_requests/data/by_id/stored_imps/play_screen_ios.json index 803e252985d..eb7e0da5992 100644 --- a/stored_requests/data/by_id/stored_imps/play_screen_ios.json +++ b/stored_requests/data/by_id/stored_imps/play_screen_ios.json @@ -35,9 +35,9 @@ "path": "mvo" }, "rubicon": { - "accountId": "15526", - "siteId": "335554", - "zoneId": "1764488" + "accountId": 15526, + "siteId": 335554, + "zoneId": 1764488 }, "sonobi": { "TagID": "12eb86143abb80d6a9c6" From c4f4803e25db466c56d98368859f4c2675761849 Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Fri, 28 Aug 2020 09:53:12 -0400 Subject: [PATCH 016/125] add a "playwire" stored request --- .../data/by_id/stored_imps/playwire.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 stored_requests/data/by_id/stored_imps/playwire.json diff --git a/stored_requests/data/by_id/stored_imps/playwire.json b/stored_requests/data/by_id/stored_imps/playwire.json new file mode 100644 index 00000000000..6eaae21818b --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/playwire.json @@ -0,0 +1,16 @@ +{ + "id": "playwire", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "appnexus": { + "placement_id": 19689655 + } + } +} \ No newline at end of file From f996887f741be5e029d558176ecc903272b2ad89 Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Fri, 28 Aug 2020 10:38:45 -0400 Subject: [PATCH 017/125] try stored_request path --- .../data/by_id/stored_requests/.gitignore | 3 --- .../data/by_id/stored_requests/playwire.json | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) delete mode 100644 stored_requests/data/by_id/stored_requests/.gitignore create mode 100644 stored_requests/data/by_id/stored_requests/playwire.json diff --git a/stored_requests/data/by_id/stored_requests/.gitignore b/stored_requests/data/by_id/stored_requests/.gitignore deleted file mode 100644 index 9a3be781f63..00000000000 --- a/stored_requests/data/by_id/stored_requests/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Ignore everything in this directory, except for this file -* -!.gitignore diff --git a/stored_requests/data/by_id/stored_requests/playwire.json b/stored_requests/data/by_id/stored_requests/playwire.json new file mode 100644 index 00000000000..6eaae21818b --- /dev/null +++ b/stored_requests/data/by_id/stored_requests/playwire.json @@ -0,0 +1,16 @@ +{ + "id": "playwire", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "appnexus": { + "placement_id": 19689655 + } + } +} \ No newline at end of file From d3c46974fe484347bc9a3cbdb7ee737b48b0c083 Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Fri, 28 Aug 2020 10:40:22 -0400 Subject: [PATCH 018/125] remove from stored_imps --- .../data/by_id/stored_imps/playwire.json | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 stored_requests/data/by_id/stored_imps/playwire.json diff --git a/stored_requests/data/by_id/stored_imps/playwire.json b/stored_requests/data/by_id/stored_imps/playwire.json deleted file mode 100644 index 6eaae21818b..00000000000 --- a/stored_requests/data/by_id/stored_imps/playwire.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "id": "playwire", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "appnexus": { - "placement_id": 19689655 - } - } -} \ No newline at end of file From 90b0a6de0e60d2bc4eccbb825b82c8308c54a8cb Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Fri, 28 Aug 2020 10:58:12 -0400 Subject: [PATCH 019/125] remove districtm, specify timeout in stored_request --- .../data/by_id/stored_imps/content_top_ios.json | 3 --- .../data/by_id/stored_imps/game_over_ios.json | 3 --- .../data/by_id/stored_imps/in_article_ios.json | 3 --- .../data/by_id/stored_imps/play_screen_ios.json | 3 --- .../data/by_id/stored_requests/playwire.json | 15 +-------------- 5 files changed, 1 insertion(+), 26 deletions(-) diff --git a/stored_requests/data/by_id/stored_imps/content_top_ios.json b/stored_requests/data/by_id/stored_imps/content_top_ios.json index 276e7bef33b..f57df9a7bf5 100644 --- a/stored_requests/data/by_id/stored_imps/content_top_ios.json +++ b/stored_requests/data/by_id/stored_imps/content_top_ios.json @@ -12,9 +12,6 @@ "appnexus": { "placement_id": 19689655 }, - "districtm": { - "placement_id": 19765947 - }, "emx_digital": { "tagid": "107133" }, diff --git a/stored_requests/data/by_id/stored_imps/game_over_ios.json b/stored_requests/data/by_id/stored_imps/game_over_ios.json index 386915a5395..5177a12aa9a 100644 --- a/stored_requests/data/by_id/stored_imps/game_over_ios.json +++ b/stored_requests/data/by_id/stored_imps/game_over_ios.json @@ -16,9 +16,6 @@ "appnexus": { "placement_id": 19689655 }, - "districtm": { - "placement_id": 19765947 - }, "emx_digital": { "tagid": "105251" }, diff --git a/stored_requests/data/by_id/stored_imps/in_article_ios.json b/stored_requests/data/by_id/stored_imps/in_article_ios.json index a00834c95bb..f1e4b2fff3a 100644 --- a/stored_requests/data/by_id/stored_imps/in_article_ios.json +++ b/stored_requests/data/by_id/stored_imps/in_article_ios.json @@ -16,9 +16,6 @@ "appnexus": { "placement_id": 19689655 }, - "districtm": { - "placement_id": 19765947 - }, "emx_digital": { "tagid": "105251" }, diff --git a/stored_requests/data/by_id/stored_imps/play_screen_ios.json b/stored_requests/data/by_id/stored_imps/play_screen_ios.json index eb7e0da5992..3208a74f50e 100644 --- a/stored_requests/data/by_id/stored_imps/play_screen_ios.json +++ b/stored_requests/data/by_id/stored_imps/play_screen_ios.json @@ -16,9 +16,6 @@ "appnexus": { "placement_id": 19689655 }, - "districtm": { - "placement_id": 19765947 - }, "emx_digital": { "tagid": "105251" }, diff --git a/stored_requests/data/by_id/stored_requests/playwire.json b/stored_requests/data/by_id/stored_requests/playwire.json index 6eaae21818b..263efbe2eff 100644 --- a/stored_requests/data/by_id/stored_requests/playwire.json +++ b/stored_requests/data/by_id/stored_requests/playwire.json @@ -1,16 +1,3 @@ { - "id": "playwire", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "appnexus": { - "placement_id": 19689655 - } - } + "tmax": 1000 } \ No newline at end of file From a183b6e8a73559e09257b69c7e5ff36271285f9d Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Fri, 28 Aug 2020 11:47:09 -0400 Subject: [PATCH 020/125] try rubicon with display params for ios testing --- stored_requests/data/by_id/stored_imps/in_article_ios.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stored_requests/data/by_id/stored_imps/in_article_ios.json b/stored_requests/data/by_id/stored_imps/in_article_ios.json index f1e4b2fff3a..b08ed3b8c12 100644 --- a/stored_requests/data/by_id/stored_imps/in_article_ios.json +++ b/stored_requests/data/by_id/stored_imps/in_article_ios.json @@ -33,8 +33,8 @@ }, "rubicon": { "accountId": 15526, - "siteId": 335554, - "zoneId": 1764488 + "siteId": 110932, + "zoneId": 523774 }, "sonobi": { "TagID": "12eb86143abb80d6a9c6" From 2d2bb16d440f9899c36104f3bffae47d7c23c17e Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Fri, 28 Aug 2020 12:15:35 -0400 Subject: [PATCH 021/125] update test ids --- stored_requests/data/by_id/stored_imps/in_article_ios.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stored_requests/data/by_id/stored_imps/in_article_ios.json b/stored_requests/data/by_id/stored_imps/in_article_ios.json index b08ed3b8c12..b10218365e1 100644 --- a/stored_requests/data/by_id/stored_imps/in_article_ios.json +++ b/stored_requests/data/by_id/stored_imps/in_article_ios.json @@ -14,7 +14,7 @@ }, "ext": { "appnexus": { - "placement_id": 19689655 + "placement_id": 19689654 }, "emx_digital": { "tagid": "105251" @@ -34,7 +34,7 @@ "rubicon": { "accountId": 15526, "siteId": 110932, - "zoneId": 523774 + "zoneId": 1764488 }, "sonobi": { "TagID": "12eb86143abb80d6a9c6" From 7adf5a00ac618555f7b80c1f74eb6d1cb50d16ca Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Fri, 28 Aug 2020 12:17:18 -0400 Subject: [PATCH 022/125] update test ids --- stored_requests/data/by_id/stored_imps/in_article_ios.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stored_requests/data/by_id/stored_imps/in_article_ios.json b/stored_requests/data/by_id/stored_imps/in_article_ios.json index b10218365e1..6e0a46f4950 100644 --- a/stored_requests/data/by_id/stored_imps/in_article_ios.json +++ b/stored_requests/data/by_id/stored_imps/in_article_ios.json @@ -33,7 +33,7 @@ }, "rubicon": { "accountId": 15526, - "siteId": 110932, + "siteId": 335554, "zoneId": 1764488 }, "sonobi": { From 6983f51fa3a02053508bc0bde177ef6eff132b4b Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Mon, 31 Aug 2020 10:00:20 -0400 Subject: [PATCH 023/125] try ad unit in stored_requests foldre --- .../by_id/{stored_imps => stored_requests}/in_article_ios.json | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename stored_requests/data/by_id/{stored_imps => stored_requests}/in_article_ios.json (100%) diff --git a/stored_requests/data/by_id/stored_imps/in_article_ios.json b/stored_requests/data/by_id/stored_requests/in_article_ios.json similarity index 100% rename from stored_requests/data/by_id/stored_imps/in_article_ios.json rename to stored_requests/data/by_id/stored_requests/in_article_ios.json From 9501ba718c293a3e8ca3a3b60d8f4da356a73b48 Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Mon, 31 Aug 2020 10:12:11 -0400 Subject: [PATCH 024/125] try in_article_ios in both folders --- .../by_id/stored_imps/in_article_ios.json | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 stored_requests/data/by_id/stored_imps/in_article_ios.json diff --git a/stored_requests/data/by_id/stored_imps/in_article_ios.json b/stored_requests/data/by_id/stored_imps/in_article_ios.json new file mode 100644 index 00000000000..6e0a46f4950 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/in_article_ios.json @@ -0,0 +1,43 @@ +{ + "id": "in_article_ios", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "appnexus": { + "placement_id": 19689654 + }, + "emx_digital": { + "tagid": "105251" + }, + "openx": { + "unit": "541169197", + "delDomain": "playwire-d.openx.net" + }, + "pubmatic": { + "publisherId": "158326" + }, + "rhythmone": { + "placementId": "213224", + "zone": "1r", + "path": "mvo" + }, + "rubicon": { + "accountId": 15526, + "siteId": 335554, + "zoneId": 1764488 + }, + "sonobi": { + "TagID": "12eb86143abb80d6a9c6" + } + } +} \ No newline at end of file From 9ae658fdfc23848b0f34e159620fadd1d72cc837 Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Thu, 1 Oct 2020 12:41:27 -0400 Subject: [PATCH 025/125] update bidder names to ortb, use single playwire adapter --- .../params_test.go | 2 +- .../playwire_ortb.go} | 6 +- adapters/playwire_ortb/playwire_ortb_test.go | 10 + .../playwire_ortbtest}/exemplary/banner.json | 0 .../params/race/banner.json | 0 .../{s2s_gumgum => playwire_ortb}/usersync.go | 2 +- .../usersync_test.go | 2 +- adapters/s2s_gumgum/s2s_gumgum_test.go | 10 - adapters/s2s_pulsepoint/params_test.go | 52 ------ adapters/s2s_pulsepoint/s2s_pulsepoint.go | 172 ------------------ .../s2s_pulsepoint/s2s_pulsepoint_test.go | 10 - .../s2s_pulsepointtest/exemplary/banner.json | 99 ---------- .../params/race/banner.json | 3 - adapters/s2s_pulsepoint/usersync.go | 12 -- adapters/s2s_pulsepoint/usersync_test.go | 35 ---- config/config.go | 4 +- exchange/adapter_map.go | 7 +- openrtb_ext/bidders.go | 8 +- .../{s2s_gumgum.yaml => gumgum_ortb.yaml} | 0 ...s_pulsepoint.yaml => pulsepoint_ortb.yaml} | 0 .../{s2s_gumgum.json => gumgum_ortb.json} | 0 ...s_pulsepoint.json => pulsepoint_ortb.json} | 0 22 files changed, 25 insertions(+), 409 deletions(-) rename adapters/{s2s_gumgum => playwire_ortb}/params_test.go (98%) rename adapters/{s2s_gumgum/s2s_gumgum.go => playwire_ortb/playwire_ortb.go} (97%) create mode 100644 adapters/playwire_ortb/playwire_ortb_test.go rename adapters/{s2s_gumgum/s2s_gumgumtest => playwire_ortb/playwire_ortbtest}/exemplary/banner.json (100%) rename adapters/{s2s_gumgum/s2s_gumgumtest => playwire_ortb/playwire_ortbtest}/params/race/banner.json (100%) rename adapters/{s2s_gumgum => playwire_ortb}/usersync.go (92%) rename adapters/{s2s_gumgum => playwire_ortb}/usersync_test.go (98%) delete mode 100644 adapters/s2s_gumgum/s2s_gumgum_test.go delete mode 100644 adapters/s2s_pulsepoint/params_test.go delete mode 100644 adapters/s2s_pulsepoint/s2s_pulsepoint.go delete mode 100644 adapters/s2s_pulsepoint/s2s_pulsepoint_test.go delete mode 100644 adapters/s2s_pulsepoint/s2s_pulsepointtest/exemplary/banner.json delete mode 100644 adapters/s2s_pulsepoint/s2s_pulsepointtest/params/race/banner.json delete mode 100644 adapters/s2s_pulsepoint/usersync.go delete mode 100644 adapters/s2s_pulsepoint/usersync_test.go rename static/bidder-info/{s2s_gumgum.yaml => gumgum_ortb.yaml} (100%) rename static/bidder-info/{s2s_pulsepoint.yaml => pulsepoint_ortb.yaml} (100%) rename static/bidder-params/{s2s_gumgum.json => gumgum_ortb.json} (100%) rename static/bidder-params/{s2s_pulsepoint.json => pulsepoint_ortb.json} (100%) diff --git a/adapters/s2s_gumgum/params_test.go b/adapters/playwire_ortb/params_test.go similarity index 98% rename from adapters/s2s_gumgum/params_test.go rename to adapters/playwire_ortb/params_test.go index 54672f37aee..47ff5cc252d 100644 --- a/adapters/s2s_gumgum/params_test.go +++ b/adapters/playwire_ortb/params_test.go @@ -1,4 +1,4 @@ -package s2s_gumgum +package playwire_ortb import ( "encoding/json" diff --git a/adapters/s2s_gumgum/s2s_gumgum.go b/adapters/playwire_ortb/playwire_ortb.go similarity index 97% rename from adapters/s2s_gumgum/s2s_gumgum.go rename to adapters/playwire_ortb/playwire_ortb.go index 876ccd85202..355e7be3d1d 100644 --- a/adapters/s2s_gumgum/s2s_gumgum.go +++ b/adapters/playwire_ortb/playwire_ortb.go @@ -1,4 +1,4 @@ -package s2s_gumgum +package playwire_ortb import ( "encoding/json" @@ -164,8 +164,8 @@ func validateVideoParams(video *openrtb.Video) (err error) { return nil } -// NewGumGumBidder configures bidder endpoint. -func NewGumGumBidder(endpoint string) *GumGumAdapter { +// NewOrtbBidder configures bidder endpoint. +func NewOrtbBidder(endpoint string) *GumGumAdapter { return &GumGumAdapter{ URI: endpoint, } diff --git a/adapters/playwire_ortb/playwire_ortb_test.go b/adapters/playwire_ortb/playwire_ortb_test.go new file mode 100644 index 00000000000..6a45a92c22b --- /dev/null +++ b/adapters/playwire_ortb/playwire_ortb_test.go @@ -0,0 +1,10 @@ +package playwire_ortb + +import ( + "github.com/prebid/prebid-server/adapters/adapterstest" + "testing" +) + +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "gumgumtest", NewOrtbBidder("https://g2.gumgum.com/providers/prbds2s/bid")) +} diff --git a/adapters/s2s_gumgum/s2s_gumgumtest/exemplary/banner.json b/adapters/playwire_ortb/playwire_ortbtest/exemplary/banner.json similarity index 100% rename from adapters/s2s_gumgum/s2s_gumgumtest/exemplary/banner.json rename to adapters/playwire_ortb/playwire_ortbtest/exemplary/banner.json diff --git a/adapters/s2s_gumgum/s2s_gumgumtest/params/race/banner.json b/adapters/playwire_ortb/playwire_ortbtest/params/race/banner.json similarity index 100% rename from adapters/s2s_gumgum/s2s_gumgumtest/params/race/banner.json rename to adapters/playwire_ortb/playwire_ortbtest/params/race/banner.json diff --git a/adapters/s2s_gumgum/usersync.go b/adapters/playwire_ortb/usersync.go similarity index 92% rename from adapters/s2s_gumgum/usersync.go rename to adapters/playwire_ortb/usersync.go index e516cc7d96a..5c9b1463e16 100644 --- a/adapters/s2s_gumgum/usersync.go +++ b/adapters/playwire_ortb/usersync.go @@ -1,4 +1,4 @@ -package s2s_gumgum +package playwire_ortb import ( "text/template" diff --git a/adapters/s2s_gumgum/usersync_test.go b/adapters/playwire_ortb/usersync_test.go similarity index 98% rename from adapters/s2s_gumgum/usersync_test.go rename to adapters/playwire_ortb/usersync_test.go index 4f73d1cb5a9..89e1c06ca13 100644 --- a/adapters/s2s_gumgum/usersync_test.go +++ b/adapters/playwire_ortb/usersync_test.go @@ -1,4 +1,4 @@ -package s2s_gumgum +package playwire_ortb import ( "testing" diff --git a/adapters/s2s_gumgum/s2s_gumgum_test.go b/adapters/s2s_gumgum/s2s_gumgum_test.go deleted file mode 100644 index 7cfae844777..00000000000 --- a/adapters/s2s_gumgum/s2s_gumgum_test.go +++ /dev/null @@ -1,10 +0,0 @@ -package s2s_gumgum - -import ( - "github.com/prebid/prebid-server/adapters/adapterstest" - "testing" -) - -func TestJsonSamples(t *testing.T) { - adapterstest.RunJSONBidderTest(t, "gumgumtest", NewGumGumBidder("https://g2.gumgum.com/providers/prbds2s/bid")) -} diff --git a/adapters/s2s_pulsepoint/params_test.go b/adapters/s2s_pulsepoint/params_test.go deleted file mode 100644 index 026ab25d244..00000000000 --- a/adapters/s2s_pulsepoint/params_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package s2s_pulsepoint - -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.BidderGumGum, json.RawMessage(validParam)); err != nil { - t.Errorf("Schema rejected gumgum 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{ - `{"zone":"dc9d6be1"}`, -} - -var invalidParams = []string{ - `null`, - `nil`, - ``, - `{}`, - `[]`, - `true`, - `2`, - `{"zone":12345678}`, - `{"zone":""}`, - `{"placementId": 1}`, - `{"zone": true}`, - `{"placementId": 1, "zone":"1234567"}`, -} diff --git a/adapters/s2s_pulsepoint/s2s_pulsepoint.go b/adapters/s2s_pulsepoint/s2s_pulsepoint.go deleted file mode 100644 index 69f532473c1..00000000000 --- a/adapters/s2s_pulsepoint/s2s_pulsepoint.go +++ /dev/null @@ -1,172 +0,0 @@ -package s2s_pulsepoint - -import ( - "encoding/json" - "fmt" - "github.com/mxmCherry/openrtb" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - "net/http" - "strconv" - "strings" -) - -// GumGumAdapter implements Bidder interface. -type GumGumAdapter struct { - URI string -} - -// MakeRequests makes the HTTP requests which should be made to fetch bids. -func (g *GumGumAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - var validImps []openrtb.Imp - var trackingId string - - numRequests := len(request.Imp) - errs := make([]error, 0, numRequests) - - for i := 0; i < numRequests; i++ { - imp := request.Imp[i] - zone, err := preprocess(&imp) - if err != nil { - errs = append(errs, err) - } else if request.Imp[i].Banner != nil { - bannerCopy := *request.Imp[i].Banner - if bannerCopy.W == nil && bannerCopy.H == nil && len(bannerCopy.Format) > 0 { - format := bannerCopy.Format[0] - bannerCopy.W = &(format.W) - bannerCopy.H = &(format.H) - } - request.Imp[i].Banner = &bannerCopy - validImps = append(validImps, request.Imp[i]) - trackingId = zone - } else if request.Imp[i].Video != nil { - err := validateVideoParams(request.Imp[i].Video) - if err != nil { - errs = append(errs, err) - } else { - validImps = append(validImps, request.Imp[i]) - trackingId = zone - } - } - } - - if len(validImps) == 0 { - return nil, errs - } - - request.Imp = validImps - - if request.Site != nil { - siteCopy := *request.Site - siteCopy.ID = trackingId - request.Site = &siteCopy - } - - reqJSON, err := json.Marshal(request) - if err != nil { - errs = append(errs, err) - return nil, errs - } - - headers := http.Header{} - headers.Add("Content-Type", "application/json;charset=utf-8") - headers.Add("Accept", "application/json") - return []*adapters.RequestData{{ - Method: "POST", - Uri: g.URI, - Body: reqJSON, - Headers: headers, - }}, errs -} - -// MakeBids unpacks the server's response into Bids. -func (g *GumGumAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { - if response.StatusCode == http.StatusNoContent { - return nil, nil - } - - if response.StatusCode == http.StatusBadRequest { - return nil, []error{&errortypes.BadInput{ - Message: fmt.Sprintf("Bad user input: HTTP status %d", response.StatusCode), - }} - } - - if response.StatusCode != http.StatusOK { - return nil, []error{&errortypes.BadServerResponse{ - Message: fmt.Sprintf("Bad server response: HTTP status %d", response.StatusCode), - }} - } - var bidResp openrtb.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { - return nil, []error{&errortypes.BadServerResponse{ - Message: fmt.Sprintf("Bad server response: %d. ", err), - }} - } - - var errs []error - bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) - - for _, sb := range bidResp.SeatBid { - for i := range sb.Bid { - mediaType := getMediaTypeForImpID(sb.Bid[i].ImpID, internalRequest.Imp) - if mediaType == openrtb_ext.BidTypeVideo { - price := strconv.FormatFloat(sb.Bid[i].Price, 'f', -1, 64) - sb.Bid[i].AdM = strings.Replace(sb.Bid[i].AdM, "${AUCTION_PRICE}", price, -1) - } - - bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ - Bid: &sb.Bid[i], - BidType: mediaType, - }) - } - } - - return bidResponse, errs -} - -func preprocess(imp *openrtb.Imp) (string, error) { - var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { - err = &errortypes.BadInput{ - Message: err.Error(), - } - return "", err - } - - var gumgumExt openrtb_ext.ExtImpGumGum - if err := json.Unmarshal(bidderExt.Bidder, &gumgumExt); err != nil { - err = &errortypes.BadInput{ - Message: err.Error(), - } - return "", err - } - - zone := gumgumExt.Zone - return zone, nil -} - -func getMediaTypeForImpID(impID string, imps []openrtb.Imp) openrtb_ext.BidType { - for _, imp := range imps { - if imp.ID == impID && imp.Banner != nil { - return openrtb_ext.BidTypeBanner - } - } - return openrtb_ext.BidTypeVideo -} - -func validateVideoParams(video *openrtb.Video) (err error) { - // if video.W == 0 || video.H == 0 || video.MinDuration == 0 || video.MaxDuration == 0 || video.Placement == 0 || video.Linearity == 0 { - // return &errortypes.BadInput{ - // Message: "Invalid or missing video field(s)", - // } - // } - return nil -} - -// NewGumGumBidder configures bidder endpoint. -func NewGumGumBidder(endpoint string) *GumGumAdapter { - return &GumGumAdapter{ - URI: endpoint, - } -} \ No newline at end of file diff --git a/adapters/s2s_pulsepoint/s2s_pulsepoint_test.go b/adapters/s2s_pulsepoint/s2s_pulsepoint_test.go deleted file mode 100644 index a314d4b6022..00000000000 --- a/adapters/s2s_pulsepoint/s2s_pulsepoint_test.go +++ /dev/null @@ -1,10 +0,0 @@ -package s2s_pulsepoint - -import ( - "github.com/prebid/prebid-server/adapters/adapterstest" - "testing" -) - -func TestJsonSamples(t *testing.T) { - adapterstest.RunJSONBidderTest(t, "gumgumtest", NewGumGumBidder("https://g2.gumgum.com/providers/prbds2s/bid")) -} diff --git a/adapters/s2s_pulsepoint/s2s_pulsepointtest/exemplary/banner.json b/adapters/s2s_pulsepoint/s2s_pulsepointtest/exemplary/banner.json deleted file mode 100644 index 2fbd3da22da..00000000000 --- a/adapters/s2s_pulsepoint/s2s_pulsepointtest/exemplary/banner.json +++ /dev/null @@ -1,99 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 300 - } - ], - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - "zone": "dc9d6be1" - } - } - } - ] - }, - - "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": { - "bidder": { - "zone": "dc9d6be1" - } - } - }] - } - }, - "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" - } - ] - } - ] -} diff --git a/adapters/s2s_pulsepoint/s2s_pulsepointtest/params/race/banner.json b/adapters/s2s_pulsepoint/s2s_pulsepointtest/params/race/banner.json deleted file mode 100644 index 6e222304f36..00000000000 --- a/adapters/s2s_pulsepoint/s2s_pulsepointtest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "zone": "dc9d6be1" -} diff --git a/adapters/s2s_pulsepoint/usersync.go b/adapters/s2s_pulsepoint/usersync.go deleted file mode 100644 index a66437b7d83..00000000000 --- a/adapters/s2s_pulsepoint/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package s2s_pulsepoint - -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", 61, temp, adapters.SyncTypeIframe) -} diff --git a/adapters/s2s_pulsepoint/usersync_test.go b/adapters/s2s_pulsepoint/usersync_test.go deleted file mode 100644 index 4316da1615b..00000000000 --- a/adapters/s2s_pulsepoint/usersync_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package s2s_pulsepoint - -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{ - Value: "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.EqualValues(t, 61, syncer.GDPRVendorID()) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/config/config.go b/config/config.go index 716eabd9c18..6b06c400ddf 100644 --- a/config/config.go +++ b/config/config.go @@ -688,7 +688,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.gamoshi.endpoint", "https://rtb.gamoshi.io") v.SetDefault("adapters.grid.endpoint", "http://grid.bidswitch.net/sp_bid?sp=prebid") v.SetDefault("adapters.gumgum.endpoint", "https://g2.gumgum.com/providers/prbds2s/bid") - v.SetDefault("adapters.s2s_gumgum.endpoint", "https://g2.gumgum.com/zones/8ylgv2wd/bid") + v.SetDefault("adapters.gumgum_ortb.endpoint", "https://g2.gumgum.com/zones/8ylgv2wd/bid") v.SetDefault("adapters.improvedigital.endpoint", "http://ad.360yield.com/pbs") v.SetDefault("adapters.ix.endpoint", "http://appnexus-us-east.lb.indexww.com/transbidder?p=184932") v.SetDefault("adapters.kubient.endpoint", "http://kbntx.ch/prebid") @@ -700,7 +700,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.pubmatic.endpoint", "https://hbopenbid.pubmatic.com/translator?source=prebid-server") v.SetDefault("adapters.pubnative.endpoint", "http://dsp.pubnative.net/bid/v1/request") v.SetDefault("adapters.pulsepoint.endpoint", "http://bid.contextweb.com/header/s/ortb/prebid-s2s") - v.SetDefault("adapters.s2s_pulsepoint.endpoint", "http://rts.lga.contextweb.com/rt-seller/bid/openrtb/playwire") + v.SetDefault("adapters.pulsepoint_ortb.endpoint", "http://rts.lga.contextweb.com/rt-seller/bid/openrtb/playwire") v.SetDefault("adapters.rhythmone.endpoint", "http://tag.1rx.io/rmp") v.SetDefault("adapters.rtbhouse.endpoint", "http://prebidserver-s2s-ams.creativecdn.com/bidder/prebidserver/bids") v.SetDefault("adapters.rubicon.endpoint", "http://exapi-us-east.rubiconproject.com/a/api/exchange.json") diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index e181506fc06..787814bde41 100644 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -61,8 +61,7 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" - "prebid-server/adapters/s2s_pulsepoint" - "prebid-server/adapters/s2s_gumgum" + "prebid-server/adapters/playwire_ortb" ) // The newAdapterMap function is segregated to its own file to make it a simple and clean location for each Adapter @@ -127,8 +126,8 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderVisx: visx.NewVisxBidder(cfg.Adapters[string(openrtb_ext.BidderVisx)].Endpoint), openrtb_ext.BidderVrtcal: vrtcal.NewVrtcalBidder(cfg.Adapters[string(openrtb_ext.BidderVrtcal)].Endpoint), openrtb_ext.BidderYieldmo: yieldmo.NewYieldmoBidder(cfg.Adapters[string(openrtb_ext.BidderYieldmo)].Endpoint), - openrtb_ext.BidderS2SPulsepoint: s2s_pulsepoint.NewGumGumBidder(cfg.Adapters[string(openrtb_ext.BidderS2SPulsepoint)].Endpoint), - openrtb_ext.BidderS2SGumGum: s2s_gumgum.NewGumGumBidder(cfg.Adapters[string(openrtb_ext.BidderS2SGumGum)].Endpoint), + openrtb_ext.BidderPulsepointOrtb: playwire_ortb.NewOrtbBidder(cfg.Adapters[string(openrtb_ext.BidderPulsepointOrtb)].Endpoint), + openrtb_ext.BidderGumGumOrtb: playwire_ortb.NewOrtbBidder(cfg.Adapters[string(openrtb_ext.BidderGumGumOrtb)].Endpoint), } legacyBidders := map[openrtb_ext.BidderName]adapters.Adapter{ diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index cfc161bba31..96092407d7c 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -71,8 +71,8 @@ const ( BidderVisx BidderName = "visx" BidderVrtcal BidderName = "vrtcal" BidderYieldmo BidderName = "yieldmo" - BidderS2SPulsepoint BidderName = "s2s_pulsepoint" - BidderS2SGumGum BidderName = "s2s_gumgum" + BidderPulsepointOrtb BidderName = "pulsepoint_ortb" + BidderGumGumOrtb BidderName = "gumgum_ortb" ) // BidderMap stores all the valid OpenRTB 2.x Bidders in the project. This map *must not* be mutated. @@ -128,8 +128,8 @@ var BidderMap = map[string]BidderName{ "visx": BidderVisx, "vrtcal": BidderVrtcal, "yieldmo": BidderYieldmo, - "s2s_gumgum": BidderS2SGumGum, - "s2s_pulsepoint": BidderS2SPulsepoint, + "gumgum_ortb": BidderGumGumOrtb, + "pulsepoint_ortb": BidderPulsepointOrtb, } // BidderList returns the values of the BidderMap diff --git a/static/bidder-info/s2s_gumgum.yaml b/static/bidder-info/gumgum_ortb.yaml similarity index 100% rename from static/bidder-info/s2s_gumgum.yaml rename to static/bidder-info/gumgum_ortb.yaml diff --git a/static/bidder-info/s2s_pulsepoint.yaml b/static/bidder-info/pulsepoint_ortb.yaml similarity index 100% rename from static/bidder-info/s2s_pulsepoint.yaml rename to static/bidder-info/pulsepoint_ortb.yaml diff --git a/static/bidder-params/s2s_gumgum.json b/static/bidder-params/gumgum_ortb.json similarity index 100% rename from static/bidder-params/s2s_gumgum.json rename to static/bidder-params/gumgum_ortb.json diff --git a/static/bidder-params/s2s_pulsepoint.json b/static/bidder-params/pulsepoint_ortb.json similarity index 100% rename from static/bidder-params/s2s_pulsepoint.json rename to static/bidder-params/pulsepoint_ortb.json From 564694f1c8629381d134de586fe47d4d20008403 Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Fri, 2 Oct 2020 13:23:43 -0400 Subject: [PATCH 026/125] match spacing, alphabetic order, naming conventions --- adapters/playwire_ortb/params_test.go | 68 ++--- adapters/playwire_ortb/playwire_ortb.go | 296 ++++++++++---------- openrtb_ext/bidders.go | 356 ++++++++++++------------ 3 files changed, 360 insertions(+), 360 deletions(-) diff --git a/adapters/playwire_ortb/params_test.go b/adapters/playwire_ortb/params_test.go index 47ff5cc252d..4adfafbdc7f 100644 --- a/adapters/playwire_ortb/params_test.go +++ b/adapters/playwire_ortb/params_test.go @@ -1,52 +1,52 @@ package playwire_ortb import ( - "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" - "testing" + "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) - } + 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.BidderGumGum, json.RawMessage(validParam)); err != nil { - t.Errorf("Schema rejected gumgum params: %s", validParam) - } - } + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderGumGum, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected gumgum 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) - } + 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) - } - } + 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{ - `{"zone":"dc9d6be1"}`, + `{"zone":"dc9d6be1"}`, } var invalidParams = []string{ - `null`, - `nil`, - ``, - `{}`, - `[]`, - `true`, - `2`, - `{"zone":12345678}`, - `{"zone":""}`, - `{"placementId": 1}`, - `{"zone": true}`, - `{"placementId": 1, "zone":"1234567"}`, + `null`, + `nil`, + ``, + `{}`, + `[]`, + `true`, + `2`, + `{"zone":12345678}`, + `{"zone":""}`, + `{"placementId": 1}`, + `{"zone": true}`, + `{"placementId": 1, "zone":"1234567"}`, } diff --git a/adapters/playwire_ortb/playwire_ortb.go b/adapters/playwire_ortb/playwire_ortb.go index 355e7be3d1d..befb679cc34 100644 --- a/adapters/playwire_ortb/playwire_ortb.go +++ b/adapters/playwire_ortb/playwire_ortb.go @@ -1,172 +1,172 @@ package playwire_ortb import ( - "encoding/json" - "fmt" - "github.com/mxmCherry/openrtb" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - "net/http" - "strconv" - "strings" + "encoding/json" + "fmt" + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" + "net/http" + "strconv" + "strings" ) -// GumGumAdapter implements Bidder interface. -type GumGumAdapter struct { - URI string +// PlaywireORTBAdapter implements Bidder interface. +type PlaywireORTBAdapter struct { + URI string } // MakeRequests makes the HTTP requests which should be made to fetch bids. -func (g *GumGumAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - var validImps []openrtb.Imp - var trackingId string - - numRequests := len(request.Imp) - errs := make([]error, 0, numRequests) - - for i := 0; i < numRequests; i++ { - imp := request.Imp[i] - zone, err := preprocess(&imp) - if err != nil { - errs = append(errs, err) - } else if request.Imp[i].Banner != nil { - bannerCopy := *request.Imp[i].Banner - if bannerCopy.W == nil && bannerCopy.H == nil && len(bannerCopy.Format) > 0 { - format := bannerCopy.Format[0] - bannerCopy.W = &(format.W) - bannerCopy.H = &(format.H) - } - request.Imp[i].Banner = &bannerCopy - validImps = append(validImps, request.Imp[i]) - trackingId = zone - } else if request.Imp[i].Video != nil { - err := validateVideoParams(request.Imp[i].Video) - if err != nil { - errs = append(errs, err) - } else { - validImps = append(validImps, request.Imp[i]) - trackingId = zone - } - } - } - - if len(validImps) == 0 { - return nil, errs - } - - request.Imp = validImps - - if request.Site != nil { - siteCopy := *request.Site - siteCopy.ID = trackingId - request.Site = &siteCopy - } - - reqJSON, err := json.Marshal(request) - if err != nil { - errs = append(errs, err) - return nil, errs - } - - headers := http.Header{} - headers.Add("Content-Type", "application/json;charset=utf-8") - headers.Add("Accept", "application/json") - return []*adapters.RequestData{{ - Method: "POST", - Uri: g.URI, - Body: reqJSON, - Headers: headers, - }}, errs +func (g *PlaywireORTBAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var validImps []openrtb.Imp + var trackingId string + + numRequests := len(request.Imp) + errs := make([]error, 0, numRequests) + + for i := 0; i < numRequests; i++ { + imp := request.Imp[i] + zone, err := preprocess(&imp) + if err != nil { + errs = append(errs, err) + } else if request.Imp[i].Banner != nil { + bannerCopy := *request.Imp[i].Banner + if bannerCopy.W == nil && bannerCopy.H == nil && len(bannerCopy.Format) > 0 { + format := bannerCopy.Format[0] + bannerCopy.W = &(format.W) + bannerCopy.H = &(format.H) + } + request.Imp[i].Banner = &bannerCopy + validImps = append(validImps, request.Imp[i]) + trackingId = zone + } else if request.Imp[i].Video != nil { + err := validateVideoParams(request.Imp[i].Video) + if err != nil { + errs = append(errs, err) + } else { + validImps = append(validImps, request.Imp[i]) + trackingId = zone + } + } + } + + if len(validImps) == 0 { + return nil, errs + } + + request.Imp = validImps + + if request.Site != nil { + siteCopy := *request.Site + siteCopy.ID = trackingId + request.Site = &siteCopy + } + + reqJSON, err := json.Marshal(request) + if err != nil { + errs = append(errs, err) + return nil, errs + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + return []*adapters.RequestData{{ + Method: "POST", + Uri: g.URI, + Body: reqJSON, + Headers: headers, + }}, errs } // MakeBids unpacks the server's response into Bids. -func (g *GumGumAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { - if response.StatusCode == http.StatusNoContent { - return nil, nil - } - - if response.StatusCode == http.StatusBadRequest { - return nil, []error{&errortypes.BadInput{ - Message: fmt.Sprintf("Bad user input: HTTP status %d", response.StatusCode), - }} - } - - if response.StatusCode != http.StatusOK { - return nil, []error{&errortypes.BadServerResponse{ - Message: fmt.Sprintf("Bad server response: HTTP status %d", response.StatusCode), - }} - } - var bidResp openrtb.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { - return nil, []error{&errortypes.BadServerResponse{ - Message: fmt.Sprintf("Bad server response: %d. ", err), - }} - } - - var errs []error - bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) - - for _, sb := range bidResp.SeatBid { - for i := range sb.Bid { - mediaType := getMediaTypeForImpID(sb.Bid[i].ImpID, internalRequest.Imp) - if mediaType == openrtb_ext.BidTypeVideo { - price := strconv.FormatFloat(sb.Bid[i].Price, 'f', -1, 64) - sb.Bid[i].AdM = strings.Replace(sb.Bid[i].AdM, "${AUCTION_PRICE}", price, -1) - } - - bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ - Bid: &sb.Bid[i], - BidType: mediaType, - }) - } - } - - return bidResponse, errs +func (g *PlaywireORTBAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + + if response.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Bad user input: HTTP status %d", response.StatusCode), + }} + } + + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Bad server response: HTTP status %d", response.StatusCode), + }} + } + var bidResp openrtb.BidResponse + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Bad server response: %d. ", err), + }} + } + + var errs []error + bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) + + for _, sb := range bidResp.SeatBid { + for i := range sb.Bid { + mediaType := getMediaTypeForImpID(sb.Bid[i].ImpID, internalRequest.Imp) + if mediaType == openrtb_ext.BidTypeVideo { + price := strconv.FormatFloat(sb.Bid[i].Price, 'f', -1, 64) + sb.Bid[i].AdM = strings.Replace(sb.Bid[i].AdM, "${AUCTION_PRICE}", price, -1) + } + + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &sb.Bid[i], + BidType: mediaType, + }) + } + } + + return bidResponse, errs } func preprocess(imp *openrtb.Imp) (string, error) { - var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { - err = &errortypes.BadInput{ - Message: err.Error(), - } - return "", err - } - - var gumgumExt openrtb_ext.ExtImpGumGum - if err := json.Unmarshal(bidderExt.Bidder, &gumgumExt); err != nil { - err = &errortypes.BadInput{ - Message: err.Error(), - } - return "", err - } - - zone := gumgumExt.Zone - return zone, nil + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + err = &errortypes.BadInput{ + Message: err.Error(), + } + return "", err + } + + var gumgumExt openrtb_ext.ExtImpGumGum + if err := json.Unmarshal(bidderExt.Bidder, &gumgumExt); err != nil { + err = &errortypes.BadInput{ + Message: err.Error(), + } + return "", err + } + + zone := gumgumExt.Zone + return zone, nil } func getMediaTypeForImpID(impID string, imps []openrtb.Imp) openrtb_ext.BidType { - for _, imp := range imps { - if imp.ID == impID && imp.Banner != nil { - return openrtb_ext.BidTypeBanner - } - } - return openrtb_ext.BidTypeVideo + for _, imp := range imps { + if imp.ID == impID && imp.Banner != nil { + return openrtb_ext.BidTypeBanner + } + } + return openrtb_ext.BidTypeVideo } func validateVideoParams(video *openrtb.Video) (err error) { - // if video.W == 0 || video.H == 0 || video.MinDuration == 0 || video.MaxDuration == 0 || video.Placement == 0 || video.Linearity == 0 { - // return &errortypes.BadInput{ - // Message: "Invalid or missing video field(s)", - // } - // } - return nil + // if video.W == 0 || video.H == 0 || video.MinDuration == 0 || video.MaxDuration == 0 || video.Placement == 0 || video.Linearity == 0 { + // return &errortypes.BadInput{ + // Message: "Invalid or missing video field(s)", + // } + // } + return nil } // NewOrtbBidder configures bidder endpoint. -func NewOrtbBidder(endpoint string) *GumGumAdapter { - return &GumGumAdapter{ - URI: endpoint, - } +func NewOrtbBidder(endpoint string) *PlaywireORTBAdapter { + return &PlaywireORTBAdapter{ + URI: endpoint, + } } \ No newline at end of file diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 96092407d7c..6759d61ebc2 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -1,15 +1,15 @@ package openrtb_ext import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "path/filepath" - "strings" - - "github.com/xeipuuv/gojsonschema" + "bytes" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "path/filepath" + "strings" + + "github.com/xeipuuv/gojsonschema" ) const schemaDirectory = "static/bidder-params" @@ -20,208 +20,208 @@ type BidderName string // These names _must_ coincide with the bidder code in Prebid.js, if an adapter also exists in that project. // Please keep these (and the BidderMap) alphabetized to minimize merge conflicts among adapter submissions. const ( - Bidder33Across BidderName = "33across" - BidderAdform BidderName = "adform" - BidderAdkernel BidderName = "adkernel" - BidderAdkernelAdn BidderName = "adkernelAdn" - BidderAdpone BidderName = "adpone" - BidderAdtelligent BidderName = "adtelligent" - BidderAdvangelists BidderName = "advangelists" - BidderApplogy BidderName = "applogy" - BidderAppnexus BidderName = "appnexus" - BidderBeachfront BidderName = "beachfront" - BidderBrightroll BidderName = "brightroll" - BidderConsumable BidderName = "consumable" - BidderConversant BidderName = "conversant" - BidderCpmstar BidderName = "cpmstar" - BidderDatablocks BidderName = "datablocks" - BidderEmxDigital BidderName = "emx_digital" - BidderEngageBDR BidderName = "engagebdr" - BidderEPlanning BidderName = "eplanning" - BidderFacebook BidderName = "audienceNetwork" - BidderGamma BidderName = "gamma" - BidderGamoshi BidderName = "gamoshi" - BidderGrid BidderName = "grid" - BidderGumGum BidderName = "gumgum" - BidderImprovedigital BidderName = "improvedigital" - BidderIx BidderName = "ix" - BidderKubient BidderName = "kubient" - BidderLifestreet BidderName = "lifestreet" - BidderLockerDome BidderName = "lockerdome" - BidderMarsmedia BidderName = "marsmedia" - BidderMgid BidderName = "mgid" - BidderOpenx BidderName = "openx" - BidderPubmatic BidderName = "pubmatic" - BidderPubnative BidderName = "pubnative" - BidderPulsepoint BidderName = "pulsepoint" - BidderRhythmone BidderName = "rhythmone" - BidderRTBHouse BidderName = "rtbhouse" - BidderRubicon BidderName = "rubicon" - BidderSharethrough BidderName = "sharethrough" - BidderSmartRTB BidderName = "smartrtb" - BidderSomoaudience BidderName = "somoaudience" - BidderSonobi BidderName = "sonobi" - BidderSovrn BidderName = "sovrn" - BidderSynacormedia BidderName = "synacormedia" - BidderTappx BidderName = "tappx" - BidderTriplelift BidderName = "triplelift" - BidderTripleliftNative BidderName = "triplelift_native" - BidderUnruly BidderName = "unruly" - BidderVerizonMedia BidderName = "verizonmedia" - BidderVisx BidderName = "visx" - BidderVrtcal BidderName = "vrtcal" - BidderYieldmo BidderName = "yieldmo" - BidderPulsepointOrtb BidderName = "pulsepoint_ortb" - BidderGumGumOrtb BidderName = "gumgum_ortb" + Bidder33Across BidderName = "33across" + BidderAdform BidderName = "adform" + BidderAdkernel BidderName = "adkernel" + BidderAdkernelAdn BidderName = "adkernelAdn" + BidderAdpone BidderName = "adpone" + BidderAdtelligent BidderName = "adtelligent" + BidderAdvangelists BidderName = "advangelists" + BidderApplogy BidderName = "applogy" + BidderAppnexus BidderName = "appnexus" + BidderBeachfront BidderName = "beachfront" + BidderBrightroll BidderName = "brightroll" + BidderConsumable BidderName = "consumable" + BidderConversant BidderName = "conversant" + BidderCpmstar BidderName = "cpmstar" + BidderDatablocks BidderName = "datablocks" + BidderEmxDigital BidderName = "emx_digital" + BidderEngageBDR BidderName = "engagebdr" + BidderEPlanning BidderName = "eplanning" + BidderFacebook BidderName = "audienceNetwork" + BidderGamma BidderName = "gamma" + BidderGamoshi BidderName = "gamoshi" + BidderGrid BidderName = "grid" + BidderGumGum BidderName = "gumgum" + BidderGumGumOrtb BidderName = "gumgum_ortb" + BidderImprovedigital BidderName = "improvedigital" + BidderIx BidderName = "ix" + BidderKubient BidderName = "kubient" + BidderLifestreet BidderName = "lifestreet" + BidderLockerDome BidderName = "lockerdome" + BidderMarsmedia BidderName = "marsmedia" + BidderMgid BidderName = "mgid" + BidderOpenx BidderName = "openx" + BidderPubmatic BidderName = "pubmatic" + BidderPubnative BidderName = "pubnative" + BidderPulsepoint BidderName = "pulsepoint" + BidderPulsepointOrtb BidderName = "pulsepoint_ortb" + BidderRhythmone BidderName = "rhythmone" + BidderRTBHouse BidderName = "rtbhouse" + BidderRubicon BidderName = "rubicon" + BidderSharethrough BidderName = "sharethrough" + BidderSmartRTB BidderName = "smartrtb" + BidderSomoaudience BidderName = "somoaudience" + BidderSonobi BidderName = "sonobi" + BidderSovrn BidderName = "sovrn" + BidderSynacormedia BidderName = "synacormedia" + BidderTappx BidderName = "tappx" + BidderTriplelift BidderName = "triplelift" + BidderTripleliftNative BidderName = "triplelift_native" + BidderUnruly BidderName = "unruly" + BidderVerizonMedia BidderName = "verizonmedia" + BidderVisx BidderName = "visx" + BidderVrtcal BidderName = "vrtcal" + BidderYieldmo BidderName = "yieldmo" ) // BidderMap stores all the valid OpenRTB 2.x Bidders in the project. This map *must not* be mutated. var BidderMap = map[string]BidderName{ - "33across": Bidder33Across, - "adform": BidderAdform, - "adkernel": BidderAdkernel, - "adkernelAdn": BidderAdkernelAdn, - "adpone": BidderAdpone, - "adtelligent": BidderAdtelligent, - "advangelists": BidderAdvangelists, - "applogy": BidderApplogy, - "appnexus": BidderAppnexus, - "beachfront": BidderBeachfront, - "brightroll": BidderBrightroll, - "consumable": BidderConsumable, - "conversant": BidderConversant, - "cpmstar": BidderCpmstar, - "datablocks": BidderDatablocks, - "emx_digital": BidderEmxDigital, - "engagebdr": BidderEngageBDR, - "eplanning": BidderEPlanning, - "audienceNetwork": BidderFacebook, - "gamma": BidderGamma, - "gamoshi": BidderGamoshi, - "grid": BidderGrid, - "gumgum": BidderGumGum, - "improvedigital": BidderImprovedigital, - "ix": BidderIx, - "kubient": BidderKubient, - "lifestreet": BidderLifestreet, - "lockerdome": BidderLockerDome, - "marsmedia": BidderMarsmedia, - "mgid": BidderMgid, - "openx": BidderOpenx, - "pubmatic": BidderPubmatic, - "pubnative": BidderPubnative, - "pulsepoint": BidderPulsepoint, - "rhythmone": BidderRhythmone, - "rtbhouse": BidderRTBHouse, - "rubicon": BidderRubicon, - "sharethrough": BidderSharethrough, - "smartrtb": BidderSmartRTB, - "somoaudience": BidderSomoaudience, - "sonobi": BidderSonobi, - "sovrn": BidderSovrn, - "synacormedia": BidderSynacormedia, - "tappx": BidderTappx, - "triplelift": BidderTriplelift, - "triplelift_native": BidderTripleliftNative, - "unruly": BidderUnruly, - "verizonmedia": BidderVerizonMedia, - "visx": BidderVisx, - "vrtcal": BidderVrtcal, - "yieldmo": BidderYieldmo, - "gumgum_ortb": BidderGumGumOrtb, - "pulsepoint_ortb": BidderPulsepointOrtb, + "33across": Bidder33Across, + "adform": BidderAdform, + "adkernel": BidderAdkernel, + "adkernelAdn": BidderAdkernelAdn, + "adpone": BidderAdpone, + "adtelligent": BidderAdtelligent, + "advangelists": BidderAdvangelists, + "applogy": BidderApplogy, + "appnexus": BidderAppnexus, + "beachfront": BidderBeachfront, + "brightroll": BidderBrightroll, + "consumable": BidderConsumable, + "conversant": BidderConversant, + "cpmstar": BidderCpmstar, + "datablocks": BidderDatablocks, + "emx_digital": BidderEmxDigital, + "engagebdr": BidderEngageBDR, + "eplanning": BidderEPlanning, + "audienceNetwork": BidderFacebook, + "gamma": BidderGamma, + "gamoshi": BidderGamoshi, + "grid": BidderGrid, + "gumgum": BidderGumGum, + "gumgum_ortb": BidderGumGumOrtb, + "improvedigital": BidderImprovedigital, + "ix": BidderIx, + "kubient": BidderKubient, + "lifestreet": BidderLifestreet, + "lockerdome": BidderLockerDome, + "marsmedia": BidderMarsmedia, + "mgid": BidderMgid, + "openx": BidderOpenx, + "pubmatic": BidderPubmatic, + "pubnative": BidderPubnative, + "pulsepoint": BidderPulsepoint, + "pulsepoint_ortb": BidderPulsepointOrtb, + "rhythmone": BidderRhythmone, + "rtbhouse": BidderRTBHouse, + "rubicon": BidderRubicon, + "sharethrough": BidderSharethrough, + "smartrtb": BidderSmartRTB, + "somoaudience": BidderSomoaudience, + "sonobi": BidderSonobi, + "sovrn": BidderSovrn, + "synacormedia": BidderSynacormedia, + "tappx": BidderTappx, + "triplelift": BidderTriplelift, + "triplelift_native": BidderTripleliftNative, + "unruly": BidderUnruly, + "verizonmedia": BidderVerizonMedia, + "visx": BidderVisx, + "vrtcal": BidderVrtcal, + "yieldmo": BidderYieldmo, } // BidderList returns the values of the BidderMap func BidderList() []BidderName { - bidders := make([]BidderName, 0, len(BidderMap)) - for _, value := range BidderMap { - bidders = append(bidders, value) - } - return bidders + bidders := make([]BidderName, 0, len(BidderMap)) + for _, value := range BidderMap { + bidders = append(bidders, value) + } + return bidders } func (name BidderName) MarshalJSON() ([]byte, error) { - return []byte(name), nil + return []byte(name), nil } func (name *BidderName) String() string { - if name == nil { - return "" - } + if name == nil { + return "" + } - return string(*name) + return string(*name) } // The BidderParamValidator is used to enforce bidrequest.imp[i].ext.{anyBidder} values. // // This is treated differently from the other types because we rely on JSON-schemas to validate bidder params. type BidderParamValidator interface { - Validate(name BidderName, ext json.RawMessage) error - // Schema returns the JSON schema used to perform validation. - Schema(name BidderName) string + Validate(name BidderName, ext json.RawMessage) error + // Schema returns the JSON schema used to perform validation. + Schema(name BidderName) string } // NewBidderParamsValidator makes a BidderParamValidator, assuming all the necessary files exist in the filesystem. // This will error if, for example, a Bidder gets added but no JSON schema is written for them. func NewBidderParamsValidator(schemaDirectory string) (BidderParamValidator, error) { - fileInfos, err := ioutil.ReadDir(schemaDirectory) - if err != nil { - return nil, fmt.Errorf("Failed to read JSON schemas from directory %s. %v", schemaDirectory, err) - } - - schemaContents := make(map[BidderName]string, 50) - schemas := make(map[BidderName]*gojsonschema.Schema, 50) - for _, fileInfo := range fileInfos { - bidderName := strings.TrimSuffix(fileInfo.Name(), ".json") - if _, isValid := BidderMap[bidderName]; !isValid { - return nil, fmt.Errorf("File %s/%s does not match a valid BidderName.", schemaDirectory, fileInfo.Name()) - } - toOpen, err := filepath.Abs(filepath.Join(schemaDirectory, fileInfo.Name())) - if err != nil { - return nil, fmt.Errorf("Failed to get an absolute representation of the path: %s, %v", toOpen, err) - } - schemaLoader := gojsonschema.NewReferenceLoader("file:///" + filepath.ToSlash(toOpen)) - loadedSchema, err := gojsonschema.NewSchema(schemaLoader) - if err != nil { - return nil, fmt.Errorf("Failed to load json schema at %s: %v", toOpen, err) - } - - fileBytes, err := ioutil.ReadFile(fmt.Sprintf("%s/%s", schemaDirectory, fileInfo.Name())) - if err != nil { - return nil, fmt.Errorf("Failed to read file %s/%s: %v", schemaDirectory, fileInfo.Name(), err) - } - - schemas[BidderName(bidderName)] = loadedSchema - schemaContents[BidderName(bidderName)] = string(fileBytes) - } - - return &bidderParamValidator{ - schemaContents: schemaContents, - parsedSchemas: schemas, - }, nil + fileInfos, err := ioutil.ReadDir(schemaDirectory) + if err != nil { + return nil, fmt.Errorf("Failed to read JSON schemas from directory %s. %v", schemaDirectory, err) + } + + schemaContents := make(map[BidderName]string, 50) + schemas := make(map[BidderName]*gojsonschema.Schema, 50) + for _, fileInfo := range fileInfos { + bidderName := strings.TrimSuffix(fileInfo.Name(), ".json") + if _, isValid := BidderMap[bidderName]; !isValid { + return nil, fmt.Errorf("File %s/%s does not match a valid BidderName.", schemaDirectory, fileInfo.Name()) + } + toOpen, err := filepath.Abs(filepath.Join(schemaDirectory, fileInfo.Name())) + if err != nil { + return nil, fmt.Errorf("Failed to get an absolute representation of the path: %s, %v", toOpen, err) + } + schemaLoader := gojsonschema.NewReferenceLoader("file:///" + filepath.ToSlash(toOpen)) + loadedSchema, err := gojsonschema.NewSchema(schemaLoader) + if err != nil { + return nil, fmt.Errorf("Failed to load json schema at %s: %v", toOpen, err) + } + + fileBytes, err := ioutil.ReadFile(fmt.Sprintf("%s/%s", schemaDirectory, fileInfo.Name())) + if err != nil { + return nil, fmt.Errorf("Failed to read file %s/%s: %v", schemaDirectory, fileInfo.Name(), err) + } + + schemas[BidderName(bidderName)] = loadedSchema + schemaContents[BidderName(bidderName)] = string(fileBytes) + } + + return &bidderParamValidator{ + schemaContents: schemaContents, + parsedSchemas: schemas, + }, nil } type bidderParamValidator struct { - schemaContents map[BidderName]string - parsedSchemas map[BidderName]*gojsonschema.Schema + schemaContents map[BidderName]string + parsedSchemas map[BidderName]*gojsonschema.Schema } func (validator *bidderParamValidator) Validate(name BidderName, ext json.RawMessage) error { - result, err := validator.parsedSchemas[name].Validate(gojsonschema.NewBytesLoader(ext)) - if err != nil { - return err - } - if !result.Valid() { - errBuilder := bytes.NewBuffer(make([]byte, 0, 300)) - for _, err := range result.Errors() { - errBuilder.WriteString(err.String()) - } - return errors.New(errBuilder.String()) - } - return nil + result, err := validator.parsedSchemas[name].Validate(gojsonschema.NewBytesLoader(ext)) + if err != nil { + return err + } + if !result.Valid() { + errBuilder := bytes.NewBuffer(make([]byte, 0, 300)) + for _, err := range result.Errors() { + errBuilder.WriteString(err.String()) + } + return errors.New(errBuilder.String()) + } + return nil } func (validator *bidderParamValidator) Schema(name BidderName) string { - return validator.schemaContents[name] + return validator.schemaContents[name] } From 844545f9b9dd30f7c0d26eeba17e9effd0b63562 Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Fri, 2 Oct 2020 13:29:14 -0400 Subject: [PATCH 027/125] another formatting attempt --- openrtb_ext/bidders.go | 294 ++++++++++++++++++++--------------------- 1 file changed, 147 insertions(+), 147 deletions(-) diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 6759d61ebc2..0d71b1c5f59 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -1,15 +1,15 @@ package openrtb_ext import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "path/filepath" - "strings" - - "github.com/xeipuuv/gojsonschema" + "bytes" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "path/filepath" + "strings" + + "github.com/xeipuuv/gojsonschema" ) const schemaDirectory = "static/bidder-params" @@ -20,123 +20,123 @@ type BidderName string // These names _must_ coincide with the bidder code in Prebid.js, if an adapter also exists in that project. // Please keep these (and the BidderMap) alphabetized to minimize merge conflicts among adapter submissions. const ( - Bidder33Across BidderName = "33across" - BidderAdform BidderName = "adform" - BidderAdkernel BidderName = "adkernel" - BidderAdkernelAdn BidderName = "adkernelAdn" - BidderAdpone BidderName = "adpone" - BidderAdtelligent BidderName = "adtelligent" - BidderAdvangelists BidderName = "advangelists" - BidderApplogy BidderName = "applogy" - BidderAppnexus BidderName = "appnexus" - BidderBeachfront BidderName = "beachfront" - BidderBrightroll BidderName = "brightroll" - BidderConsumable BidderName = "consumable" - BidderConversant BidderName = "conversant" - BidderCpmstar BidderName = "cpmstar" - BidderDatablocks BidderName = "datablocks" - BidderEmxDigital BidderName = "emx_digital" - BidderEngageBDR BidderName = "engagebdr" - BidderEPlanning BidderName = "eplanning" - BidderFacebook BidderName = "audienceNetwork" - BidderGamma BidderName = "gamma" - BidderGamoshi BidderName = "gamoshi" - BidderGrid BidderName = "grid" - BidderGumGum BidderName = "gumgum" - BidderGumGumOrtb BidderName = "gumgum_ortb" - BidderImprovedigital BidderName = "improvedigital" - BidderIx BidderName = "ix" - BidderKubient BidderName = "kubient" - BidderLifestreet BidderName = "lifestreet" - BidderLockerDome BidderName = "lockerdome" - BidderMarsmedia BidderName = "marsmedia" - BidderMgid BidderName = "mgid" - BidderOpenx BidderName = "openx" - BidderPubmatic BidderName = "pubmatic" - BidderPubnative BidderName = "pubnative" - BidderPulsepoint BidderName = "pulsepoint" - BidderPulsepointOrtb BidderName = "pulsepoint_ortb" - BidderRhythmone BidderName = "rhythmone" - BidderRTBHouse BidderName = "rtbhouse" - BidderRubicon BidderName = "rubicon" - BidderSharethrough BidderName = "sharethrough" - BidderSmartRTB BidderName = "smartrtb" - BidderSomoaudience BidderName = "somoaudience" - BidderSonobi BidderName = "sonobi" - BidderSovrn BidderName = "sovrn" - BidderSynacormedia BidderName = "synacormedia" - BidderTappx BidderName = "tappx" - BidderTriplelift BidderName = "triplelift" - BidderTripleliftNative BidderName = "triplelift_native" - BidderUnruly BidderName = "unruly" - BidderVerizonMedia BidderName = "verizonmedia" - BidderVisx BidderName = "visx" - BidderVrtcal BidderName = "vrtcal" - BidderYieldmo BidderName = "yieldmo" + Bidder33Across BidderName = "33across" + BidderAdform BidderName = "adform" + BidderAdkernel BidderName = "adkernel" + BidderAdkernelAdn BidderName = "adkernelAdn" + BidderAdpone BidderName = "adpone" + BidderAdtelligent BidderName = "adtelligent" + BidderAdvangelists BidderName = "advangelists" + BidderApplogy BidderName = "applogy" + BidderAppnexus BidderName = "appnexus" + BidderBeachfront BidderName = "beachfront" + BidderBrightroll BidderName = "brightroll" + BidderConsumable BidderName = "consumable" + BidderConversant BidderName = "conversant" + BidderCpmstar BidderName = "cpmstar" + BidderDatablocks BidderName = "datablocks" + BidderEmxDigital BidderName = "emx_digital" + BidderEngageBDR BidderName = "engagebdr" + BidderEPlanning BidderName = "eplanning" + BidderFacebook BidderName = "audienceNetwork" + BidderGamma BidderName = "gamma" + BidderGamoshi BidderName = "gamoshi" + BidderGrid BidderName = "grid" + BidderGumGum BidderName = "gumgum" + BidderGumGumOrtb BidderName = "gumgum_ortb" + BidderImprovedigital BidderName = "improvedigital" + BidderIx BidderName = "ix" + BidderKubient BidderName = "kubient" + BidderLifestreet BidderName = "lifestreet" + BidderLockerDome BidderName = "lockerdome" + BidderMarsmedia BidderName = "marsmedia" + BidderMgid BidderName = "mgid" + BidderOpenx BidderName = "openx" + BidderPubmatic BidderName = "pubmatic" + BidderPubnative BidderName = "pubnative" + BidderPulsepoint BidderName = "pulsepoint" + BidderPulsepointOrtb BidderName = "pulsepoint_ortb" + BidderRhythmone BidderName = "rhythmone" + BidderRTBHouse BidderName = "rtbhouse" + BidderRubicon BidderName = "rubicon" + BidderSharethrough BidderName = "sharethrough" + BidderSmartRTB BidderName = "smartrtb" + BidderSomoaudience BidderName = "somoaudience" + BidderSonobi BidderName = "sonobi" + BidderSovrn BidderName = "sovrn" + BidderSynacormedia BidderName = "synacormedia" + BidderTappx BidderName = "tappx" + BidderTriplelift BidderName = "triplelift" + BidderTripleliftNative BidderName = "triplelift_native" + BidderUnruly BidderName = "unruly" + BidderVerizonMedia BidderName = "verizonmedia" + BidderVisx BidderName = "visx" + BidderVrtcal BidderName = "vrtcal" + BidderYieldmo BidderName = "yieldmo" ) // BidderMap stores all the valid OpenRTB 2.x Bidders in the project. This map *must not* be mutated. var BidderMap = map[string]BidderName{ - "33across": Bidder33Across, - "adform": BidderAdform, - "adkernel": BidderAdkernel, - "adkernelAdn": BidderAdkernelAdn, - "adpone": BidderAdpone, - "adtelligent": BidderAdtelligent, - "advangelists": BidderAdvangelists, - "applogy": BidderApplogy, - "appnexus": BidderAppnexus, - "beachfront": BidderBeachfront, - "brightroll": BidderBrightroll, - "consumable": BidderConsumable, - "conversant": BidderConversant, - "cpmstar": BidderCpmstar, - "datablocks": BidderDatablocks, - "emx_digital": BidderEmxDigital, - "engagebdr": BidderEngageBDR, - "eplanning": BidderEPlanning, - "audienceNetwork": BidderFacebook, - "gamma": BidderGamma, - "gamoshi": BidderGamoshi, - "grid": BidderGrid, - "gumgum": BidderGumGum, - "gumgum_ortb": BidderGumGumOrtb, - "improvedigital": BidderImprovedigital, - "ix": BidderIx, - "kubient": BidderKubient, - "lifestreet": BidderLifestreet, - "lockerdome": BidderLockerDome, - "marsmedia": BidderMarsmedia, - "mgid": BidderMgid, - "openx": BidderOpenx, - "pubmatic": BidderPubmatic, - "pubnative": BidderPubnative, - "pulsepoint": BidderPulsepoint, - "pulsepoint_ortb": BidderPulsepointOrtb, - "rhythmone": BidderRhythmone, - "rtbhouse": BidderRTBHouse, - "rubicon": BidderRubicon, - "sharethrough": BidderSharethrough, - "smartrtb": BidderSmartRTB, - "somoaudience": BidderSomoaudience, - "sonobi": BidderSonobi, - "sovrn": BidderSovrn, - "synacormedia": BidderSynacormedia, - "tappx": BidderTappx, - "triplelift": BidderTriplelift, - "triplelift_native": BidderTripleliftNative, - "unruly": BidderUnruly, - "verizonmedia": BidderVerizonMedia, - "visx": BidderVisx, - "vrtcal": BidderVrtcal, - "yieldmo": BidderYieldmo, + "33across": Bidder33Across, + "adform": BidderAdform, + "adkernel": BidderAdkernel, + "adkernelAdn": BidderAdkernelAdn, + "adpone": BidderAdpone, + "adtelligent": BidderAdtelligent, + "advangelists": BidderAdvangelists, + "applogy": BidderApplogy, + "appnexus": BidderAppnexus, + "beachfront": BidderBeachfront, + "brightroll": BidderBrightroll, + "consumable": BidderConsumable, + "conversant": BidderConversant, + "cpmstar": BidderCpmstar, + "datablocks": BidderDatablocks, + "emx_digital": BidderEmxDigital, + "engagebdr": BidderEngageBDR, + "eplanning": BidderEPlanning, + "audienceNetwork": BidderFacebook, + "gamma": BidderGamma, + "gamoshi": BidderGamoshi, + "grid": BidderGrid, + "gumgum": BidderGumGum, + "gumgum_ortb": BidderGumGumOrtb, + "improvedigital": BidderImprovedigital, + "ix": BidderIx, + "kubient": BidderKubient, + "lifestreet": BidderLifestreet, + "lockerdome": BidderLockerDome, + "marsmedia": BidderMarsmedia, + "mgid": BidderMgid, + "openx": BidderOpenx, + "pubmatic": BidderPubmatic, + "pubnative": BidderPubnative, + "pulsepoint": BidderPulsepoint, + "pulsepoint_ortb": BidderPulsepointOrtb, + "rhythmone": BidderRhythmone, + "rtbhouse": BidderRTBHouse, + "rubicon": BidderRubicon, + "sharethrough": BidderSharethrough, + "smartrtb": BidderSmartRTB, + "somoaudience": BidderSomoaudience, + "sonobi": BidderSonobi, + "sovrn": BidderSovrn, + "synacormedia": BidderSynacormedia, + "tappx": BidderTappx, + "triplelift": BidderTriplelift, + "triplelift_native": BidderTripleliftNative, + "unruly": BidderUnruly, + "verizonmedia": BidderVerizonMedia, + "visx": BidderVisx, + "vrtcal": BidderVrtcal, + "yieldmo": BidderYieldmo, } // BidderList returns the values of the BidderMap func BidderList() []BidderName { bidders := make([]BidderName, 0, len(BidderMap)) for _, value := range BidderMap { - bidders = append(bidders, value) + bidders = append(bidders, value) } return bidders } @@ -147,7 +147,7 @@ func (name BidderName) MarshalJSON() ([]byte, error) { func (name *BidderName) String() string { if name == nil { - return "" + return "" } return string(*name) @@ -167,38 +167,38 @@ type BidderParamValidator interface { func NewBidderParamsValidator(schemaDirectory string) (BidderParamValidator, error) { fileInfos, err := ioutil.ReadDir(schemaDirectory) if err != nil { - return nil, fmt.Errorf("Failed to read JSON schemas from directory %s. %v", schemaDirectory, err) + return nil, fmt.Errorf("Failed to read JSON schemas from directory %s. %v", schemaDirectory, err) } schemaContents := make(map[BidderName]string, 50) schemas := make(map[BidderName]*gojsonschema.Schema, 50) for _, fileInfo := range fileInfos { - bidderName := strings.TrimSuffix(fileInfo.Name(), ".json") - if _, isValid := BidderMap[bidderName]; !isValid { - return nil, fmt.Errorf("File %s/%s does not match a valid BidderName.", schemaDirectory, fileInfo.Name()) - } - toOpen, err := filepath.Abs(filepath.Join(schemaDirectory, fileInfo.Name())) - if err != nil { - return nil, fmt.Errorf("Failed to get an absolute representation of the path: %s, %v", toOpen, err) - } - schemaLoader := gojsonschema.NewReferenceLoader("file:///" + filepath.ToSlash(toOpen)) - loadedSchema, err := gojsonschema.NewSchema(schemaLoader) - if err != nil { - return nil, fmt.Errorf("Failed to load json schema at %s: %v", toOpen, err) - } - - fileBytes, err := ioutil.ReadFile(fmt.Sprintf("%s/%s", schemaDirectory, fileInfo.Name())) - if err != nil { - return nil, fmt.Errorf("Failed to read file %s/%s: %v", schemaDirectory, fileInfo.Name(), err) - } - - schemas[BidderName(bidderName)] = loadedSchema - schemaContents[BidderName(bidderName)] = string(fileBytes) + bidderName := strings.TrimSuffix(fileInfo.Name(), ".json") + if _, isValid := BidderMap[bidderName]; !isValid { + return nil, fmt.Errorf("File %s/%s does not match a valid BidderName.", schemaDirectory, fileInfo.Name()) + } + toOpen, err := filepath.Abs(filepath.Join(schemaDirectory, fileInfo.Name())) + if err != nil { + return nil, fmt.Errorf("Failed to get an absolute representation of the path: %s, %v", toOpen, err) + } + schemaLoader := gojsonschema.NewReferenceLoader("file:///" + filepath.ToSlash(toOpen)) + loadedSchema, err := gojsonschema.NewSchema(schemaLoader) + if err != nil { + return nil, fmt.Errorf("Failed to load json schema at %s: %v", toOpen, err) + } + + fileBytes, err := ioutil.ReadFile(fmt.Sprintf("%s/%s", schemaDirectory, fileInfo.Name())) + if err != nil { + return nil, fmt.Errorf("Failed to read file %s/%s: %v", schemaDirectory, fileInfo.Name(), err) + } + + schemas[BidderName(bidderName)] = loadedSchema + schemaContents[BidderName(bidderName)] = string(fileBytes) } return &bidderParamValidator{ - schemaContents: schemaContents, - parsedSchemas: schemas, + schemaContents: schemaContents, + parsedSchemas: schemas, }, nil } @@ -210,14 +210,14 @@ type bidderParamValidator struct { func (validator *bidderParamValidator) Validate(name BidderName, ext json.RawMessage) error { result, err := validator.parsedSchemas[name].Validate(gojsonschema.NewBytesLoader(ext)) if err != nil { - return err + return err } if !result.Valid() { - errBuilder := bytes.NewBuffer(make([]byte, 0, 300)) - for _, err := range result.Errors() { - errBuilder.WriteString(err.String()) - } - return errors.New(errBuilder.String()) + errBuilder := bytes.NewBuffer(make([]byte, 0, 300)) + for _, err := range result.Errors() { + errBuilder.WriteString(err.String()) + } + return errors.New(errBuilder.String()) } return nil } From 94060da0151780db50cb3c6e2b1d3fa68d796345 Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Fri, 2 Oct 2020 13:33:00 -0400 Subject: [PATCH 028/125] anotherr formatting attempt --- openrtb_ext/bidders.go | 126 ++++++++++++++++++++--------------------- 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 0d71b1c5f59..1286e4220d4 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -134,94 +134,94 @@ var BidderMap = map[string]BidderName{ // BidderList returns the values of the BidderMap func BidderList() []BidderName { - bidders := make([]BidderName, 0, len(BidderMap)) - for _, value := range BidderMap { - bidders = append(bidders, value) - } - return bidders + bidders := make([]BidderName, 0, len(BidderMap)) + for _, value := range BidderMap { + bidders = append(bidders, value) + } + return bidders } func (name BidderName) MarshalJSON() ([]byte, error) { - return []byte(name), nil + return []byte(name), nil } func (name *BidderName) String() string { - if name == nil { - return "" - } + if name == nil { + return "" + } - return string(*name) + return string(*name) } // The BidderParamValidator is used to enforce bidrequest.imp[i].ext.{anyBidder} values. // // This is treated differently from the other types because we rely on JSON-schemas to validate bidder params. type BidderParamValidator interface { - Validate(name BidderName, ext json.RawMessage) error - // Schema returns the JSON schema used to perform validation. - Schema(name BidderName) string + Validate(name BidderName, ext json.RawMessage) error + // Schema returns the JSON schema used to perform validation. + Schema(name BidderName) string } // NewBidderParamsValidator makes a BidderParamValidator, assuming all the necessary files exist in the filesystem. // This will error if, for example, a Bidder gets added but no JSON schema is written for them. func NewBidderParamsValidator(schemaDirectory string) (BidderParamValidator, error) { - fileInfos, err := ioutil.ReadDir(schemaDirectory) - if err != nil { - return nil, fmt.Errorf("Failed to read JSON schemas from directory %s. %v", schemaDirectory, err) - } - - schemaContents := make(map[BidderName]string, 50) - schemas := make(map[BidderName]*gojsonschema.Schema, 50) - for _, fileInfo := range fileInfos { - bidderName := strings.TrimSuffix(fileInfo.Name(), ".json") - if _, isValid := BidderMap[bidderName]; !isValid { - return nil, fmt.Errorf("File %s/%s does not match a valid BidderName.", schemaDirectory, fileInfo.Name()) - } - toOpen, err := filepath.Abs(filepath.Join(schemaDirectory, fileInfo.Name())) - if err != nil { - return nil, fmt.Errorf("Failed to get an absolute representation of the path: %s, %v", toOpen, err) - } - schemaLoader := gojsonschema.NewReferenceLoader("file:///" + filepath.ToSlash(toOpen)) - loadedSchema, err := gojsonschema.NewSchema(schemaLoader) - if err != nil { - return nil, fmt.Errorf("Failed to load json schema at %s: %v", toOpen, err) - } - - fileBytes, err := ioutil.ReadFile(fmt.Sprintf("%s/%s", schemaDirectory, fileInfo.Name())) - if err != nil { - return nil, fmt.Errorf("Failed to read file %s/%s: %v", schemaDirectory, fileInfo.Name(), err) - } - - schemas[BidderName(bidderName)] = loadedSchema - schemaContents[BidderName(bidderName)] = string(fileBytes) - } - - return &bidderParamValidator{ - schemaContents: schemaContents, - parsedSchemas: schemas, - }, nil + fileInfos, err := ioutil.ReadDir(schemaDirectory) + if err != nil { + return nil, fmt.Errorf("Failed to read JSON schemas from directory %s. %v", schemaDirectory, err) + } + + schemaContents := make(map[BidderName]string, 50) + schemas := make(map[BidderName]*gojsonschema.Schema, 50) + for _, fileInfo := range fileInfos { + bidderName := strings.TrimSuffix(fileInfo.Name(), ".json") + if _, isValid := BidderMap[bidderName]; !isValid { + return nil, fmt.Errorf("File %s/%s does not match a valid BidderName.", schemaDirectory, fileInfo.Name()) + } + toOpen, err := filepath.Abs(filepath.Join(schemaDirectory, fileInfo.Name())) + if err != nil { + return nil, fmt.Errorf("Failed to get an absolute representation of the path: %s, %v", toOpen, err) + } + schemaLoader := gojsonschema.NewReferenceLoader("file:///" + filepath.ToSlash(toOpen)) + loadedSchema, err := gojsonschema.NewSchema(schemaLoader) + if err != nil { + return nil, fmt.Errorf("Failed to load json schema at %s: %v", toOpen, err) + } + + fileBytes, err := ioutil.ReadFile(fmt.Sprintf("%s/%s", schemaDirectory, fileInfo.Name())) + if err != nil { + return nil, fmt.Errorf("Failed to read file %s/%s: %v", schemaDirectory, fileInfo.Name(), err) + } + + schemas[BidderName(bidderName)] = loadedSchema + schemaContents[BidderName(bidderName)] = string(fileBytes) + } + + return &bidderParamValidator{ + schemaContents: schemaContents, + parsedSchemas: schemas, + }, nil } type bidderParamValidator struct { - schemaContents map[BidderName]string - parsedSchemas map[BidderName]*gojsonschema.Schema + schemaContents map[BidderName]string + parsedSchemas map[BidderName]*gojsonschema.Schema } func (validator *bidderParamValidator) Validate(name BidderName, ext json.RawMessage) error { - result, err := validator.parsedSchemas[name].Validate(gojsonschema.NewBytesLoader(ext)) - if err != nil { - return err - } - if !result.Valid() { - errBuilder := bytes.NewBuffer(make([]byte, 0, 300)) - for _, err := range result.Errors() { - errBuilder.WriteString(err.String()) - } - return errors.New(errBuilder.String()) - } - return nil + result, err := validator.parsedSchemas[name].Validate(gojsonschema.NewBytesLoader(ext)) + if err != nil { + return err + } + if !result.Valid() { + errBuilder := bytes.NewBuffer(make([]byte, 0, 300)) + for _, err := range result.Errors() { + errBuilder.WriteString(err.String()) + } + return errors.New(errBuilder.String()) + } + return nil } func (validator *bidderParamValidator) Schema(name BidderName) string { - return validator.schemaContents[name] + return validator.schemaContents[name] } From 381cc69eac27cc1008ffa26029d35f20ebc198d4 Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Wed, 11 Nov 2020 16:07:12 -0500 Subject: [PATCH 029/125] add engageBDR as an ortb bidder --- config/config.go | 1 + openrtb_ext/bidders.go | 2 ++ 2 files changed, 3 insertions(+) diff --git a/config/config.go b/config/config.go index 6b06c400ddf..de6cbd04856 100644 --- a/config/config.go +++ b/config/config.go @@ -683,6 +683,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.datablocks.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}") v.SetDefault("adapters.emx_digital.endpoint", "https://hb.emxdgt.com") v.SetDefault("adapters.engagebdr.endpoint", "http://dsp.bnmla.com/hb") + v.SetDefault("adapters.engagebdr_ortb.endpoint", "dsp.bnmla.com/bid?sspid=1000204") v.SetDefault("adapters.eplanning.endpoint", "https://ads.us.e-planning.net/hb/1") v.SetDefault("adapters.gamma.endpoint", "https://hb.gammaplatform.com/adx/request/") v.SetDefault("adapters.gamoshi.endpoint", "https://rtb.gamoshi.io") diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 1286e4220d4..3ebab10169f 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -37,6 +37,7 @@ const ( BidderDatablocks BidderName = "datablocks" BidderEmxDigital BidderName = "emx_digital" BidderEngageBDR BidderName = "engagebdr" + BidderEngageBDROrtb BidderName = "engagebdr_ortb" BidderEPlanning BidderName = "eplanning" BidderFacebook BidderName = "audienceNetwork" BidderGamma BidderName = "gamma" @@ -94,6 +95,7 @@ var BidderMap = map[string]BidderName{ "datablocks": BidderDatablocks, "emx_digital": BidderEmxDigital, "engagebdr": BidderEngageBDR, + "engagebdr_ortb" BidderEngageBDROrtb "eplanning": BidderEPlanning, "audienceNetwork": BidderFacebook, "gamma": BidderGamma, From 5cbaa22353104b193dbc45e6dbe1c241e4dddcd1 Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Wed, 11 Nov 2020 22:51:52 -0500 Subject: [PATCH 030/125] add engagebdr files and endpoint --- config/config.go | 2 +- exchange/adapter_map.go | 5 +++-- openrtb_ext/bidders.go | 2 +- static/bidder-info/engagebdr_ortb.yaml | 7 +++++++ static/bidder-params/engagebdr_ortb.json | 9 +++++++++ 5 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 static/bidder-info/engagebdr_ortb.yaml create mode 100644 static/bidder-params/engagebdr_ortb.json diff --git a/config/config.go b/config/config.go index de6cbd04856..b2c527cc0a8 100644 --- a/config/config.go +++ b/config/config.go @@ -683,7 +683,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.datablocks.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}") v.SetDefault("adapters.emx_digital.endpoint", "https://hb.emxdgt.com") v.SetDefault("adapters.engagebdr.endpoint", "http://dsp.bnmla.com/hb") - v.SetDefault("adapters.engagebdr_ortb.endpoint", "dsp.bnmla.com/bid?sspid=1000204") + v.SetDefault("adapters.engagebdr_ortb.endpoint", "https://dsp.bnmla.com/bid?sspid=1000204") v.SetDefault("adapters.eplanning.endpoint", "https://ads.us.e-planning.net/hb/1") v.SetDefault("adapters.gamma.endpoint", "https://hb.gammaplatform.com/adx/request/") v.SetDefault("adapters.gamoshi.endpoint", "https://rtb.gamoshi.io") diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index 787814bde41..8a4e2bc21f6 100644 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -86,6 +86,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderDatablocks: datablocks.NewDatablocksBidder(cfg.Adapters[string(openrtb_ext.BidderDatablocks)].Endpoint), openrtb_ext.BidderEmxDigital: emx_digital.NewEmxDigitalBidder(cfg.Adapters[string(openrtb_ext.BidderEmxDigital)].Endpoint), openrtb_ext.BidderEngageBDR: engagebdr.NewEngageBDRBidder(client, cfg.Adapters[string(openrtb_ext.BidderEngageBDR)].Endpoint), + openrtb_ext.BidderEngageBDROrtb: playwire_ortb.NewOrtbBidder(cfg.Adapters[string(openrtb_ext.BidderEngageBDROrtb)].Endpoint), openrtb_ext.BidderEPlanning: eplanning.NewEPlanningBidder(client, cfg.Adapters[string(openrtb_ext.BidderEPlanning)].Endpoint), openrtb_ext.BidderFacebook: audienceNetwork.NewFacebookBidder( client, @@ -95,6 +96,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderGamoshi: gamoshi.NewGamoshiBidder(cfg.Adapters[string(openrtb_ext.BidderGamoshi)].Endpoint), openrtb_ext.BidderGrid: grid.NewGridBidder(cfg.Adapters[string(openrtb_ext.BidderGrid)].Endpoint), openrtb_ext.BidderGumGum: gumgum.NewGumGumBidder(cfg.Adapters[string(openrtb_ext.BidderGumGum)].Endpoint), + openrtb_ext.BidderGumGumOrtb: playwire_ortb.NewOrtbBidder(cfg.Adapters[string(openrtb_ext.BidderGumGumOrtb)].Endpoint), openrtb_ext.BidderImprovedigital: improvedigital.NewImprovedigitalBidder(cfg.Adapters[string(openrtb_ext.BidderImprovedigital)].Endpoint), openrtb_ext.BidderKubient: kubient.NewKubientBidder(cfg.Adapters[string(openrtb_ext.BidderKubient)].Endpoint), openrtb_ext.BidderLockerDome: lockerdome.NewLockerDomeBidder(cfg.Adapters[string(openrtb_ext.BidderLockerDome)].Endpoint), @@ -103,6 +105,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderOpenx: openx.NewOpenxBidder(cfg.Adapters[string(openrtb_ext.BidderOpenx)].Endpoint), openrtb_ext.BidderPubmatic: pubmatic.NewPubmaticBidder(client, cfg.Adapters[string(openrtb_ext.BidderPubmatic)].Endpoint), openrtb_ext.BidderPubnative: pubnative.NewPubnativeBidder(cfg.Adapters[string(openrtb_ext.BidderPubnative)].Endpoint), + openrtb_ext.BidderPulsepointOrtb: playwire_ortb.NewOrtbBidder(cfg.Adapters[string(openrtb_ext.BidderPulsepointOrtb)].Endpoint), openrtb_ext.BidderRhythmone: rhythmone.NewRhythmoneBidder(cfg.Adapters[string(openrtb_ext.BidderRhythmone)].Endpoint), openrtb_ext.BidderRTBHouse: rtbhouse.NewRTBHouseBidder(cfg.Adapters[string(openrtb_ext.BidderRTBHouse)].Endpoint), openrtb_ext.BidderRubicon: rubicon.NewRubiconBidder( @@ -126,8 +129,6 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderVisx: visx.NewVisxBidder(cfg.Adapters[string(openrtb_ext.BidderVisx)].Endpoint), openrtb_ext.BidderVrtcal: vrtcal.NewVrtcalBidder(cfg.Adapters[string(openrtb_ext.BidderVrtcal)].Endpoint), openrtb_ext.BidderYieldmo: yieldmo.NewYieldmoBidder(cfg.Adapters[string(openrtb_ext.BidderYieldmo)].Endpoint), - openrtb_ext.BidderPulsepointOrtb: playwire_ortb.NewOrtbBidder(cfg.Adapters[string(openrtb_ext.BidderPulsepointOrtb)].Endpoint), - openrtb_ext.BidderGumGumOrtb: playwire_ortb.NewOrtbBidder(cfg.Adapters[string(openrtb_ext.BidderGumGumOrtb)].Endpoint), } legacyBidders := map[openrtb_ext.BidderName]adapters.Adapter{ diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 3ebab10169f..ab303fc7744 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -95,7 +95,7 @@ var BidderMap = map[string]BidderName{ "datablocks": BidderDatablocks, "emx_digital": BidderEmxDigital, "engagebdr": BidderEngageBDR, - "engagebdr_ortb" BidderEngageBDROrtb + "engagebdr_ortb": BidderEngageBDROrtb, "eplanning": BidderEPlanning, "audienceNetwork": BidderFacebook, "gamma": BidderGamma, diff --git a/static/bidder-info/engagebdr_ortb.yaml b/static/bidder-info/engagebdr_ortb.yaml new file mode 100644 index 00000000000..764b49023df --- /dev/null +++ b/static/bidder-info/engagebdr_ortb.yaml @@ -0,0 +1,7 @@ +maintainer: + email: "pubtech@na.com" +capabilities: + site: + mediaTypes: + - banner + - video diff --git a/static/bidder-params/engagebdr_ortb.json b/static/bidder-params/engagebdr_ortb.json new file mode 100644 index 00000000000..acc2d5c221b --- /dev/null +++ b/static/bidder-params/engagebdr_ortb.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "GumGum Adapter Params", + "description": "A schema which validates params accepted by the GumGum adapter", + "type": "object", + "properties": { + + } +} From 9013b2c4901dcb296e6eb4862da92f0f11e8eeb3 Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Tue, 17 Nov 2020 15:31:28 -0500 Subject: [PATCH 031/125] Update reference to playwire_ortb adapter --- exchange/adapter_map.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index 8a4e2bc21f6..cf3110b1d82 100644 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -61,7 +61,7 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" - "prebid-server/adapters/playwire_ortb" + "https://github.com/intergi/prebid-server/tree/develop/adapters/playwire_ortb" ) // The newAdapterMap function is segregated to its own file to make it a simple and clean location for each Adapter From a27a59298201ea159d55c1d29cb0bcf1c3a07493 Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Tue, 17 Nov 2020 15:34:37 -0500 Subject: [PATCH 032/125] Revert "Update reference to playwire_ortb adapter" --- exchange/adapter_map.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index cf3110b1d82..8a4e2bc21f6 100644 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -61,7 +61,7 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" - "https://github.com/intergi/prebid-server/tree/develop/adapters/playwire_ortb" + "prebid-server/adapters/playwire_ortb" ) // The newAdapterMap function is segregated to its own file to make it a simple and clean location for each Adapter From e8faacf8b8daf0bc7bf57292c36f9f060760737b Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Tue, 17 Nov 2020 15:36:55 -0500 Subject: [PATCH 033/125] Revert "Ortb bidders" --- adapters/playwire_ortb/params_test.go | 52 ------ adapters/playwire_ortb/playwire_ortb.go | 172 ------------------ adapters/playwire_ortb/playwire_ortb_test.go | 10 - .../playwire_ortbtest/exemplary/banner.json | 99 ---------- .../playwire_ortbtest/params/race/banner.json | 3 - adapters/playwire_ortb/usersync.go | 12 -- adapters/playwire_ortb/usersync_test.go | 35 ---- config/config.go | 3 - exchange/adapter_map.go | 5 - openrtb_ext/bidders.go | 6 - static/bidder-info/engagebdr_ortb.yaml | 7 - static/bidder-info/gumgum_ortb.yaml | 7 - static/bidder-info/pulsepoint_ortb.yaml | 7 - static/bidder-params/engagebdr_ortb.json | 9 - static/bidder-params/gumgum_ortb.json | 9 - static/bidder-params/pulsepoint_ortb.json | 9 - 16 files changed, 445 deletions(-) delete mode 100644 adapters/playwire_ortb/params_test.go delete mode 100644 adapters/playwire_ortb/playwire_ortb.go delete mode 100644 adapters/playwire_ortb/playwire_ortb_test.go delete mode 100644 adapters/playwire_ortb/playwire_ortbtest/exemplary/banner.json delete mode 100644 adapters/playwire_ortb/playwire_ortbtest/params/race/banner.json delete mode 100644 adapters/playwire_ortb/usersync.go delete mode 100644 adapters/playwire_ortb/usersync_test.go delete mode 100644 static/bidder-info/engagebdr_ortb.yaml delete mode 100644 static/bidder-info/gumgum_ortb.yaml delete mode 100644 static/bidder-info/pulsepoint_ortb.yaml delete mode 100644 static/bidder-params/engagebdr_ortb.json delete mode 100644 static/bidder-params/gumgum_ortb.json delete mode 100644 static/bidder-params/pulsepoint_ortb.json diff --git a/adapters/playwire_ortb/params_test.go b/adapters/playwire_ortb/params_test.go deleted file mode 100644 index 4adfafbdc7f..00000000000 --- a/adapters/playwire_ortb/params_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package playwire_ortb - -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.BidderGumGum, json.RawMessage(validParam)); err != nil { - t.Errorf("Schema rejected gumgum 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{ - `{"zone":"dc9d6be1"}`, -} - -var invalidParams = []string{ - `null`, - `nil`, - ``, - `{}`, - `[]`, - `true`, - `2`, - `{"zone":12345678}`, - `{"zone":""}`, - `{"placementId": 1}`, - `{"zone": true}`, - `{"placementId": 1, "zone":"1234567"}`, -} diff --git a/adapters/playwire_ortb/playwire_ortb.go b/adapters/playwire_ortb/playwire_ortb.go deleted file mode 100644 index befb679cc34..00000000000 --- a/adapters/playwire_ortb/playwire_ortb.go +++ /dev/null @@ -1,172 +0,0 @@ -package playwire_ortb - -import ( - "encoding/json" - "fmt" - "github.com/mxmCherry/openrtb" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - "net/http" - "strconv" - "strings" -) - -// PlaywireORTBAdapter implements Bidder interface. -type PlaywireORTBAdapter struct { - URI string -} - -// MakeRequests makes the HTTP requests which should be made to fetch bids. -func (g *PlaywireORTBAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - var validImps []openrtb.Imp - var trackingId string - - numRequests := len(request.Imp) - errs := make([]error, 0, numRequests) - - for i := 0; i < numRequests; i++ { - imp := request.Imp[i] - zone, err := preprocess(&imp) - if err != nil { - errs = append(errs, err) - } else if request.Imp[i].Banner != nil { - bannerCopy := *request.Imp[i].Banner - if bannerCopy.W == nil && bannerCopy.H == nil && len(bannerCopy.Format) > 0 { - format := bannerCopy.Format[0] - bannerCopy.W = &(format.W) - bannerCopy.H = &(format.H) - } - request.Imp[i].Banner = &bannerCopy - validImps = append(validImps, request.Imp[i]) - trackingId = zone - } else if request.Imp[i].Video != nil { - err := validateVideoParams(request.Imp[i].Video) - if err != nil { - errs = append(errs, err) - } else { - validImps = append(validImps, request.Imp[i]) - trackingId = zone - } - } - } - - if len(validImps) == 0 { - return nil, errs - } - - request.Imp = validImps - - if request.Site != nil { - siteCopy := *request.Site - siteCopy.ID = trackingId - request.Site = &siteCopy - } - - reqJSON, err := json.Marshal(request) - if err != nil { - errs = append(errs, err) - return nil, errs - } - - headers := http.Header{} - headers.Add("Content-Type", "application/json;charset=utf-8") - headers.Add("Accept", "application/json") - return []*adapters.RequestData{{ - Method: "POST", - Uri: g.URI, - Body: reqJSON, - Headers: headers, - }}, errs -} - -// MakeBids unpacks the server's response into Bids. -func (g *PlaywireORTBAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { - if response.StatusCode == http.StatusNoContent { - return nil, nil - } - - if response.StatusCode == http.StatusBadRequest { - return nil, []error{&errortypes.BadInput{ - Message: fmt.Sprintf("Bad user input: HTTP status %d", response.StatusCode), - }} - } - - if response.StatusCode != http.StatusOK { - return nil, []error{&errortypes.BadServerResponse{ - Message: fmt.Sprintf("Bad server response: HTTP status %d", response.StatusCode), - }} - } - var bidResp openrtb.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { - return nil, []error{&errortypes.BadServerResponse{ - Message: fmt.Sprintf("Bad server response: %d. ", err), - }} - } - - var errs []error - bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) - - for _, sb := range bidResp.SeatBid { - for i := range sb.Bid { - mediaType := getMediaTypeForImpID(sb.Bid[i].ImpID, internalRequest.Imp) - if mediaType == openrtb_ext.BidTypeVideo { - price := strconv.FormatFloat(sb.Bid[i].Price, 'f', -1, 64) - sb.Bid[i].AdM = strings.Replace(sb.Bid[i].AdM, "${AUCTION_PRICE}", price, -1) - } - - bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ - Bid: &sb.Bid[i], - BidType: mediaType, - }) - } - } - - return bidResponse, errs -} - -func preprocess(imp *openrtb.Imp) (string, error) { - var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { - err = &errortypes.BadInput{ - Message: err.Error(), - } - return "", err - } - - var gumgumExt openrtb_ext.ExtImpGumGum - if err := json.Unmarshal(bidderExt.Bidder, &gumgumExt); err != nil { - err = &errortypes.BadInput{ - Message: err.Error(), - } - return "", err - } - - zone := gumgumExt.Zone - return zone, nil -} - -func getMediaTypeForImpID(impID string, imps []openrtb.Imp) openrtb_ext.BidType { - for _, imp := range imps { - if imp.ID == impID && imp.Banner != nil { - return openrtb_ext.BidTypeBanner - } - } - return openrtb_ext.BidTypeVideo -} - -func validateVideoParams(video *openrtb.Video) (err error) { - // if video.W == 0 || video.H == 0 || video.MinDuration == 0 || video.MaxDuration == 0 || video.Placement == 0 || video.Linearity == 0 { - // return &errortypes.BadInput{ - // Message: "Invalid or missing video field(s)", - // } - // } - return nil -} - -// NewOrtbBidder configures bidder endpoint. -func NewOrtbBidder(endpoint string) *PlaywireORTBAdapter { - return &PlaywireORTBAdapter{ - URI: endpoint, - } -} \ No newline at end of file diff --git a/adapters/playwire_ortb/playwire_ortb_test.go b/adapters/playwire_ortb/playwire_ortb_test.go deleted file mode 100644 index 6a45a92c22b..00000000000 --- a/adapters/playwire_ortb/playwire_ortb_test.go +++ /dev/null @@ -1,10 +0,0 @@ -package playwire_ortb - -import ( - "github.com/prebid/prebid-server/adapters/adapterstest" - "testing" -) - -func TestJsonSamples(t *testing.T) { - adapterstest.RunJSONBidderTest(t, "gumgumtest", NewOrtbBidder("https://g2.gumgum.com/providers/prbds2s/bid")) -} diff --git a/adapters/playwire_ortb/playwire_ortbtest/exemplary/banner.json b/adapters/playwire_ortb/playwire_ortbtest/exemplary/banner.json deleted file mode 100644 index 2fbd3da22da..00000000000 --- a/adapters/playwire_ortb/playwire_ortbtest/exemplary/banner.json +++ /dev/null @@ -1,99 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 300 - } - ], - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - "zone": "dc9d6be1" - } - } - } - ] - }, - - "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": { - "bidder": { - "zone": "dc9d6be1" - } - } - }] - } - }, - "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" - } - ] - } - ] -} diff --git a/adapters/playwire_ortb/playwire_ortbtest/params/race/banner.json b/adapters/playwire_ortb/playwire_ortbtest/params/race/banner.json deleted file mode 100644 index 6e222304f36..00000000000 --- a/adapters/playwire_ortb/playwire_ortbtest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "zone": "dc9d6be1" -} diff --git a/adapters/playwire_ortb/usersync.go b/adapters/playwire_ortb/usersync.go deleted file mode 100644 index 5c9b1463e16..00000000000 --- a/adapters/playwire_ortb/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package playwire_ortb - -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", 61, temp, adapters.SyncTypeIframe) -} diff --git a/adapters/playwire_ortb/usersync_test.go b/adapters/playwire_ortb/usersync_test.go deleted file mode 100644 index 89e1c06ca13..00000000000 --- a/adapters/playwire_ortb/usersync_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package playwire_ortb - -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{ - Value: "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.EqualValues(t, 61, syncer.GDPRVendorID()) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/config/config.go b/config/config.go index b2c527cc0a8..2f302cc6328 100644 --- a/config/config.go +++ b/config/config.go @@ -683,13 +683,11 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.datablocks.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}") v.SetDefault("adapters.emx_digital.endpoint", "https://hb.emxdgt.com") v.SetDefault("adapters.engagebdr.endpoint", "http://dsp.bnmla.com/hb") - v.SetDefault("adapters.engagebdr_ortb.endpoint", "https://dsp.bnmla.com/bid?sspid=1000204") v.SetDefault("adapters.eplanning.endpoint", "https://ads.us.e-planning.net/hb/1") v.SetDefault("adapters.gamma.endpoint", "https://hb.gammaplatform.com/adx/request/") v.SetDefault("adapters.gamoshi.endpoint", "https://rtb.gamoshi.io") v.SetDefault("adapters.grid.endpoint", "http://grid.bidswitch.net/sp_bid?sp=prebid") v.SetDefault("adapters.gumgum.endpoint", "https://g2.gumgum.com/providers/prbds2s/bid") - v.SetDefault("adapters.gumgum_ortb.endpoint", "https://g2.gumgum.com/zones/8ylgv2wd/bid") v.SetDefault("adapters.improvedigital.endpoint", "http://ad.360yield.com/pbs") v.SetDefault("adapters.ix.endpoint", "http://appnexus-us-east.lb.indexww.com/transbidder?p=184932") v.SetDefault("adapters.kubient.endpoint", "http://kbntx.ch/prebid") @@ -701,7 +699,6 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.pubmatic.endpoint", "https://hbopenbid.pubmatic.com/translator?source=prebid-server") v.SetDefault("adapters.pubnative.endpoint", "http://dsp.pubnative.net/bid/v1/request") v.SetDefault("adapters.pulsepoint.endpoint", "http://bid.contextweb.com/header/s/ortb/prebid-s2s") - v.SetDefault("adapters.pulsepoint_ortb.endpoint", "http://rts.lga.contextweb.com/rt-seller/bid/openrtb/playwire") v.SetDefault("adapters.rhythmone.endpoint", "http://tag.1rx.io/rmp") v.SetDefault("adapters.rtbhouse.endpoint", "http://prebidserver-s2s-ams.creativecdn.com/bidder/prebidserver/bids") v.SetDefault("adapters.rubicon.endpoint", "http://exapi-us-east.rubiconproject.com/a/api/exchange.json") diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index 8a4e2bc21f6..95f5b7f5882 100644 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -60,8 +60,6 @@ import ( "github.com/prebid/prebid-server/adapters/yieldmo" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" - - "prebid-server/adapters/playwire_ortb" ) // The newAdapterMap function is segregated to its own file to make it a simple and clean location for each Adapter @@ -86,7 +84,6 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderDatablocks: datablocks.NewDatablocksBidder(cfg.Adapters[string(openrtb_ext.BidderDatablocks)].Endpoint), openrtb_ext.BidderEmxDigital: emx_digital.NewEmxDigitalBidder(cfg.Adapters[string(openrtb_ext.BidderEmxDigital)].Endpoint), openrtb_ext.BidderEngageBDR: engagebdr.NewEngageBDRBidder(client, cfg.Adapters[string(openrtb_ext.BidderEngageBDR)].Endpoint), - openrtb_ext.BidderEngageBDROrtb: playwire_ortb.NewOrtbBidder(cfg.Adapters[string(openrtb_ext.BidderEngageBDROrtb)].Endpoint), openrtb_ext.BidderEPlanning: eplanning.NewEPlanningBidder(client, cfg.Adapters[string(openrtb_ext.BidderEPlanning)].Endpoint), openrtb_ext.BidderFacebook: audienceNetwork.NewFacebookBidder( client, @@ -96,7 +93,6 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderGamoshi: gamoshi.NewGamoshiBidder(cfg.Adapters[string(openrtb_ext.BidderGamoshi)].Endpoint), openrtb_ext.BidderGrid: grid.NewGridBidder(cfg.Adapters[string(openrtb_ext.BidderGrid)].Endpoint), openrtb_ext.BidderGumGum: gumgum.NewGumGumBidder(cfg.Adapters[string(openrtb_ext.BidderGumGum)].Endpoint), - openrtb_ext.BidderGumGumOrtb: playwire_ortb.NewOrtbBidder(cfg.Adapters[string(openrtb_ext.BidderGumGumOrtb)].Endpoint), openrtb_ext.BidderImprovedigital: improvedigital.NewImprovedigitalBidder(cfg.Adapters[string(openrtb_ext.BidderImprovedigital)].Endpoint), openrtb_ext.BidderKubient: kubient.NewKubientBidder(cfg.Adapters[string(openrtb_ext.BidderKubient)].Endpoint), openrtb_ext.BidderLockerDome: lockerdome.NewLockerDomeBidder(cfg.Adapters[string(openrtb_ext.BidderLockerDome)].Endpoint), @@ -105,7 +101,6 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderOpenx: openx.NewOpenxBidder(cfg.Adapters[string(openrtb_ext.BidderOpenx)].Endpoint), openrtb_ext.BidderPubmatic: pubmatic.NewPubmaticBidder(client, cfg.Adapters[string(openrtb_ext.BidderPubmatic)].Endpoint), openrtb_ext.BidderPubnative: pubnative.NewPubnativeBidder(cfg.Adapters[string(openrtb_ext.BidderPubnative)].Endpoint), - openrtb_ext.BidderPulsepointOrtb: playwire_ortb.NewOrtbBidder(cfg.Adapters[string(openrtb_ext.BidderPulsepointOrtb)].Endpoint), openrtb_ext.BidderRhythmone: rhythmone.NewRhythmoneBidder(cfg.Adapters[string(openrtb_ext.BidderRhythmone)].Endpoint), openrtb_ext.BidderRTBHouse: rtbhouse.NewRTBHouseBidder(cfg.Adapters[string(openrtb_ext.BidderRTBHouse)].Endpoint), openrtb_ext.BidderRubicon: rubicon.NewRubiconBidder( diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index ab303fc7744..7a3f24eb07f 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -37,14 +37,12 @@ const ( BidderDatablocks BidderName = "datablocks" BidderEmxDigital BidderName = "emx_digital" BidderEngageBDR BidderName = "engagebdr" - BidderEngageBDROrtb BidderName = "engagebdr_ortb" BidderEPlanning BidderName = "eplanning" BidderFacebook BidderName = "audienceNetwork" BidderGamma BidderName = "gamma" BidderGamoshi BidderName = "gamoshi" BidderGrid BidderName = "grid" BidderGumGum BidderName = "gumgum" - BidderGumGumOrtb BidderName = "gumgum_ortb" BidderImprovedigital BidderName = "improvedigital" BidderIx BidderName = "ix" BidderKubient BidderName = "kubient" @@ -56,7 +54,6 @@ const ( BidderPubmatic BidderName = "pubmatic" BidderPubnative BidderName = "pubnative" BidderPulsepoint BidderName = "pulsepoint" - BidderPulsepointOrtb BidderName = "pulsepoint_ortb" BidderRhythmone BidderName = "rhythmone" BidderRTBHouse BidderName = "rtbhouse" BidderRubicon BidderName = "rubicon" @@ -95,14 +92,12 @@ var BidderMap = map[string]BidderName{ "datablocks": BidderDatablocks, "emx_digital": BidderEmxDigital, "engagebdr": BidderEngageBDR, - "engagebdr_ortb": BidderEngageBDROrtb, "eplanning": BidderEPlanning, "audienceNetwork": BidderFacebook, "gamma": BidderGamma, "gamoshi": BidderGamoshi, "grid": BidderGrid, "gumgum": BidderGumGum, - "gumgum_ortb": BidderGumGumOrtb, "improvedigital": BidderImprovedigital, "ix": BidderIx, "kubient": BidderKubient, @@ -114,7 +109,6 @@ var BidderMap = map[string]BidderName{ "pubmatic": BidderPubmatic, "pubnative": BidderPubnative, "pulsepoint": BidderPulsepoint, - "pulsepoint_ortb": BidderPulsepointOrtb, "rhythmone": BidderRhythmone, "rtbhouse": BidderRTBHouse, "rubicon": BidderRubicon, diff --git a/static/bidder-info/engagebdr_ortb.yaml b/static/bidder-info/engagebdr_ortb.yaml deleted file mode 100644 index 764b49023df..00000000000 --- a/static/bidder-info/engagebdr_ortb.yaml +++ /dev/null @@ -1,7 +0,0 @@ -maintainer: - email: "pubtech@na.com" -capabilities: - site: - mediaTypes: - - banner - - video diff --git a/static/bidder-info/gumgum_ortb.yaml b/static/bidder-info/gumgum_ortb.yaml deleted file mode 100644 index 764b49023df..00000000000 --- a/static/bidder-info/gumgum_ortb.yaml +++ /dev/null @@ -1,7 +0,0 @@ -maintainer: - email: "pubtech@na.com" -capabilities: - site: - mediaTypes: - - banner - - video diff --git a/static/bidder-info/pulsepoint_ortb.yaml b/static/bidder-info/pulsepoint_ortb.yaml deleted file mode 100644 index 764b49023df..00000000000 --- a/static/bidder-info/pulsepoint_ortb.yaml +++ /dev/null @@ -1,7 +0,0 @@ -maintainer: - email: "pubtech@na.com" -capabilities: - site: - mediaTypes: - - banner - - video diff --git a/static/bidder-params/engagebdr_ortb.json b/static/bidder-params/engagebdr_ortb.json deleted file mode 100644 index acc2d5c221b..00000000000 --- a/static/bidder-params/engagebdr_ortb.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "GumGum Adapter Params", - "description": "A schema which validates params accepted by the GumGum adapter", - "type": "object", - "properties": { - - } -} diff --git a/static/bidder-params/gumgum_ortb.json b/static/bidder-params/gumgum_ortb.json deleted file mode 100644 index acc2d5c221b..00000000000 --- a/static/bidder-params/gumgum_ortb.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "GumGum Adapter Params", - "description": "A schema which validates params accepted by the GumGum adapter", - "type": "object", - "properties": { - - } -} diff --git a/static/bidder-params/pulsepoint_ortb.json b/static/bidder-params/pulsepoint_ortb.json deleted file mode 100644 index acc2d5c221b..00000000000 --- a/static/bidder-params/pulsepoint_ortb.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "GumGum Adapter Params", - "description": "A schema which validates params accepted by the GumGum adapter", - "type": "object", - "properties": { - - } -} From 85d45c32b159400acd4a0266938db90010106d16 Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Tue, 17 Nov 2020 15:40:47 -0500 Subject: [PATCH 034/125] Revert "Revert "Ortb bidders"" --- adapters/playwire_ortb/params_test.go | 52 ++++++ adapters/playwire_ortb/playwire_ortb.go | 172 ++++++++++++++++++ adapters/playwire_ortb/playwire_ortb_test.go | 10 + .../playwire_ortbtest/exemplary/banner.json | 99 ++++++++++ .../playwire_ortbtest/params/race/banner.json | 3 + adapters/playwire_ortb/usersync.go | 12 ++ adapters/playwire_ortb/usersync_test.go | 35 ++++ config/config.go | 3 + exchange/adapter_map.go | 5 + openrtb_ext/bidders.go | 6 + static/bidder-info/engagebdr_ortb.yaml | 7 + static/bidder-info/gumgum_ortb.yaml | 7 + static/bidder-info/pulsepoint_ortb.yaml | 7 + static/bidder-params/engagebdr_ortb.json | 9 + static/bidder-params/gumgum_ortb.json | 9 + static/bidder-params/pulsepoint_ortb.json | 9 + 16 files changed, 445 insertions(+) create mode 100644 adapters/playwire_ortb/params_test.go create mode 100644 adapters/playwire_ortb/playwire_ortb.go create mode 100644 adapters/playwire_ortb/playwire_ortb_test.go create mode 100644 adapters/playwire_ortb/playwire_ortbtest/exemplary/banner.json create mode 100644 adapters/playwire_ortb/playwire_ortbtest/params/race/banner.json create mode 100644 adapters/playwire_ortb/usersync.go create mode 100644 adapters/playwire_ortb/usersync_test.go create mode 100644 static/bidder-info/engagebdr_ortb.yaml create mode 100644 static/bidder-info/gumgum_ortb.yaml create mode 100644 static/bidder-info/pulsepoint_ortb.yaml create mode 100644 static/bidder-params/engagebdr_ortb.json create mode 100644 static/bidder-params/gumgum_ortb.json create mode 100644 static/bidder-params/pulsepoint_ortb.json diff --git a/adapters/playwire_ortb/params_test.go b/adapters/playwire_ortb/params_test.go new file mode 100644 index 00000000000..4adfafbdc7f --- /dev/null +++ b/adapters/playwire_ortb/params_test.go @@ -0,0 +1,52 @@ +package playwire_ortb + +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.BidderGumGum, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected gumgum 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{ + `{"zone":"dc9d6be1"}`, +} + +var invalidParams = []string{ + `null`, + `nil`, + ``, + `{}`, + `[]`, + `true`, + `2`, + `{"zone":12345678}`, + `{"zone":""}`, + `{"placementId": 1}`, + `{"zone": true}`, + `{"placementId": 1, "zone":"1234567"}`, +} diff --git a/adapters/playwire_ortb/playwire_ortb.go b/adapters/playwire_ortb/playwire_ortb.go new file mode 100644 index 00000000000..befb679cc34 --- /dev/null +++ b/adapters/playwire_ortb/playwire_ortb.go @@ -0,0 +1,172 @@ +package playwire_ortb + +import ( + "encoding/json" + "fmt" + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" + "net/http" + "strconv" + "strings" +) + +// PlaywireORTBAdapter implements Bidder interface. +type PlaywireORTBAdapter struct { + URI string +} + +// MakeRequests makes the HTTP requests which should be made to fetch bids. +func (g *PlaywireORTBAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var validImps []openrtb.Imp + var trackingId string + + numRequests := len(request.Imp) + errs := make([]error, 0, numRequests) + + for i := 0; i < numRequests; i++ { + imp := request.Imp[i] + zone, err := preprocess(&imp) + if err != nil { + errs = append(errs, err) + } else if request.Imp[i].Banner != nil { + bannerCopy := *request.Imp[i].Banner + if bannerCopy.W == nil && bannerCopy.H == nil && len(bannerCopy.Format) > 0 { + format := bannerCopy.Format[0] + bannerCopy.W = &(format.W) + bannerCopy.H = &(format.H) + } + request.Imp[i].Banner = &bannerCopy + validImps = append(validImps, request.Imp[i]) + trackingId = zone + } else if request.Imp[i].Video != nil { + err := validateVideoParams(request.Imp[i].Video) + if err != nil { + errs = append(errs, err) + } else { + validImps = append(validImps, request.Imp[i]) + trackingId = zone + } + } + } + + if len(validImps) == 0 { + return nil, errs + } + + request.Imp = validImps + + if request.Site != nil { + siteCopy := *request.Site + siteCopy.ID = trackingId + request.Site = &siteCopy + } + + reqJSON, err := json.Marshal(request) + if err != nil { + errs = append(errs, err) + return nil, errs + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + return []*adapters.RequestData{{ + Method: "POST", + Uri: g.URI, + Body: reqJSON, + Headers: headers, + }}, errs +} + +// MakeBids unpacks the server's response into Bids. +func (g *PlaywireORTBAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + + if response.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Bad user input: HTTP status %d", response.StatusCode), + }} + } + + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Bad server response: HTTP status %d", response.StatusCode), + }} + } + var bidResp openrtb.BidResponse + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Bad server response: %d. ", err), + }} + } + + var errs []error + bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) + + for _, sb := range bidResp.SeatBid { + for i := range sb.Bid { + mediaType := getMediaTypeForImpID(sb.Bid[i].ImpID, internalRequest.Imp) + if mediaType == openrtb_ext.BidTypeVideo { + price := strconv.FormatFloat(sb.Bid[i].Price, 'f', -1, 64) + sb.Bid[i].AdM = strings.Replace(sb.Bid[i].AdM, "${AUCTION_PRICE}", price, -1) + } + + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &sb.Bid[i], + BidType: mediaType, + }) + } + } + + return bidResponse, errs +} + +func preprocess(imp *openrtb.Imp) (string, error) { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + err = &errortypes.BadInput{ + Message: err.Error(), + } + return "", err + } + + var gumgumExt openrtb_ext.ExtImpGumGum + if err := json.Unmarshal(bidderExt.Bidder, &gumgumExt); err != nil { + err = &errortypes.BadInput{ + Message: err.Error(), + } + return "", err + } + + zone := gumgumExt.Zone + return zone, nil +} + +func getMediaTypeForImpID(impID string, imps []openrtb.Imp) openrtb_ext.BidType { + for _, imp := range imps { + if imp.ID == impID && imp.Banner != nil { + return openrtb_ext.BidTypeBanner + } + } + return openrtb_ext.BidTypeVideo +} + +func validateVideoParams(video *openrtb.Video) (err error) { + // if video.W == 0 || video.H == 0 || video.MinDuration == 0 || video.MaxDuration == 0 || video.Placement == 0 || video.Linearity == 0 { + // return &errortypes.BadInput{ + // Message: "Invalid or missing video field(s)", + // } + // } + return nil +} + +// NewOrtbBidder configures bidder endpoint. +func NewOrtbBidder(endpoint string) *PlaywireORTBAdapter { + return &PlaywireORTBAdapter{ + URI: endpoint, + } +} \ No newline at end of file diff --git a/adapters/playwire_ortb/playwire_ortb_test.go b/adapters/playwire_ortb/playwire_ortb_test.go new file mode 100644 index 00000000000..6a45a92c22b --- /dev/null +++ b/adapters/playwire_ortb/playwire_ortb_test.go @@ -0,0 +1,10 @@ +package playwire_ortb + +import ( + "github.com/prebid/prebid-server/adapters/adapterstest" + "testing" +) + +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "gumgumtest", NewOrtbBidder("https://g2.gumgum.com/providers/prbds2s/bid")) +} diff --git a/adapters/playwire_ortb/playwire_ortbtest/exemplary/banner.json b/adapters/playwire_ortb/playwire_ortbtest/exemplary/banner.json new file mode 100644 index 00000000000..2fbd3da22da --- /dev/null +++ b/adapters/playwire_ortb/playwire_ortbtest/exemplary/banner.json @@ -0,0 +1,99 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 300 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "zone": "dc9d6be1" + } + } + } + ] + }, + + "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": { + "bidder": { + "zone": "dc9d6be1" + } + } + }] + } + }, + "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" + } + ] + } + ] +} diff --git a/adapters/playwire_ortb/playwire_ortbtest/params/race/banner.json b/adapters/playwire_ortb/playwire_ortbtest/params/race/banner.json new file mode 100644 index 00000000000..6e222304f36 --- /dev/null +++ b/adapters/playwire_ortb/playwire_ortbtest/params/race/banner.json @@ -0,0 +1,3 @@ +{ + "zone": "dc9d6be1" +} diff --git a/adapters/playwire_ortb/usersync.go b/adapters/playwire_ortb/usersync.go new file mode 100644 index 00000000000..5c9b1463e16 --- /dev/null +++ b/adapters/playwire_ortb/usersync.go @@ -0,0 +1,12 @@ +package playwire_ortb + +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", 61, temp, adapters.SyncTypeIframe) +} diff --git a/adapters/playwire_ortb/usersync_test.go b/adapters/playwire_ortb/usersync_test.go new file mode 100644 index 00000000000..89e1c06ca13 --- /dev/null +++ b/adapters/playwire_ortb/usersync_test.go @@ -0,0 +1,35 @@ +package playwire_ortb + +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{ + Value: "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.EqualValues(t, 61, syncer.GDPRVendorID()) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/config/config.go b/config/config.go index 2f302cc6328..b2c527cc0a8 100644 --- a/config/config.go +++ b/config/config.go @@ -683,11 +683,13 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.datablocks.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}") v.SetDefault("adapters.emx_digital.endpoint", "https://hb.emxdgt.com") v.SetDefault("adapters.engagebdr.endpoint", "http://dsp.bnmla.com/hb") + v.SetDefault("adapters.engagebdr_ortb.endpoint", "https://dsp.bnmla.com/bid?sspid=1000204") v.SetDefault("adapters.eplanning.endpoint", "https://ads.us.e-planning.net/hb/1") v.SetDefault("adapters.gamma.endpoint", "https://hb.gammaplatform.com/adx/request/") v.SetDefault("adapters.gamoshi.endpoint", "https://rtb.gamoshi.io") v.SetDefault("adapters.grid.endpoint", "http://grid.bidswitch.net/sp_bid?sp=prebid") v.SetDefault("adapters.gumgum.endpoint", "https://g2.gumgum.com/providers/prbds2s/bid") + v.SetDefault("adapters.gumgum_ortb.endpoint", "https://g2.gumgum.com/zones/8ylgv2wd/bid") v.SetDefault("adapters.improvedigital.endpoint", "http://ad.360yield.com/pbs") v.SetDefault("adapters.ix.endpoint", "http://appnexus-us-east.lb.indexww.com/transbidder?p=184932") v.SetDefault("adapters.kubient.endpoint", "http://kbntx.ch/prebid") @@ -699,6 +701,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.pubmatic.endpoint", "https://hbopenbid.pubmatic.com/translator?source=prebid-server") v.SetDefault("adapters.pubnative.endpoint", "http://dsp.pubnative.net/bid/v1/request") v.SetDefault("adapters.pulsepoint.endpoint", "http://bid.contextweb.com/header/s/ortb/prebid-s2s") + v.SetDefault("adapters.pulsepoint_ortb.endpoint", "http://rts.lga.contextweb.com/rt-seller/bid/openrtb/playwire") v.SetDefault("adapters.rhythmone.endpoint", "http://tag.1rx.io/rmp") v.SetDefault("adapters.rtbhouse.endpoint", "http://prebidserver-s2s-ams.creativecdn.com/bidder/prebidserver/bids") v.SetDefault("adapters.rubicon.endpoint", "http://exapi-us-east.rubiconproject.com/a/api/exchange.json") diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index 95f5b7f5882..8a4e2bc21f6 100644 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -60,6 +60,8 @@ import ( "github.com/prebid/prebid-server/adapters/yieldmo" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" + + "prebid-server/adapters/playwire_ortb" ) // The newAdapterMap function is segregated to its own file to make it a simple and clean location for each Adapter @@ -84,6 +86,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderDatablocks: datablocks.NewDatablocksBidder(cfg.Adapters[string(openrtb_ext.BidderDatablocks)].Endpoint), openrtb_ext.BidderEmxDigital: emx_digital.NewEmxDigitalBidder(cfg.Adapters[string(openrtb_ext.BidderEmxDigital)].Endpoint), openrtb_ext.BidderEngageBDR: engagebdr.NewEngageBDRBidder(client, cfg.Adapters[string(openrtb_ext.BidderEngageBDR)].Endpoint), + openrtb_ext.BidderEngageBDROrtb: playwire_ortb.NewOrtbBidder(cfg.Adapters[string(openrtb_ext.BidderEngageBDROrtb)].Endpoint), openrtb_ext.BidderEPlanning: eplanning.NewEPlanningBidder(client, cfg.Adapters[string(openrtb_ext.BidderEPlanning)].Endpoint), openrtb_ext.BidderFacebook: audienceNetwork.NewFacebookBidder( client, @@ -93,6 +96,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderGamoshi: gamoshi.NewGamoshiBidder(cfg.Adapters[string(openrtb_ext.BidderGamoshi)].Endpoint), openrtb_ext.BidderGrid: grid.NewGridBidder(cfg.Adapters[string(openrtb_ext.BidderGrid)].Endpoint), openrtb_ext.BidderGumGum: gumgum.NewGumGumBidder(cfg.Adapters[string(openrtb_ext.BidderGumGum)].Endpoint), + openrtb_ext.BidderGumGumOrtb: playwire_ortb.NewOrtbBidder(cfg.Adapters[string(openrtb_ext.BidderGumGumOrtb)].Endpoint), openrtb_ext.BidderImprovedigital: improvedigital.NewImprovedigitalBidder(cfg.Adapters[string(openrtb_ext.BidderImprovedigital)].Endpoint), openrtb_ext.BidderKubient: kubient.NewKubientBidder(cfg.Adapters[string(openrtb_ext.BidderKubient)].Endpoint), openrtb_ext.BidderLockerDome: lockerdome.NewLockerDomeBidder(cfg.Adapters[string(openrtb_ext.BidderLockerDome)].Endpoint), @@ -101,6 +105,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderOpenx: openx.NewOpenxBidder(cfg.Adapters[string(openrtb_ext.BidderOpenx)].Endpoint), openrtb_ext.BidderPubmatic: pubmatic.NewPubmaticBidder(client, cfg.Adapters[string(openrtb_ext.BidderPubmatic)].Endpoint), openrtb_ext.BidderPubnative: pubnative.NewPubnativeBidder(cfg.Adapters[string(openrtb_ext.BidderPubnative)].Endpoint), + openrtb_ext.BidderPulsepointOrtb: playwire_ortb.NewOrtbBidder(cfg.Adapters[string(openrtb_ext.BidderPulsepointOrtb)].Endpoint), openrtb_ext.BidderRhythmone: rhythmone.NewRhythmoneBidder(cfg.Adapters[string(openrtb_ext.BidderRhythmone)].Endpoint), openrtb_ext.BidderRTBHouse: rtbhouse.NewRTBHouseBidder(cfg.Adapters[string(openrtb_ext.BidderRTBHouse)].Endpoint), openrtb_ext.BidderRubicon: rubicon.NewRubiconBidder( diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 7a3f24eb07f..ab303fc7744 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -37,12 +37,14 @@ const ( BidderDatablocks BidderName = "datablocks" BidderEmxDigital BidderName = "emx_digital" BidderEngageBDR BidderName = "engagebdr" + BidderEngageBDROrtb BidderName = "engagebdr_ortb" BidderEPlanning BidderName = "eplanning" BidderFacebook BidderName = "audienceNetwork" BidderGamma BidderName = "gamma" BidderGamoshi BidderName = "gamoshi" BidderGrid BidderName = "grid" BidderGumGum BidderName = "gumgum" + BidderGumGumOrtb BidderName = "gumgum_ortb" BidderImprovedigital BidderName = "improvedigital" BidderIx BidderName = "ix" BidderKubient BidderName = "kubient" @@ -54,6 +56,7 @@ const ( BidderPubmatic BidderName = "pubmatic" BidderPubnative BidderName = "pubnative" BidderPulsepoint BidderName = "pulsepoint" + BidderPulsepointOrtb BidderName = "pulsepoint_ortb" BidderRhythmone BidderName = "rhythmone" BidderRTBHouse BidderName = "rtbhouse" BidderRubicon BidderName = "rubicon" @@ -92,12 +95,14 @@ var BidderMap = map[string]BidderName{ "datablocks": BidderDatablocks, "emx_digital": BidderEmxDigital, "engagebdr": BidderEngageBDR, + "engagebdr_ortb": BidderEngageBDROrtb, "eplanning": BidderEPlanning, "audienceNetwork": BidderFacebook, "gamma": BidderGamma, "gamoshi": BidderGamoshi, "grid": BidderGrid, "gumgum": BidderGumGum, + "gumgum_ortb": BidderGumGumOrtb, "improvedigital": BidderImprovedigital, "ix": BidderIx, "kubient": BidderKubient, @@ -109,6 +114,7 @@ var BidderMap = map[string]BidderName{ "pubmatic": BidderPubmatic, "pubnative": BidderPubnative, "pulsepoint": BidderPulsepoint, + "pulsepoint_ortb": BidderPulsepointOrtb, "rhythmone": BidderRhythmone, "rtbhouse": BidderRTBHouse, "rubicon": BidderRubicon, diff --git a/static/bidder-info/engagebdr_ortb.yaml b/static/bidder-info/engagebdr_ortb.yaml new file mode 100644 index 00000000000..764b49023df --- /dev/null +++ b/static/bidder-info/engagebdr_ortb.yaml @@ -0,0 +1,7 @@ +maintainer: + email: "pubtech@na.com" +capabilities: + site: + mediaTypes: + - banner + - video diff --git a/static/bidder-info/gumgum_ortb.yaml b/static/bidder-info/gumgum_ortb.yaml new file mode 100644 index 00000000000..764b49023df --- /dev/null +++ b/static/bidder-info/gumgum_ortb.yaml @@ -0,0 +1,7 @@ +maintainer: + email: "pubtech@na.com" +capabilities: + site: + mediaTypes: + - banner + - video diff --git a/static/bidder-info/pulsepoint_ortb.yaml b/static/bidder-info/pulsepoint_ortb.yaml new file mode 100644 index 00000000000..764b49023df --- /dev/null +++ b/static/bidder-info/pulsepoint_ortb.yaml @@ -0,0 +1,7 @@ +maintainer: + email: "pubtech@na.com" +capabilities: + site: + mediaTypes: + - banner + - video diff --git a/static/bidder-params/engagebdr_ortb.json b/static/bidder-params/engagebdr_ortb.json new file mode 100644 index 00000000000..acc2d5c221b --- /dev/null +++ b/static/bidder-params/engagebdr_ortb.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "GumGum Adapter Params", + "description": "A schema which validates params accepted by the GumGum adapter", + "type": "object", + "properties": { + + } +} diff --git a/static/bidder-params/gumgum_ortb.json b/static/bidder-params/gumgum_ortb.json new file mode 100644 index 00000000000..acc2d5c221b --- /dev/null +++ b/static/bidder-params/gumgum_ortb.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "GumGum Adapter Params", + "description": "A schema which validates params accepted by the GumGum adapter", + "type": "object", + "properties": { + + } +} diff --git a/static/bidder-params/pulsepoint_ortb.json b/static/bidder-params/pulsepoint_ortb.json new file mode 100644 index 00000000000..acc2d5c221b --- /dev/null +++ b/static/bidder-params/pulsepoint_ortb.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "GumGum Adapter Params", + "description": "A schema which validates params accepted by the GumGum adapter", + "type": "object", + "properties": { + + } +} From 08ab3760a3a75bda1b810e1fedbf3648b619224b Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Tue, 17 Nov 2020 16:27:19 -0500 Subject: [PATCH 035/125] update go mod to read adapter import --- adapters/playwire_ortb/go.mod | 1 + exchange/adapter_map.go | 2 +- go.mod | 3 +++ 3 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 adapters/playwire_ortb/go.mod diff --git a/adapters/playwire_ortb/go.mod b/adapters/playwire_ortb/go.mod new file mode 100644 index 00000000000..c96523beac1 --- /dev/null +++ b/adapters/playwire_ortb/go.mod @@ -0,0 +1 @@ +module github.com/prebid-server/adapters/playwire_ortb \ No newline at end of file diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index 8a4e2bc21f6..3dfd2ed9631 100644 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -61,7 +61,7 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" - "prebid-server/adapters/playwire_ortb" + "github.com/prebid-server/adapters/playwire_ortb" ) // The newAdapterMap function is segregated to its own file to make it a simple and clean location for each Adapter diff --git a/go.mod b/go.mod index cd9491aa6ec..d363f4cae9a 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,8 @@ go 1.12 // Magic comment that determines which Go version Heroku uses. // +heroku goVersion go1.12 +replace github.com/prebid-server/adapters/playwire_ortb => ./adapters/playwire_ortb/ + require ( github.com/BurntSushi/toml v0.3.1 // indirect github.com/DATA-DOG/go-sqlmock v1.3.0 @@ -35,6 +37,7 @@ require ( github.com/onsi/ginkgo v1.10.1 // indirect github.com/onsi/gomega v1.7.0 // indirect github.com/pelletier/go-toml v1.2.0 // indirect + github.com/prebid-server/adapters/playwire_ortb v0.0.0-00010101000000-000000000000 github.com/prebid/go-gdpr v0.6.0 github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 From 5731efa2c4464b9482a91014a1d401c9caaf39e7 Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Wed, 26 May 2021 08:36:59 -0400 Subject: [PATCH 036/125] update bidder params in stored requests --- .../stored_imps/content_top_android.json | 43 +++++++++++++++++++ .../by_id/stored_imps/content_top_ios.json | 8 +++- .../data/by_id/stored_imps/game_over_ios.json | 8 +++- .../by_id/stored_imps/in_article_ios.json | 8 +++- .../by_id/stored_imps/play_screen_ios.json | 8 +++- 5 files changed, 67 insertions(+), 8 deletions(-) create mode 100644 stored_requests/data/by_id/stored_imps/content_top_android.json diff --git a/stored_requests/data/by_id/stored_imps/content_top_android.json b/stored_requests/data/by_id/stored_imps/content_top_android.json new file mode 100644 index 00000000000..edf7f988489 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/content_top_android.json @@ -0,0 +1,43 @@ +{ + "id": "content_top_ios", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "appnexus": { + "placement_id": 19689655 + }, + "emx_digital": { + "tagid": "107133" + }, + "openx": { + "unit": "541169197", + "delDomain": "playwire-d.openx.net" + }, + "pubmatic": { + "publisherId": "158326", + "adSlot": "1957467" + }, + "rhythmone": { + "placementId": "213696", + "zone": "1r", + "path": "mvo" + }, + "rubicon": { + "accountId": 15526, + "siteId": 335554, + "zoneId": 1764488 + }, + "sonobi": { + "TagID": "12eb86143abb80d6a9c6" + }, + "oftmedia": { + "placementId": "19606578" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/content_top_ios.json b/stored_requests/data/by_id/stored_imps/content_top_ios.json index f57df9a7bf5..5582ccce573 100644 --- a/stored_requests/data/by_id/stored_imps/content_top_ios.json +++ b/stored_requests/data/by_id/stored_imps/content_top_ios.json @@ -20,10 +20,11 @@ "delDomain": "playwire-d.openx.net" }, "pubmatic": { - "publisherId": "158326" + "publisherId": "158326", + "adSlot": "1957464" }, "rhythmone": { - "placementId": "213224", + "placementId": "213696", "zone": "1r", "path": "mvo" }, @@ -34,6 +35,9 @@ }, "sonobi": { "TagID": "12eb86143abb80d6a9c6" + }, + "oftmedia": { + "placementId": "19606578" } } } \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/game_over_ios.json b/stored_requests/data/by_id/stored_imps/game_over_ios.json index 5177a12aa9a..ff960468c7d 100644 --- a/stored_requests/data/by_id/stored_imps/game_over_ios.json +++ b/stored_requests/data/by_id/stored_imps/game_over_ios.json @@ -24,10 +24,11 @@ "delDomain": "playwire-d.openx.net" }, "pubmatic": { - "publisherId": "158326" + "publisherId": "158326", + "adSlot": "1957464" }, "rhythmone": { - "placementId": "213224", + "placementId": "213696", "zone": "1r", "path": "mvo" }, @@ -38,6 +39,9 @@ }, "sonobi": { "TagID": "12eb86143abb80d6a9c6" + }, + "oftmedia": { + "placementId": "19606578" } } } \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/in_article_ios.json b/stored_requests/data/by_id/stored_imps/in_article_ios.json index 6e0a46f4950..49643218c70 100644 --- a/stored_requests/data/by_id/stored_imps/in_article_ios.json +++ b/stored_requests/data/by_id/stored_imps/in_article_ios.json @@ -24,10 +24,11 @@ "delDomain": "playwire-d.openx.net" }, "pubmatic": { - "publisherId": "158326" + "publisherId": "158326", + "adSlot": "1957464" }, "rhythmone": { - "placementId": "213224", + "placementId": "213696", "zone": "1r", "path": "mvo" }, @@ -38,6 +39,9 @@ }, "sonobi": { "TagID": "12eb86143abb80d6a9c6" + }, + "oftmedia": { + "placementId": "19606578" } } } \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/play_screen_ios.json b/stored_requests/data/by_id/stored_imps/play_screen_ios.json index 3208a74f50e..75bf799f43d 100644 --- a/stored_requests/data/by_id/stored_imps/play_screen_ios.json +++ b/stored_requests/data/by_id/stored_imps/play_screen_ios.json @@ -24,10 +24,11 @@ "delDomain": "playwire-d.openx.net" }, "pubmatic": { - "publisherId": "158326" + "publisherId": "158326", + "adSlot": "1957464" }, "rhythmone": { - "placementId": "213224", + "placementId": "213696", "zone": "1r", "path": "mvo" }, @@ -38,6 +39,9 @@ }, "sonobi": { "TagID": "12eb86143abb80d6a9c6" + }, + "oftmedia": { + "placementId": "19606578" } } } \ No newline at end of file From fa093d2f537f7e039b5127524869c3d2c73840d9 Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Wed, 26 May 2021 08:51:06 -0400 Subject: [PATCH 037/125] oftmedia as int, store ext data --- .../stored_imps/content_top_android.json | 2 +- .../by_id/stored_imps/content_top_ios.json | 2 +- .../data/by_id/stored_imps/game_over_ios.json | 2 +- .../by_id/stored_imps/in_article_ios.json | 2 +- .../by_id/stored_imps/play_screen_ios.json | 2 +- .../data/by_id/stored_requests/playwire.json | 28 +++++++++++++++++-- 6 files changed, 31 insertions(+), 7 deletions(-) diff --git a/stored_requests/data/by_id/stored_imps/content_top_android.json b/stored_requests/data/by_id/stored_imps/content_top_android.json index edf7f988489..67cc417d456 100644 --- a/stored_requests/data/by_id/stored_imps/content_top_android.json +++ b/stored_requests/data/by_id/stored_imps/content_top_android.json @@ -37,7 +37,7 @@ "TagID": "12eb86143abb80d6a9c6" }, "oftmedia": { - "placementId": "19606578" + "placementId": 19606578 } } } \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/content_top_ios.json b/stored_requests/data/by_id/stored_imps/content_top_ios.json index 5582ccce573..c87159ef3f5 100644 --- a/stored_requests/data/by_id/stored_imps/content_top_ios.json +++ b/stored_requests/data/by_id/stored_imps/content_top_ios.json @@ -37,7 +37,7 @@ "TagID": "12eb86143abb80d6a9c6" }, "oftmedia": { - "placementId": "19606578" + "placementId": 19606578 } } } \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/game_over_ios.json b/stored_requests/data/by_id/stored_imps/game_over_ios.json index ff960468c7d..39b82459cc3 100644 --- a/stored_requests/data/by_id/stored_imps/game_over_ios.json +++ b/stored_requests/data/by_id/stored_imps/game_over_ios.json @@ -41,7 +41,7 @@ "TagID": "12eb86143abb80d6a9c6" }, "oftmedia": { - "placementId": "19606578" + "placementId": 19606578 } } } \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/in_article_ios.json b/stored_requests/data/by_id/stored_imps/in_article_ios.json index 49643218c70..e05b5be4ea3 100644 --- a/stored_requests/data/by_id/stored_imps/in_article_ios.json +++ b/stored_requests/data/by_id/stored_imps/in_article_ios.json @@ -41,7 +41,7 @@ "TagID": "12eb86143abb80d6a9c6" }, "oftmedia": { - "placementId": "19606578" + "placementId": 19606578 } } } \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/play_screen_ios.json b/stored_requests/data/by_id/stored_imps/play_screen_ios.json index 75bf799f43d..c893b6efe79 100644 --- a/stored_requests/data/by_id/stored_imps/play_screen_ios.json +++ b/stored_requests/data/by_id/stored_imps/play_screen_ios.json @@ -41,7 +41,7 @@ "TagID": "12eb86143abb80d6a9c6" }, "oftmedia": { - "placementId": "19606578" + "placementId": 19606578 } } } \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_requests/playwire.json b/stored_requests/data/by_id/stored_requests/playwire.json index 263efbe2eff..351529ad6e2 100644 --- a/stored_requests/data/by_id/stored_requests/playwire.json +++ b/stored_requests/data/by_id/stored_requests/playwire.json @@ -1,3 +1,27 @@ { - "tmax": 1000 -} \ No newline at end of file + "tmax": 1000, + "ext": { + "prebid": { + "targeting": { + "includewinners": true, + "includebidderkeys": false, + "mediatypepricegranularity": { + "banner": { + "ranges": [ + { + "max": 50, + "min": 0, + "increment": 0.01 + } + ], + "precision": 3 + } + } + }, + "cache": { + "bids": {} + }, + "aliases": { "oftmedia": "appnexus" } + } + } +} From 850fd2f8c4f78a47263c540ee6fb1f49b53f9ec2 Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Wed, 26 May 2021 10:05:40 -0400 Subject: [PATCH 038/125] fix id --- stored_requests/data/by_id/stored_imps/content_top_android.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stored_requests/data/by_id/stored_imps/content_top_android.json b/stored_requests/data/by_id/stored_imps/content_top_android.json index 67cc417d456..36fae36e735 100644 --- a/stored_requests/data/by_id/stored_imps/content_top_android.json +++ b/stored_requests/data/by_id/stored_imps/content_top_android.json @@ -1,5 +1,5 @@ { - "id": "content_top_ios", + "id": "content_top_android", "banner": { "format": [ { From e4774f69ea3f6a0b13bd1e1a33e6edaa0c67ba5e Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Fri, 28 May 2021 09:41:47 -0400 Subject: [PATCH 039/125] files for our adatper --- adapters/playwire/playwire.go | 108 ++++++++++++++++++ openrtb_ext/bidders.go | 2 + static/bidder-info/playwire.yaml | 7 ++ static/bidder-params/playwire.json | 8 ++ .../by_id/stored_imps/content_top_ios.json | 6 +- .../data/by_id/stored_requests/playwire.json | 3 + 6 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 adapters/playwire/playwire.go create mode 100644 static/bidder-info/playwire.yaml create mode 100644 static/bidder-params/playwire.json diff --git a/adapters/playwire/playwire.go b/adapters/playwire/playwire.go new file mode 100644 index 00000000000..93e4484a7ab --- /dev/null +++ b/adapters/playwire/playwire.go @@ -0,0 +1,108 @@ +package playwire + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type PlaywireAdapter struct { + endpoint string +} + +// MakeRequests makes the HTTP requests to fetch bids. +func (a *PlaywireAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo * adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var errors = make([]error, 0) + + reqJSON, err := json.Marshal(request) + if err != nil { + errors = append(errors, err) + return nil, errors + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + + return []*adapters.RequestData{{ + Method: "POST", + Uri: a.endpoint, + Body: reqJSON, + Headers: headers, + }}, errors +} + +// MakeBids unpacks the server's response into Bids. +func (a *PlaywireAdapter) MakeBids(internalRequest * openrtb.BidRequest, externalRequest, *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + + if response.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Unexpected status code: %d, Run with request.debug = 1 for more info", response.StatusCode), + }} + } + + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), + }} + } + + var bidResp openrtb.BidderResponse + if err := json.UnMarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + bidResposne := adapters.NewBidderResponseWithBidsCapacity(1) + + for _, sb := range bidResp.SeatBid { + for i := range sb.Bid { + bidType, err := getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp) + if err != nil { + return nil, []error{err} + } + + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &sb.Bid[1], + BidType: bidType + }) + } + } + return bidResponse, nil +} + +// NewPlaywireBidder configure bidder endpoint +func NewPlaywireBidder(endpoint string) *PlaywireAdapter { + return &PlaywireAdapter{ + endpoint: endpoint, + } +} + +func getMediaTypeForImp(impID string, imps []openrtb.Imp) (openrtb_ext.BidType, error) { + for _, imp := range imps { + if imp.ID == impID { + if imp.Banner != nil { + return openrtb_ext.BidTypeBanner, nil + } + + if imp.Video != nil { + return openrtb_ext.BidTypeVideo, nil + } + + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unknown impression type for ID: \"%s\"", impID), + } + } + } + + // This shouldnt happen. Lets handle it just incase by returning an error. + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Failed to find impression for ID: \"%s\"", impID), + } +} \ No newline at end of file diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index ab303fc7744..4317f6cd2d7 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -53,6 +53,7 @@ const ( BidderMarsmedia BidderName = "marsmedia" BidderMgid BidderName = "mgid" BidderOpenx BidderName = "openx" + BidderPlaywire BidderName = "playwire" BidderPubmatic BidderName = "pubmatic" BidderPubnative BidderName = "pubnative" BidderPulsepoint BidderName = "pulsepoint" @@ -111,6 +112,7 @@ var BidderMap = map[string]BidderName{ "marsmedia": BidderMarsmedia, "mgid": BidderMgid, "openx": BidderOpenx, + "playwire": BidderPlaywire, "pubmatic": BidderPubmatic, "pubnative": BidderPubnative, "pulsepoint": BidderPulsepoint, diff --git a/static/bidder-info/playwire.yaml b/static/bidder-info/playwire.yaml new file mode 100644 index 00000000000..96944508d26 --- /dev/null +++ b/static/bidder-info/playwire.yaml @@ -0,0 +1,7 @@ +maintainer: + email: "na@playwire.com" +capabilities: + site: + mediaTypes: + - banner + - video diff --git a/static/bidder-params/playwire.json b/static/bidder-params/playwire.json new file mode 100644 index 00000000000..ebd2bbefeae --- /dev/null +++ b/static/bidder-params/playwire.json @@ -0,0 +1,8 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Playwire Adapter Params", + "description": "A schema which validates params accepted by Playwire adapter", + "type": "object", + "properties": {}, + "required": [] +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/content_top_ios.json b/stored_requests/data/by_id/stored_imps/content_top_ios.json index c87159ef3f5..301e5f80fd2 100644 --- a/stored_requests/data/by_id/stored_imps/content_top_ios.json +++ b/stored_requests/data/by_id/stored_imps/content_top_ios.json @@ -3,8 +3,8 @@ "banner": { "format": [ { - "w": 320, - "h": 50 + "w": 300, + "h": 250 } ] }, @@ -21,7 +21,7 @@ }, "pubmatic": { "publisherId": "158326", - "adSlot": "1957464" + "adSlot": "3029490" }, "rhythmone": { "placementId": "213696", diff --git a/stored_requests/data/by_id/stored_requests/playwire.json b/stored_requests/data/by_id/stored_requests/playwire.json index 351529ad6e2..9c5eb1aadf7 100644 --- a/stored_requests/data/by_id/stored_requests/playwire.json +++ b/stored_requests/data/by_id/stored_requests/playwire.json @@ -1,5 +1,8 @@ { "tmax": 1000, + "cur": [ + "USD" + ], "ext": { "prebid": { "targeting": { From 5ea0de6f02f3eabc3a796d61ff27082f31890a3c Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Fri, 28 May 2021 09:46:53 -0400 Subject: [PATCH 040/125] import adapter and add endpoint --- config/config.go | 1 + exchange/adapter_map.go | 2 ++ 2 files changed, 3 insertions(+) diff --git a/config/config.go b/config/config.go index b2c527cc0a8..79730fe5f77 100644 --- a/config/config.go +++ b/config/config.go @@ -698,6 +698,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.marsmedia.endpoint", "https://bid306.rtbsrv.com/bidder/?bid=f3xtet") v.SetDefault("adapters.mgid.endpoint", "https://prebid.mgid.com/prebid/") v.SetDefault("adapters.openx.endpoint", "http://rtb.openx.net/prebid") + v.SetDefault("adapters.playwire.endpoint", "http://grid.bidswitch.net/sp_bid?sp=prebid") v.SetDefault("adapters.pubmatic.endpoint", "https://hbopenbid.pubmatic.com/translator?source=prebid-server") v.SetDefault("adapters.pubnative.endpoint", "http://dsp.pubnative.net/bid/v1/request") v.SetDefault("adapters.pulsepoint.endpoint", "http://bid.contextweb.com/header/s/ortb/prebid-s2s") diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index 3dfd2ed9631..3051d1a8af1 100644 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -38,6 +38,7 @@ import ( "github.com/prebid/prebid-server/adapters/marsmedia" "github.com/prebid/prebid-server/adapters/mgid" "github.com/prebid/prebid-server/adapters/openx" + "github.com/prebid/prebid-server/adapters/playwire" "github.com/prebid/prebid-server/adapters/pubmatic" "github.com/prebid/prebid-server/adapters/pubnative" "github.com/prebid/prebid-server/adapters/pulsepoint" @@ -103,6 +104,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderMarsmedia: marsmedia.NewMarsmediaBidder(cfg.Adapters[string(openrtb_ext.BidderMarsmedia)].Endpoint), openrtb_ext.BidderMgid: mgid.NewMgidBidder(cfg.Adapters[string(openrtb_ext.BidderMgid)].Endpoint), openrtb_ext.BidderOpenx: openx.NewOpenxBidder(cfg.Adapters[string(openrtb_ext.BidderOpenx)].Endpoint), + openrtb_ext.BidderPlaywire: playwire.NewPlaywireBidder(cfg.Adapters[string(openrtb_ext.BidderPlaywire)].Endpoint), openrtb_ext.BidderPubmatic: pubmatic.NewPubmaticBidder(client, cfg.Adapters[string(openrtb_ext.BidderPubmatic)].Endpoint), openrtb_ext.BidderPubnative: pubnative.NewPubnativeBidder(cfg.Adapters[string(openrtb_ext.BidderPubnative)].Endpoint), openrtb_ext.BidderPulsepointOrtb: playwire_ortb.NewOrtbBidder(cfg.Adapters[string(openrtb_ext.BidderPulsepointOrtb)].Endpoint), From 3e8e40548554a7d33acd3d0015f86c499f562568 Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Fri, 28 May 2021 09:54:51 -0400 Subject: [PATCH 041/125] fix typos --- adapters/playwire/playwire.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/adapters/playwire/playwire.go b/adapters/playwire/playwire.go index 93e4484a7ab..544fbec6910 100644 --- a/adapters/playwire/playwire.go +++ b/adapters/playwire/playwire.go @@ -37,7 +37,7 @@ func (a *PlaywireAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo * ad } // MakeBids unpacks the server's response into Bids. -func (a *PlaywireAdapter) MakeBids(internalRequest * openrtb.BidRequest, externalRequest, *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *PlaywireAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -54,12 +54,12 @@ func (a *PlaywireAdapter) MakeBids(internalRequest * openrtb.BidRequest, externa }} } - var bidResp openrtb.BidderResponse - if err := json.UnMarshal(response.Body, &bidResp); err != nil { + var bidResp openrtb.BidResponse + if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } - bidResposne := adapters.NewBidderResponseWithBidsCapacity(1) + bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) for _, sb := range bidResp.SeatBid { for i := range sb.Bid { @@ -69,8 +69,8 @@ func (a *PlaywireAdapter) MakeBids(internalRequest * openrtb.BidRequest, externa } bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ - Bid: &sb.Bid[1], - BidType: bidType + Bid: &sb.Bid[i], + BidType: bidType, }) } } From 1a946ede46b427f27e9959898354ec4fb8c55f66 Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Fri, 28 May 2021 11:01:46 -0400 Subject: [PATCH 042/125] add sizes to stored requests --- .../data/by_id/stored_imps/content_top_android.json | 4 ++++ stored_requests/data/by_id/stored_imps/content_top_ios.json | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/stored_requests/data/by_id/stored_imps/content_top_android.json b/stored_requests/data/by_id/stored_imps/content_top_android.json index 36fae36e735..8ca6dcc6d39 100644 --- a/stored_requests/data/by_id/stored_imps/content_top_android.json +++ b/stored_requests/data/by_id/stored_imps/content_top_android.json @@ -5,6 +5,10 @@ { "w": 320, "h": 50 + }, + { + "w": 300, + "h": 250 } ] }, diff --git a/stored_requests/data/by_id/stored_imps/content_top_ios.json b/stored_requests/data/by_id/stored_imps/content_top_ios.json index 301e5f80fd2..4df75274d33 100644 --- a/stored_requests/data/by_id/stored_imps/content_top_ios.json +++ b/stored_requests/data/by_id/stored_imps/content_top_ios.json @@ -2,6 +2,10 @@ "id": "content_top_ios", "banner": { "format": [ + { + "w": 320, + "h": 50 + }, { "w": 300, "h": 250 From 3f53cf0b97759f94cddedf86b80ca8b6923c6329 Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Wed, 23 Jun 2021 19:43:12 -0400 Subject: [PATCH 043/125] whitelabeling grid names --- adapters/playwire/playwire.go | 185 ++++++++++++++++++ adapters/playwire/playwire_test.go | 20 ++ .../playwiretest/exemplary/simple-banner.json | 87 ++++++++ .../playwiretest/exemplary/simple-video.json | 87 ++++++++ .../playwiretest/exemplary/with_gpid.json | 100 ++++++++++ .../playwiretest/params/race/banner.json | 4 + .../supplemental/bad_bidder_request.json | 33 ++++ .../supplemental/bad_ext_request.json | 30 +++ .../supplemental/bad_response.json | 63 ++++++ .../supplemental/empty_uid_request.json | 33 ++++ .../supplemental/no_imp_request.json | 13 ++ .../playwiretest/supplemental/status_204.json | 58 ++++++ .../playwiretest/supplemental/status_400.json | 63 ++++++ .../playwiretest/supplemental/status_418.json | 63 ++++++ adapters/playwire/usersync.go | 12 ++ adapters/playwire/usersync_test.go | 29 +++ config/config.go | 2 + exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_playwire.go | 6 + static/bidder-info/playwire.yaml | 12 ++ static/bidder-params/playwire.json | 13 ++ usersync/usersyncers/syncer.go | 2 + 23 files changed, 919 insertions(+) create mode 100644 adapters/playwire/playwire.go create mode 100644 adapters/playwire/playwire_test.go create mode 100644 adapters/playwire/playwiretest/exemplary/simple-banner.json create mode 100644 adapters/playwire/playwiretest/exemplary/simple-video.json create mode 100644 adapters/playwire/playwiretest/exemplary/with_gpid.json create mode 100644 adapters/playwire/playwiretest/params/race/banner.json create mode 100644 adapters/playwire/playwiretest/supplemental/bad_bidder_request.json create mode 100644 adapters/playwire/playwiretest/supplemental/bad_ext_request.json create mode 100644 adapters/playwire/playwiretest/supplemental/bad_response.json create mode 100644 adapters/playwire/playwiretest/supplemental/empty_uid_request.json create mode 100644 adapters/playwire/playwiretest/supplemental/no_imp_request.json create mode 100644 adapters/playwire/playwiretest/supplemental/status_204.json create mode 100644 adapters/playwire/playwiretest/supplemental/status_400.json create mode 100644 adapters/playwire/playwiretest/supplemental/status_418.json create mode 100644 adapters/playwire/usersync.go create mode 100644 adapters/playwire/usersync_test.go create mode 100644 openrtb_ext/imp_playwire.go create mode 100644 static/bidder-info/playwire.yaml create mode 100644 static/bidder-params/playwire.json diff --git a/adapters/playwire/playwire.go b/adapters/playwire/playwire.go new file mode 100644 index 00000000000..f0ec7817da2 --- /dev/null +++ b/adapters/playwire/playwire.go @@ -0,0 +1,185 @@ +package playwire + +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 PlaywireAdapter struct { + endpoint string +} + +type ExtImpDataAdServer struct { + Name string `json:"name"` + AdSlot string `json:"adslot"` +} + +type ExtImpData struct { + PbAdslot string `json:"pbadslot,omitempty"` + AdServer *ExtImpDataAdServer `json:"adserver,omitempty"` +} + +type ExtImp struct { + Prebid *openrtb_ext.ExtImpPrebid `json:"prebid,omitempty"` + Bidder json.RawMessage `json:"bidder"` + Data *ExtImpData `json:"data,omitempty"` + Gpid string `json:"gpid,omitempty"` +} + +func processImp(imp *openrtb2.Imp) error { + // get the playwire extension + var ext adapters.ExtImpBidder + var playwireExt openrtb_ext.ExtImpPlaywire + if err := json.Unmarshal(imp.Ext, &ext); err != nil { + return err + } + if err := json.Unmarshal(ext.Bidder, &playwireExt); err != nil { + return err + } + + if playwireExt.Uid == 0 { + err := &errortypes.BadInput{ + Message: "uid is empty", + } + return err + } + // no error + return nil +} + +func setImpExtData(imp openrtb2.Imp) openrtb2.Imp { + var ext ExtImp + if err := json.Unmarshal(imp.Ext, &ext); err != nil { + return imp + } + if ext.Data != nil && ext.Data.AdServer != nil && ext.Data.AdServer.AdSlot != "" { + ext.Gpid = ext.Data.AdServer.AdSlot + extJSON, err := json.Marshal(ext) + if err == nil { + imp.Ext = extJSON + } + } + return imp +} + +// MakeRequests makes the HTTP requests which should be made to fetch bids. +func (a *PlaywireAdapter) 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 { + if err := processImp(&imp); err == nil { + validImps = append(validImps, setImpExtData(imp)) + } else { + errors = append(errors, err) + } + } + if len(validImps) == 0 { + err := &errortypes.BadInput{ + Message: "No valid impressions for playwire", + } + errors = append(errors, err) + return nil, errors + } + requestCopy.Imp = validImps + + reqJSON, err := json.Marshal(requestCopy) + if err != nil { + errors = append(errors, err) + return nil, errors + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + + return []*adapters.RequestData{{ + Method: "POST", + Uri: a.endpoint, + Body: reqJSON, + Headers: headers, + }}, errors +} + +// MakeBids unpacks the server's response into Bids. +func (a *PlaywireAdapter) 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 bidResp openrtb2.BidResponse + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) + + for _, sb := range bidResp.SeatBid { + for i := range sb.Bid { + bidType, err := getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp) + if err != nil { + return nil, []error{err} + } + + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &sb.Bid[i], + BidType: bidType, + }) + } + } + return bidResponse, nil + +} + +// Builder builds a new instance of the Playwire adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &PlaywireAdapter{ + endpoint: config.Endpoint, + } + return bidder, 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 + } + + if imp.Video != nil { + return openrtb_ext.BidTypeVideo, nil + } + + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unknown impression type for ID: \"%s\"", impID), + } + } + } + + // This shouldnt happen. Lets handle it just incase by returning an error. + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Failed to find impression for ID: \"%s\"", impID), + } +} diff --git a/adapters/playwire/playwire_test.go b/adapters/playwire/playwire_test.go new file mode 100644 index 00000000000..e2f6f1990d6 --- /dev/null +++ b/adapters/playwire/playwire_test.go @@ -0,0 +1,20 @@ +package playwire + +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.BidderPlaywire, config.Adapter{ + Endpoint: "http://localhost/prebid"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "playwiretest", bidder) +} diff --git a/adapters/playwire/playwiretest/exemplary/simple-banner.json b/adapters/playwire/playwiretest/exemplary/simple-banner.json new file mode 100644 index 00000000000..a1603fd4b6c --- /dev/null +++ b/adapters/playwire/playwiretest/exemplary/simple-banner.json @@ -0,0 +1,87 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "uid": 1 + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "uid": 1 + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "seat": "playwire", + "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/playwire/playwiretest/exemplary/simple-video.json b/adapters/playwire/playwiretest/exemplary/simple-video.json new file mode 100644 index 00000000000..fc15013a8c0 --- /dev/null +++ b/adapters/playwire/playwiretest/exemplary/simple-video.json @@ -0,0 +1,87 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "uid": 1 + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "uid": 1 + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "seat": "playwire", + "bid": [{ + "id": "randomid", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "12345678", + "adm": "some-test-ad-vast", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300 + }] + }], + "cur": "USD" + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "randomid", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad-vast", + "adid": "12345678", + "cid": "987", + "crid": "12345678", + "w": 300, + "h": 250 + }, + "type": "video" + }] + }] +} diff --git a/adapters/playwire/playwiretest/exemplary/with_gpid.json b/adapters/playwire/playwiretest/exemplary/with_gpid.json new file mode 100644 index 00000000000..386f827cf99 --- /dev/null +++ b/adapters/playwire/playwiretest/exemplary/with_gpid.json @@ -0,0 +1,100 @@ +{ + "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 + }, + "data": { + "adserver": { + "name": "some_name", + "adslot": "some_slot" + } + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "uid": 1 + }, + "data": { + "adserver": { + "name": "some_name", + "adslot": "some_slot" + } + }, + "gpid": "some_slot" + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "seat": "playwire", + "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/playwire/playwiretest/params/race/banner.json b/adapters/playwire/playwiretest/params/race/banner.json new file mode 100644 index 00000000000..7e347f11b45 --- /dev/null +++ b/adapters/playwire/playwiretest/params/race/banner.json @@ -0,0 +1,4 @@ +{ + "uid": 1 +} + diff --git a/adapters/playwire/playwiretest/supplemental/bad_bidder_request.json b/adapters/playwire/playwiretest/supplemental/bad_bidder_request.json new file mode 100644 index 00000000000..cb9c9333070 --- /dev/null +++ b/adapters/playwire/playwiretest/supplemental/bad_bidder_request.json @@ -0,0 +1,33 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": "some not exist" + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb_ext.ExtImpPlaywire", + "comparison": "literal" + }, + { + "value": "No valid impressions for playwire", + "comparison": "literal" + } + ], + "httpCalls":[], + "expectedBidResponses": [] +} diff --git a/adapters/playwire/playwiretest/supplemental/bad_ext_request.json b/adapters/playwire/playwiretest/supplemental/bad_ext_request.json new file mode 100644 index 00000000000..76cce111948 --- /dev/null +++ b/adapters/playwire/playwiretest/supplemental/bad_ext_request.json @@ -0,0 +1,30 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": "any" + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type adapters.ExtImpBidder", + "comparison": "literal" + }, + { + "value": "No valid impressions for playwire", + "comparison": "literal" + } + ], + "httpCalls": [] +} diff --git a/adapters/playwire/playwiretest/supplemental/bad_response.json b/adapters/playwire/playwiretest/supplemental/bad_response.json new file mode 100644 index 00000000000..a9d38368ab2 --- /dev/null +++ b/adapters/playwire/playwiretest/supplemental/bad_response.json @@ -0,0 +1,63 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "uid": 1 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "uid": 1 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": "{\"id\"data.lost" + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "comparison": "literal" + } + ] +} diff --git a/adapters/playwire/playwiretest/supplemental/empty_uid_request.json b/adapters/playwire/playwiretest/supplemental/empty_uid_request.json new file mode 100644 index 00000000000..e6b242942e9 --- /dev/null +++ b/adapters/playwire/playwiretest/supplemental/empty_uid_request.json @@ -0,0 +1,33 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": {} + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "uid is empty", + "comparison": "literal" + }, + { + "value": "No valid impressions for playwire", + "comparison": "literal" + } + ], + "httpCalls":[], + "expectedBidResponses": [] +} diff --git a/adapters/playwire/playwiretest/supplemental/no_imp_request.json b/adapters/playwire/playwiretest/supplemental/no_imp_request.json new file mode 100644 index 00000000000..c12686f7328 --- /dev/null +++ b/adapters/playwire/playwiretest/supplemental/no_imp_request.json @@ -0,0 +1,13 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [] + }, + "expectedMakeRequestsErrors": [ + { + "value": "No valid impressions for playwire", + "comparison": "literal" + } + ], + "httpCalls":[] +} diff --git a/adapters/playwire/playwiretest/supplemental/status_204.json b/adapters/playwire/playwiretest/supplemental/status_204.json new file mode 100644 index 00000000000..f935cbe85ae --- /dev/null +++ b/adapters/playwire/playwiretest/supplemental/status_204.json @@ -0,0 +1,58 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "uid": 1 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "uid": 1 + } + } + } + ] + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + + "expectedBidResponses": [] +} diff --git a/adapters/playwire/playwiretest/supplemental/status_400.json b/adapters/playwire/playwiretest/supplemental/status_400.json new file mode 100644 index 00000000000..629b1c07bd7 --- /dev/null +++ b/adapters/playwire/playwiretest/supplemental/status_400.json @@ -0,0 +1,63 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "uid": 1 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "uid": 1 + } + } + } + ] + } + }, + "mockResponse": { + "status": 400, + "body": {} + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/playwire/playwiretest/supplemental/status_418.json b/adapters/playwire/playwiretest/supplemental/status_418.json new file mode 100644 index 00000000000..0ca365c76ce --- /dev/null +++ b/adapters/playwire/playwiretest/supplemental/status_418.json @@ -0,0 +1,63 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "uid": 1 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "uid": 1 + } + } + } + ] + } + }, + "mockResponse": { + "status": 418, + "body": {} + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 418. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/playwire/usersync.go b/adapters/playwire/usersync.go new file mode 100644 index 00000000000..76cf9dc0329 --- /dev/null +++ b/adapters/playwire/usersync.go @@ -0,0 +1,12 @@ +package playwire + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewPlaywireSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("playwire", temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/playwire/usersync_test.go b/adapters/playwire/usersync_test.go new file mode 100644 index 00000000000..3006daf0b76 --- /dev/null +++ b/adapters/playwire/usersync_test.go @@ -0,0 +1,29 @@ +package playwire + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestPlaywireSyncer(t *testing.T) { + syncURL := "//not_localhost/synclocalhost%2Fsetuid%3Fbidder%3Dplaywire%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewPlaywireSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "0", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "//not_localhost/synclocalhost%2Fsetuid%3Fbidder%3Dplaywire%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/config/config.go b/config/config.go index 58bb40a592c..d0154f49113 100644 --- a/config/config.go +++ b/config/config.go @@ -627,6 +627,7 @@ func (cfg *Configuration) setDerivedDefaults() { 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.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.BidderPlaywire, "https://x.bidswitch.net/check_uuid/"+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dplaywire%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.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") @@ -892,6 +893,7 @@ func SetupViper(v *viper.Viper, filename string) { 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) + v.SetDefault("adapters.playwire.endpoint", "https://grid.bidswitch.net/sp_bid?sp=playwire") v.SetDefault("adapters.pubmatic.endpoint", "https://hbopenbid.pubmatic.com/translator?source=prebid-server") v.SetDefault("adapters.pubnative.endpoint", "http://dsp.pubnative.net/bid/v1/request") v.SetDefault("adapters.pulsepoint.endpoint", "http://bid.contextweb.com/header/s/ortb/prebid-s2s") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 1fdb7c2489d..f66f643c4f5 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -81,6 +81,7 @@ import ( "github.com/prebid/prebid-server/adapters/orbidder" "github.com/prebid/prebid-server/adapters/outbrain" "github.com/prebid/prebid-server/adapters/pangle" + "github.com/prebid/prebid-server/adapters/playwire" "github.com/prebid/prebid-server/adapters/pubmatic" "github.com/prebid/prebid-server/adapters/pubnative" "github.com/prebid/prebid-server/adapters/pulsepoint" @@ -203,6 +204,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderOrbidder: orbidder.Builder, openrtb_ext.BidderOutbrain: outbrain.Builder, openrtb_ext.BidderPangle: pangle.Builder, + openrtb_ext.BidderPlaywire: playwire.Builder, openrtb_ext.BidderPubmatic: pubmatic.Builder, openrtb_ext.BidderPubnative: pubnative.Builder, openrtb_ext.BidderPulsepoint: pulsepoint.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 91ce9f0d27f..92b9c0a8ce5 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -153,6 +153,7 @@ const ( BidderOrbidder BidderName = "orbidder" BidderOutbrain BidderName = "outbrain" BidderPangle BidderName = "pangle" + BidderPlaywire BidderName = "playwire" BidderPubmatic BidderName = "pubmatic" BidderPubnative BidderName = "pubnative" BidderPulsepoint BidderName = "pulsepoint" @@ -274,6 +275,7 @@ func CoreBidderNames() []BidderName { BidderOrbidder, BidderOutbrain, BidderPangle, + BidderPlaywire, BidderPubmatic, BidderPubnative, BidderPulsepoint, diff --git a/openrtb_ext/imp_playwire.go b/openrtb_ext/imp_playwire.go new file mode 100644 index 00000000000..4c1e6b36268 --- /dev/null +++ b/openrtb_ext/imp_playwire.go @@ -0,0 +1,6 @@ +package openrtb_ext + +// ExtImpPlaywire defines the contract for bidrequest.imp[i].ext.playwire +type ExtImpPlaywire struct { + Uid int `json:"uid"` +} diff --git a/static/bidder-info/playwire.yaml b/static/bidder-info/playwire.yaml new file mode 100644 index 00000000000..bdf5a83b27c --- /dev/null +++ b/static/bidder-info/playwire.yaml @@ -0,0 +1,12 @@ +maintainer: + email: "na@playwire.com" +gvlVendorID: 686 +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video \ No newline at end of file diff --git a/static/bidder-params/playwire.json b/static/bidder-params/playwire.json new file mode 100644 index 00000000000..dd4b9be09a7 --- /dev/null +++ b/static/bidder-params/playwire.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Playwire Adapter Params", + "description": "A schema which validates params accepted by the Playwire adapter", + "type": "object", + "properties": { + "uid": { + "type": "integer", + "description": "An ID which identifies this placement of the impression" + } + }, + "required": [] +} diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 169417e07e8..d9259fdaf97 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -64,6 +64,7 @@ import ( "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/playwire" "github.com/prebid/prebid-server/adapters/pubmatic" "github.com/prebid/prebid-server/adapters/pulsepoint" "github.com/prebid/prebid-server/adapters/rhythmone" @@ -163,6 +164,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.BidderPlaywire, playwire.NewPlaywireSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderPubmatic, pubmatic.NewPubmaticSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderPulsepoint, pulsepoint.NewPulsepointSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderRhythmone, rhythmone.NewRhythmoneSyncer) From 959d5428483466c291338f3b6fd5072f38fcacb3 Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Thu, 24 Jun 2021 11:21:27 -0400 Subject: [PATCH 044/125] fix endpoint --- config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index d0154f49113..19c8004f694 100644 --- a/config/config.go +++ b/config/config.go @@ -893,7 +893,7 @@ func SetupViper(v *viper.Viper, filename string) { 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) - v.SetDefault("adapters.playwire.endpoint", "https://grid.bidswitch.net/sp_bid?sp=playwire") + v.SetDefault("adapters.playwire.endpoint", "https://grid.bidswitch.net/sp_bid?sp=prebid") v.SetDefault("adapters.pubmatic.endpoint", "https://hbopenbid.pubmatic.com/translator?source=prebid-server") v.SetDefault("adapters.pubnative.endpoint", "http://dsp.pubnative.net/bid/v1/request") v.SetDefault("adapters.pulsepoint.endpoint", "http://bid.contextweb.com/header/s/ortb/prebid-s2s") From defd2df586f251eeb45723d9eafc2704f678135a Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Thu, 8 Jul 2021 11:35:09 -0400 Subject: [PATCH 045/125] remove _ortb for now --- config/config.go | 9 +++------ static/bidder-info/engagebdr_ortb.yaml | 7 ------- static/bidder-info/gumgum_ortb.yaml | 7 ------- static/bidder-info/pulsepoint_ortb.yaml | 7 ------- static/bidder-params/engagebdr_ortb.json | 9 --------- static/bidder-params/gumgum_ortb.json | 9 --------- static/bidder-params/pulsepoint_ortb.json | 9 --------- 7 files changed, 3 insertions(+), 54 deletions(-) delete mode 100644 static/bidder-info/engagebdr_ortb.yaml delete mode 100644 static/bidder-info/gumgum_ortb.yaml delete mode 100644 static/bidder-info/pulsepoint_ortb.yaml delete mode 100644 static/bidder-params/engagebdr_ortb.json delete mode 100644 static/bidder-params/gumgum_ortb.json delete mode 100644 static/bidder-params/pulsepoint_ortb.json diff --git a/config/config.go b/config/config.go index f4a3177e365..1213115639c 100644 --- a/config/config.go +++ b/config/config.go @@ -858,8 +858,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.dmx.endpoint", "https://dmx-direct.districtm.io/b/v2") v.SetDefault("adapters.emx_digital.endpoint", "https://hb.emxdgt.com") v.SetDefault("adapters.engagebdr.endpoint", "http://dsp.bnmla.com/hb") - v.SetDefault("adapters.engagebdr_ortb.endpoint", "https://dsp.bnmla.com/bid?sspid=1000204") - v.SetDefault("adapters.engagebdr_ortb.disabled", true) + // v.SetDefault("adapters.engagebdr_ortb.endpoint", "https://dsp.bnmla.com/bid?sspid=1000204") 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) @@ -868,8 +867,7 @@ 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.gumgum_ortb.endpoint", "https://g2.gumgum.com/zones/8ylgv2wd/bid") - v.SetDefault("adapters.gumgum_ortb.disabled", true) + // v.SetDefault("adapters.gumgum_ortb.endpoint", "https://g2.gumgum.com/zones/8ylgv2wd/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") @@ -901,8 +899,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.pubmatic.endpoint", "https://hbopenbid.pubmatic.com/translator?source=prebid-server") v.SetDefault("adapters.pubnative.endpoint", "http://dsp.pubnative.net/bid/v1/request") v.SetDefault("adapters.pulsepoint.endpoint", "http://bid.contextweb.com/header/s/ortb/prebid-s2s") - v.SetDefault("adapters.pulsepoint_ortb.endpoint", "http://rts.lga.contextweb.com/rt-seller/bid/openrtb/playwire") - v.SetDefault("adapters.pulsepoint_ortb.disabled", true) + // v.SetDefault("adapters.pulsepoint_ortb.endpoint", "http://rts.lga.contextweb.com/rt-seller/bid/openrtb/playwire") 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") diff --git a/static/bidder-info/engagebdr_ortb.yaml b/static/bidder-info/engagebdr_ortb.yaml deleted file mode 100644 index 764b49023df..00000000000 --- a/static/bidder-info/engagebdr_ortb.yaml +++ /dev/null @@ -1,7 +0,0 @@ -maintainer: - email: "pubtech@na.com" -capabilities: - site: - mediaTypes: - - banner - - video diff --git a/static/bidder-info/gumgum_ortb.yaml b/static/bidder-info/gumgum_ortb.yaml deleted file mode 100644 index 764b49023df..00000000000 --- a/static/bidder-info/gumgum_ortb.yaml +++ /dev/null @@ -1,7 +0,0 @@ -maintainer: - email: "pubtech@na.com" -capabilities: - site: - mediaTypes: - - banner - - video diff --git a/static/bidder-info/pulsepoint_ortb.yaml b/static/bidder-info/pulsepoint_ortb.yaml deleted file mode 100644 index 764b49023df..00000000000 --- a/static/bidder-info/pulsepoint_ortb.yaml +++ /dev/null @@ -1,7 +0,0 @@ -maintainer: - email: "pubtech@na.com" -capabilities: - site: - mediaTypes: - - banner - - video diff --git a/static/bidder-params/engagebdr_ortb.json b/static/bidder-params/engagebdr_ortb.json deleted file mode 100644 index acc2d5c221b..00000000000 --- a/static/bidder-params/engagebdr_ortb.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "GumGum Adapter Params", - "description": "A schema which validates params accepted by the GumGum adapter", - "type": "object", - "properties": { - - } -} diff --git a/static/bidder-params/gumgum_ortb.json b/static/bidder-params/gumgum_ortb.json deleted file mode 100644 index acc2d5c221b..00000000000 --- a/static/bidder-params/gumgum_ortb.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "GumGum Adapter Params", - "description": "A schema which validates params accepted by the GumGum adapter", - "type": "object", - "properties": { - - } -} diff --git a/static/bidder-params/pulsepoint_ortb.json b/static/bidder-params/pulsepoint_ortb.json deleted file mode 100644 index acc2d5c221b..00000000000 --- a/static/bidder-params/pulsepoint_ortb.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "GumGum Adapter Params", - "description": "A schema which validates params accepted by the GumGum adapter", - "type": "object", - "properties": { - - } -} From 90eaebfab8163f6c93a2dde355a92995b720e256 Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Thu, 8 Jul 2021 12:41:53 -0400 Subject: [PATCH 046/125] add _ortb bidders back --- adapters/playwire_ortb/playwire_ortb.go | 330 ++++++++++++---------- adapters/playwire_ortb/usersync.go | 2 +- config/config.go | 6 +- exchange/adapter_builders.go | 6 +- openrtb_ext/bidders.go | 6 + static/bidder-info/engagebdr_ortb.yaml | 16 ++ static/bidder-info/gumgum_ortb.yaml | 16 ++ static/bidder-info/pulsepoint_ortb.yaml | 16 ++ static/bidder-params/engagebdr_ortb.json | 13 + static/bidder-params/gumgum_ortb.json | 13 + static/bidder-params/pulsepoint_ortb.json | 13 + 11 files changed, 279 insertions(+), 158 deletions(-) create mode 100644 static/bidder-info/engagebdr_ortb.yaml create mode 100644 static/bidder-info/gumgum_ortb.yaml create mode 100644 static/bidder-info/pulsepoint_ortb.yaml create mode 100644 static/bidder-params/engagebdr_ortb.json create mode 100644 static/bidder-params/gumgum_ortb.json create mode 100644 static/bidder-params/pulsepoint_ortb.json diff --git a/adapters/playwire_ortb/playwire_ortb.go b/adapters/playwire_ortb/playwire_ortb.go index befb679cc34..a1b7bacbac3 100644 --- a/adapters/playwire_ortb/playwire_ortb.go +++ b/adapters/playwire_ortb/playwire_ortb.go @@ -1,172 +1,196 @@ package playwire_ortb import ( - "encoding/json" - "fmt" - "github.com/mxmCherry/openrtb" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - "net/http" - "strconv" - "strings" + "encoding/json" + "fmt" + "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" ) -// PlaywireORTBAdapter implements Bidder interface. -type PlaywireORTBAdapter struct { - URI string +// PlaywireOrtbAdapter implements Bidder interface. +type PlaywireOrtbAdapter struct { + URI string } // MakeRequests makes the HTTP requests which should be made to fetch bids. -func (g *PlaywireORTBAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - var validImps []openrtb.Imp - var trackingId string - - numRequests := len(request.Imp) - errs := make([]error, 0, numRequests) - - for i := 0; i < numRequests; i++ { - imp := request.Imp[i] - zone, err := preprocess(&imp) - if err != nil { - errs = append(errs, err) - } else if request.Imp[i].Banner != nil { - bannerCopy := *request.Imp[i].Banner - if bannerCopy.W == nil && bannerCopy.H == nil && len(bannerCopy.Format) > 0 { - format := bannerCopy.Format[0] - bannerCopy.W = &(format.W) - bannerCopy.H = &(format.H) - } - request.Imp[i].Banner = &bannerCopy - validImps = append(validImps, request.Imp[i]) - trackingId = zone - } else if request.Imp[i].Video != nil { - err := validateVideoParams(request.Imp[i].Video) - if err != nil { - errs = append(errs, err) - } else { - validImps = append(validImps, request.Imp[i]) - trackingId = zone - } - } - } - - if len(validImps) == 0 { - return nil, errs - } - - request.Imp = validImps - - if request.Site != nil { - siteCopy := *request.Site - siteCopy.ID = trackingId - request.Site = &siteCopy - } - - reqJSON, err := json.Marshal(request) - if err != nil { - errs = append(errs, err) - return nil, errs - } - - headers := http.Header{} - headers.Add("Content-Type", "application/json;charset=utf-8") - headers.Add("Accept", "application/json") - return []*adapters.RequestData{{ - Method: "POST", - Uri: g.URI, - Body: reqJSON, - Headers: headers, - }}, errs +func (g *PlaywireOrtbAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var validImps []openrtb2.Imp + var siteCopy openrtb2.Site + if request.Site != nil { + siteCopy = *request.Site + } + + numRequests := len(request.Imp) + errs := make([]error, 0, numRequests) + + for i := 0; i < numRequests; i++ { + imp := request.Imp[i] + playwire_ortbExt, err := preprocess(&imp) + if err != nil { + errs = append(errs, err) + } else { + if playwire_ortbExt.Zone != "" { + siteCopy.ID = playwire_ortbExt.Zone + } + + if playwire_ortbExt.PubID != 0 { + if siteCopy.Publisher != nil { + siteCopy.Publisher.ID = strconv.FormatFloat(playwire_ortbExt.PubID, 'f', -1, 64) + } else { + siteCopy.Publisher = &openrtb2.Publisher{ID: strconv.FormatFloat(playwire_ortbExt.PubID, 'f', -1, 64)} + } + } + + validImps = append(validImps, imp) + } + } + + if len(validImps) == 0 { + return nil, errs + } + + request.Imp = validImps + + if request.Site != nil { + request.Site = &siteCopy + } + + reqJSON, err := json.Marshal(request) + if err != nil { + errs = append(errs, err) + return nil, errs + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + return []*adapters.RequestData{{ + Method: "POST", + Uri: g.URI, + Body: reqJSON, + Headers: headers, + }}, errs } // MakeBids unpacks the server's response into Bids. -func (g *PlaywireORTBAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { - if response.StatusCode == http.StatusNoContent { - return nil, nil - } - - if response.StatusCode == http.StatusBadRequest { - return nil, []error{&errortypes.BadInput{ - Message: fmt.Sprintf("Bad user input: HTTP status %d", response.StatusCode), - }} - } - - if response.StatusCode != http.StatusOK { - return nil, []error{&errortypes.BadServerResponse{ - Message: fmt.Sprintf("Bad server response: HTTP status %d", response.StatusCode), - }} - } - var bidResp openrtb.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { - return nil, []error{&errortypes.BadServerResponse{ - Message: fmt.Sprintf("Bad server response: %d. ", err), - }} - } - - var errs []error - bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) - - for _, sb := range bidResp.SeatBid { - for i := range sb.Bid { - mediaType := getMediaTypeForImpID(sb.Bid[i].ImpID, internalRequest.Imp) - if mediaType == openrtb_ext.BidTypeVideo { - price := strconv.FormatFloat(sb.Bid[i].Price, 'f', -1, 64) - sb.Bid[i].AdM = strings.Replace(sb.Bid[i].AdM, "${AUCTION_PRICE}", price, -1) - } - - bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ - Bid: &sb.Bid[i], - BidType: mediaType, - }) - } - } - - return bidResponse, errs +func (g *PlaywireOrtbAdapter) 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", response.StatusCode), + }} + } + + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Bad server response: HTTP status %d", 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: %d. ", err), + }} + } + + var errs []error + bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) + + for _, sb := range bidResp.SeatBid { + for i := range sb.Bid { + mediaType := getMediaTypeForImpID(sb.Bid[i].ImpID, internalRequest.Imp) + if mediaType == openrtb_ext.BidTypeVideo { + price := strconv.FormatFloat(sb.Bid[i].Price, 'f', -1, 64) + sb.Bid[i].AdM = strings.Replace(sb.Bid[i].AdM, "${AUCTION_PRICE}", price, -1) + } + + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &sb.Bid[i], + BidType: mediaType, + }) + } + } + + return bidResponse, errs } -func preprocess(imp *openrtb.Imp) (string, error) { - var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { - err = &errortypes.BadInput{ - Message: err.Error(), - } - return "", err - } - - var gumgumExt openrtb_ext.ExtImpGumGum - if err := json.Unmarshal(bidderExt.Bidder, &gumgumExt); err != nil { - err = &errortypes.BadInput{ - Message: err.Error(), - } - return "", err - } - - zone := gumgumExt.Zone - return zone, nil +func preprocess(imp *openrtb2.Imp) (*openrtb_ext.ExtImpGumGum, error) { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + err = &errortypes.BadInput{ + Message: err.Error(), + } + return nil, err + } + + var playwire_ortbExt openrtb_ext.ExtImpGumGum + if err := json.Unmarshal(bidderExt.Bidder, &playwire_ortbExt); err != nil { + err = &errortypes.BadInput{ + Message: err.Error(), + } + return nil, err + } + + if imp.Banner != nil && imp.Banner.W == nil && imp.Banner.H == nil && len(imp.Banner.Format) > 0 { + bannerCopy := *imp.Banner + format := bannerCopy.Format[0] + bannerCopy.W = &(format.W) + bannerCopy.H = &(format.H) + imp.Banner = &bannerCopy + } + + if imp.Video != nil { + err := validateVideoParams(imp.Video) + if err != nil { + return nil, err + } + + if playwire_ortbExt.IrisID != "" { + videoCopy := *imp.Video + videoExt := openrtb_ext.ExtImpGumGumVideo{IrisID: playwire_ortbExt.IrisID} + videoCopy.Ext, err = json.Marshal(&videoExt) + if err != nil { + return nil, err + } + imp.Video = &videoCopy + } + } + + return &playwire_ortbExt, nil } -func getMediaTypeForImpID(impID string, imps []openrtb.Imp) openrtb_ext.BidType { - for _, imp := range imps { - if imp.ID == impID && imp.Banner != nil { - return openrtb_ext.BidTypeBanner - } - } - return openrtb_ext.BidTypeVideo +func getMediaTypeForImpID(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { + for _, imp := range imps { + if imp.ID == impID && imp.Banner != nil { + return openrtb_ext.BidTypeBanner + } + } + return openrtb_ext.BidTypeVideo } -func validateVideoParams(video *openrtb.Video) (err error) { - // if video.W == 0 || video.H == 0 || video.MinDuration == 0 || video.MaxDuration == 0 || video.Placement == 0 || video.Linearity == 0 { - // return &errortypes.BadInput{ - // Message: "Invalid or missing video field(s)", - // } - // } - return nil +func validateVideoParams(video *openrtb2.Video) (err error) { + if video.W == 0 || video.H == 0 || video.MinDuration == 0 || video.MaxDuration == 0 || video.Placement == 0 || video.Linearity == 0 { + return &errortypes.BadInput{ + Message: "Invalid or missing video field(s)", + } + } + return nil } -// NewOrtbBidder configures bidder endpoint. -func NewOrtbBidder(endpoint string) *PlaywireORTBAdapter { - return &PlaywireORTBAdapter{ - URI: endpoint, - } -} \ No newline at end of file +// Builder builds a new instance of the PlaywireOrtb adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &PlaywireOrtbAdapter{ + URI: config.Endpoint, + } + return bidder, nil +} diff --git a/adapters/playwire_ortb/usersync.go b/adapters/playwire_ortb/usersync.go index 5c9b1463e16..dd82b7280b5 100644 --- a/adapters/playwire_ortb/usersync.go +++ b/adapters/playwire_ortb/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewGumGumSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("gumgum", 61, temp, adapters.SyncTypeIframe) + return adapters.NewSyncer("gumgum", temp, adapters.SyncTypeIframe) } diff --git a/config/config.go b/config/config.go index 1213115639c..fe86af70fe2 100644 --- a/config/config.go +++ b/config/config.go @@ -858,7 +858,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.dmx.endpoint", "https://dmx-direct.districtm.io/b/v2") v.SetDefault("adapters.emx_digital.endpoint", "https://hb.emxdgt.com") v.SetDefault("adapters.engagebdr.endpoint", "http://dsp.bnmla.com/hb") - // v.SetDefault("adapters.engagebdr_ortb.endpoint", "https://dsp.bnmla.com/bid?sspid=1000204") + v.SetDefault("adapters.engagebdr_ortb.endpoint", "https://dsp.bnmla.com/bid?sspid=1000204") 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) @@ -867,7 +867,7 @@ 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.gumgum_ortb.endpoint", "https://g2.gumgum.com/zones/8ylgv2wd/bid") + v.SetDefault("adapters.gumgum_ortb.endpoint", "https://g2.gumgum.com/zones/8ylgv2wd/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") @@ -899,7 +899,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.pubmatic.endpoint", "https://hbopenbid.pubmatic.com/translator?source=prebid-server") v.SetDefault("adapters.pubnative.endpoint", "http://dsp.pubnative.net/bid/v1/request") v.SetDefault("adapters.pulsepoint.endpoint", "http://bid.contextweb.com/header/s/ortb/prebid-s2s") - // v.SetDefault("adapters.pulsepoint_ortb.endpoint", "http://rts.lga.contextweb.com/rt-seller/bid/openrtb/playwire") + v.SetDefault("adapters.pulsepoint_ortb.endpoint", "http://rts.lga.contextweb.com/rt-seller/bid/openrtb/playwire") 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") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index f66f643c4f5..232fb335fff 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -1,6 +1,7 @@ package exchange import ( + "github.com/prebid-server/adapters/playwire_ortb" "github.com/prebid/prebid-server/adapters" ttx "github.com/prebid/prebid-server/adapters/33across" "github.com/prebid/prebid-server/adapters/acuityads" @@ -46,7 +47,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" @@ -170,6 +171,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderDmx: dmx.Builder, openrtb_ext.BidderEmxDigital: emx_digital.Builder, openrtb_ext.BidderEngageBDR: engagebdr.Builder, + openrtb_ext.BidderEngageBDROrtb: playwire_ortb.Builder, openrtb_ext.BidderEPlanning: eplanning.Builder, openrtb_ext.BidderEpom: epom.Builder, openrtb_ext.BidderEVolution: evolution.Builder, @@ -177,6 +179,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.BidderGumGumOrtb: playwire_ortb.Builder, openrtb_ext.BidderImprovedigital: improvedigital.Builder, openrtb_ext.BidderInMobi: inmobi.Builder, openrtb_ext.BidderInteractiveoffers: interactiveoffers.Builder, @@ -208,6 +211,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderPubmatic: pubmatic.Builder, openrtb_ext.BidderPubnative: pubnative.Builder, openrtb_ext.BidderPulsepoint: pulsepoint.Builder, + openrtb_ext.BidderPulsepointOrtb: playwire_ortb.Builder, openrtb_ext.BidderRevcontent: revcontent.Builder, openrtb_ext.BidderRhythmone: rhythmone.Builder, openrtb_ext.BidderRTBHouse: rtbhouse.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 92b9c0a8ce5..6381eb54ea2 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -119,6 +119,7 @@ const ( BidderDeepintent BidderName = "deepintent" BidderEmxDigital BidderName = "emx_digital" BidderEngageBDR BidderName = "engagebdr" + BidderEngageBDROrtb BidderName = "engagebdr_ortb" BidderEPlanning BidderName = "eplanning" BidderEpom BidderName = "epom" BidderEVolution BidderName = "e_volution" @@ -126,6 +127,7 @@ const ( BidderGamoshi BidderName = "gamoshi" BidderGrid BidderName = "grid" BidderGumGum BidderName = "gumgum" + BidderGumGumOrtb BidderName = "gumgum_ortb" BidderImprovedigital BidderName = "improvedigital" BidderInMobi BidderName = "inmobi" BidderInteractiveoffers BidderName = "interactiveoffers" @@ -157,6 +159,7 @@ const ( BidderPubmatic BidderName = "pubmatic" BidderPubnative BidderName = "pubnative" BidderPulsepoint BidderName = "pulsepoint" + BidderPulsepointOrtb BidderName = "pulsepoint_ortb" BidderRevcontent BidderName = "revcontent" BidderRhythmone BidderName = "rhythmone" BidderRTBHouse BidderName = "rtbhouse" @@ -241,6 +244,7 @@ func CoreBidderNames() []BidderName { BidderDmx, BidderEmxDigital, BidderEngageBDR, + BidderEngageBDROrtb, BidderEPlanning, BidderEpom, BidderEVolution, @@ -248,6 +252,7 @@ func CoreBidderNames() []BidderName { BidderGamoshi, BidderGrid, BidderGumGum, + BidderGumGumOrtb, BidderImprovedigital, BidderInMobi, BidderInteractiveoffers, @@ -279,6 +284,7 @@ func CoreBidderNames() []BidderName { BidderPubmatic, BidderPubnative, BidderPulsepoint, + BidderPulsepointOrtb, BidderRevcontent, BidderRhythmone, BidderRTBHouse, diff --git a/static/bidder-info/engagebdr_ortb.yaml b/static/bidder-info/engagebdr_ortb.yaml new file mode 100644 index 00000000000..fd2074f9d63 --- /dev/null +++ b/static/bidder-info/engagebdr_ortb.yaml @@ -0,0 +1,16 @@ +maintainer: + email: "na@playwire.com" +gvlVendorID: 62 +capabilities: + app: + mediaTypes: + - banner + - video + - audio + - native + site: + mediaTypes: + - banner + - video + - audio + - native diff --git a/static/bidder-info/gumgum_ortb.yaml b/static/bidder-info/gumgum_ortb.yaml new file mode 100644 index 00000000000..fd2074f9d63 --- /dev/null +++ b/static/bidder-info/gumgum_ortb.yaml @@ -0,0 +1,16 @@ +maintainer: + email: "na@playwire.com" +gvlVendorID: 62 +capabilities: + app: + mediaTypes: + - banner + - video + - audio + - native + site: + mediaTypes: + - banner + - video + - audio + - native diff --git a/static/bidder-info/pulsepoint_ortb.yaml b/static/bidder-info/pulsepoint_ortb.yaml new file mode 100644 index 00000000000..fd2074f9d63 --- /dev/null +++ b/static/bidder-info/pulsepoint_ortb.yaml @@ -0,0 +1,16 @@ +maintainer: + email: "na@playwire.com" +gvlVendorID: 62 +capabilities: + app: + mediaTypes: + - banner + - video + - audio + - native + site: + mediaTypes: + - banner + - video + - audio + - native diff --git a/static/bidder-params/engagebdr_ortb.json b/static/bidder-params/engagebdr_ortb.json new file mode 100644 index 00000000000..8849dc474dc --- /dev/null +++ b/static/bidder-params/engagebdr_ortb.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Playwire ORTB Adapter Params", + "description": "A schema which validates params accepted by the Playwire ORTB adapter", + "type": "object", + "properties": { + "sspid": { + "type": "string", + "description": "SSPID parameter", + "pattern": "^[0-9]+$" + } + } +} diff --git a/static/bidder-params/gumgum_ortb.json b/static/bidder-params/gumgum_ortb.json new file mode 100644 index 00000000000..8849dc474dc --- /dev/null +++ b/static/bidder-params/gumgum_ortb.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Playwire ORTB Adapter Params", + "description": "A schema which validates params accepted by the Playwire ORTB adapter", + "type": "object", + "properties": { + "sspid": { + "type": "string", + "description": "SSPID parameter", + "pattern": "^[0-9]+$" + } + } +} diff --git a/static/bidder-params/pulsepoint_ortb.json b/static/bidder-params/pulsepoint_ortb.json new file mode 100644 index 00000000000..8849dc474dc --- /dev/null +++ b/static/bidder-params/pulsepoint_ortb.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Playwire ORTB Adapter Params", + "description": "A schema which validates params accepted by the Playwire ORTB adapter", + "type": "object", + "properties": { + "sspid": { + "type": "string", + "description": "SSPID parameter", + "pattern": "^[0-9]+$" + } + } +} From 26142373fdc519ae5037b4bd3b987806cc3a3394 Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Thu, 8 Jul 2021 13:25:04 -0400 Subject: [PATCH 047/125] update go version to use --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 2c322023302..b50e817b15a 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/prebid/prebid-server go 1.16 // Magic comment that determines which Go version Heroku uses. -// +heroku goVersion go1.12 +// +heroku goVersion go1.16 replace github.com/prebid-server/adapters/playwire_ortb => ./adapters/playwire_ortb/ From 5d0e4416e9f35e526d88af5ad59776a070607d32 Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Wed, 14 Jul 2021 10:01:35 -0400 Subject: [PATCH 048/125] spaces to tabs --- adapters/playwire_ortb/params_test.go | 73 ++++++++++++++------------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/adapters/playwire_ortb/params_test.go b/adapters/playwire_ortb/params_test.go index 4adfafbdc7f..48cab6c73ee 100644 --- a/adapters/playwire_ortb/params_test.go +++ b/adapters/playwire_ortb/params_test.go @@ -1,52 +1,53 @@ package playwire_ortb import ( - "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" - "testing" + "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.BidderGumGum, json.RawMessage(validParam)); err != nil { - t.Errorf("Schema rejected gumgum params: %s", validParam) - } - } + 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.BidderGumGum, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected gumgum 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) - } - } + 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{ - `{"zone":"dc9d6be1"}`, + `{"zone":"dc9d6be1"}`, } var invalidParams = []string{ - `null`, - `nil`, - ``, - `{}`, - `[]`, - `true`, - `2`, - `{"zone":12345678}`, - `{"zone":""}`, - `{"placementId": 1}`, - `{"zone": true}`, - `{"placementId": 1, "zone":"1234567"}`, + `null`, + `nil`, + ``, + `{}`, + `[]`, + `true`, + `2`, + `{"zone":12345678}`, + `{"zone":""}`, + `{"placementId": 1}`, + `{"zone": true}`, + `{"placementId": 1, "zone":"1234567"}`, } From 7582f771cf02d83bce47dc51463ba590ca60dd8b Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Thu, 22 Jul 2021 15:07:13 -0400 Subject: [PATCH 049/125] update ad unit names and sizes --- .../stored_imps/content_top_android.json | 4 -- ...nt_top_ios.json => game_over_android.json} | 12 ++--- .../data/by_id/stored_imps/game_over_ios.json | 8 ++-- .../by_id/stored_imps/in_article_android.json | 47 +++++++++++++++++++ .../by_id/stored_imps/in_article_ios.json | 8 ++-- .../by_id/stored_imps/play_screen_ios.json | 4 -- 6 files changed, 61 insertions(+), 22 deletions(-) rename stored_requests/data/by_id/stored_imps/{content_top_ios.json => game_over_android.json} (93%) create mode 100644 stored_requests/data/by_id/stored_imps/in_article_android.json diff --git a/stored_requests/data/by_id/stored_imps/content_top_android.json b/stored_requests/data/by_id/stored_imps/content_top_android.json index 8ca6dcc6d39..36fae36e735 100644 --- a/stored_requests/data/by_id/stored_imps/content_top_android.json +++ b/stored_requests/data/by_id/stored_imps/content_top_android.json @@ -5,10 +5,6 @@ { "w": 320, "h": 50 - }, - { - "w": 300, - "h": 250 } ] }, diff --git a/stored_requests/data/by_id/stored_imps/content_top_ios.json b/stored_requests/data/by_id/stored_imps/game_over_android.json similarity index 93% rename from stored_requests/data/by_id/stored_imps/content_top_ios.json rename to stored_requests/data/by_id/stored_imps/game_over_android.json index 4df75274d33..b4ccfbdfa18 100644 --- a/stored_requests/data/by_id/stored_imps/content_top_ios.json +++ b/stored_requests/data/by_id/stored_imps/game_over_android.json @@ -1,14 +1,14 @@ { - "id": "content_top_ios", + "id": "game_over_android", "banner": { "format": [ - { - "w": 320, - "h": 50 - }, { "w": 300, "h": 250 + }, + { + "w": 320, + "h": 50 } ] }, @@ -25,7 +25,7 @@ }, "pubmatic": { "publisherId": "158326", - "adSlot": "3029490" + "adSlot": "1957467" }, "rhythmone": { "placementId": "213696", diff --git a/stored_requests/data/by_id/stored_imps/game_over_ios.json b/stored_requests/data/by_id/stored_imps/game_over_ios.json index 39b82459cc3..2cd3aff8262 100644 --- a/stored_requests/data/by_id/stored_imps/game_over_ios.json +++ b/stored_requests/data/by_id/stored_imps/game_over_ios.json @@ -2,13 +2,13 @@ "id": "game_over_ios", "banner": { "format": [ - { - "w": 320, - "h": 50 - }, { "w": 300, "h": 250 + }, + { + "w": 320, + "h": 50 } ] }, diff --git a/stored_requests/data/by_id/stored_imps/in_article_android.json b/stored_requests/data/by_id/stored_imps/in_article_android.json new file mode 100644 index 00000000000..3f0ca3d138f --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/in_article_android.json @@ -0,0 +1,47 @@ +{ + "id": "in_article_android", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "appnexus": { + "placement_id": 19689655 + }, + "emx_digital": { + "tagid": "107133" + }, + "openx": { + "unit": "541169197", + "delDomain": "playwire-d.openx.net" + }, + "pubmatic": { + "publisherId": "158326", + "adSlot": "1957467" + }, + "rhythmone": { + "placementId": "213696", + "zone": "1r", + "path": "mvo" + }, + "rubicon": { + "accountId": 15526, + "siteId": 335554, + "zoneId": 1764488 + }, + "sonobi": { + "TagID": "12eb86143abb80d6a9c6" + }, + "oftmedia": { + "placementId": 19606578 + } + } +} diff --git a/stored_requests/data/by_id/stored_imps/in_article_ios.json b/stored_requests/data/by_id/stored_imps/in_article_ios.json index e05b5be4ea3..56989f02fc8 100644 --- a/stored_requests/data/by_id/stored_imps/in_article_ios.json +++ b/stored_requests/data/by_id/stored_imps/in_article_ios.json @@ -2,13 +2,13 @@ "id": "in_article_ios", "banner": { "format": [ - { - "w": 320, - "h": 50 - }, { "w": 300, "h": 250 + }, + { + "w": 320, + "h": 50 } ] }, diff --git a/stored_requests/data/by_id/stored_imps/play_screen_ios.json b/stored_requests/data/by_id/stored_imps/play_screen_ios.json index c893b6efe79..496024e0837 100644 --- a/stored_requests/data/by_id/stored_imps/play_screen_ios.json +++ b/stored_requests/data/by_id/stored_imps/play_screen_ios.json @@ -5,10 +5,6 @@ { "w": 320, "h": 50 - }, - { - "w": 300, - "h": 250 } ] }, From ed99528df8d0d12ad8323a171dc0833720ce3cd0 Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Thu, 22 Jul 2021 15:14:53 -0400 Subject: [PATCH 050/125] add trilelift bid params --- .../data/by_id/stored_imps/content_top_android.json | 3 +++ stored_requests/data/by_id/stored_imps/play_screen_ios.json | 3 +++ 2 files changed, 6 insertions(+) diff --git a/stored_requests/data/by_id/stored_imps/content_top_android.json b/stored_requests/data/by_id/stored_imps/content_top_android.json index 36fae36e735..a9ab92407fa 100644 --- a/stored_requests/data/by_id/stored_imps/content_top_android.json +++ b/stored_requests/data/by_id/stored_imps/content_top_android.json @@ -38,6 +38,9 @@ }, "oftmedia": { "placementId": 19606578 + }, + "triplelift": { + "inventoryCode": "pwm_android_hdx_pb" } } } \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/play_screen_ios.json b/stored_requests/data/by_id/stored_imps/play_screen_ios.json index 496024e0837..6fd0ac82f1d 100644 --- a/stored_requests/data/by_id/stored_imps/play_screen_ios.json +++ b/stored_requests/data/by_id/stored_imps/play_screen_ios.json @@ -38,6 +38,9 @@ }, "oftmedia": { "placementId": 19606578 + }, + "triplelift": { + "inventoryCode": "pwm_android_hdx_pb" } } } \ No newline at end of file From 2e2ac52dc4b70dd5743a67b9769ee7b25fdad4a8 Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Fri, 30 Jul 2021 10:27:07 -0400 Subject: [PATCH 051/125] fix ios play screen triplelift params --- stored_requests/data/by_id/stored_imps/play_screen_ios.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stored_requests/data/by_id/stored_imps/play_screen_ios.json b/stored_requests/data/by_id/stored_imps/play_screen_ios.json index 6fd0ac82f1d..dd4bc8cf1e0 100644 --- a/stored_requests/data/by_id/stored_imps/play_screen_ios.json +++ b/stored_requests/data/by_id/stored_imps/play_screen_ios.json @@ -40,7 +40,7 @@ "placementId": 19606578 }, "triplelift": { - "inventoryCode": "pwm_android_hdx_pb" + "inventoryCode": "pwm_ios_hdx_pb" } } } \ No newline at end of file From 2c1f701cbf9fe68ab24aafe1a989ab60cbf3f3e6 Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Mon, 2 Aug 2021 09:14:46 -0400 Subject: [PATCH 052/125] updated params for each bidder per size --- .../by_id/stored_imps/content_top_android.json | 18 +++++++++--------- .../by_id/stored_imps/game_over_android.json | 17 ++++++++++------- .../data/by_id/stored_imps/game_over_ios.json | 15 +++++++++------ .../by_id/stored_imps/in_article_android.json | 17 ++++++++++------- .../data/by_id/stored_imps/in_article_ios.json | 15 +++++++++------ .../by_id/stored_imps/play_screen_ios.json | 18 +++++++++--------- 6 files changed, 56 insertions(+), 44 deletions(-) diff --git a/stored_requests/data/by_id/stored_imps/content_top_android.json b/stored_requests/data/by_id/stored_imps/content_top_android.json index a9ab92407fa..c2b29e38b8b 100644 --- a/stored_requests/data/by_id/stored_imps/content_top_android.json +++ b/stored_requests/data/by_id/stored_imps/content_top_android.json @@ -15,13 +15,16 @@ "emx_digital": { "tagid": "107133" }, + "oftmedia": { + "placementId": 19606578 + }, "openx": { - "unit": "541169197", + "unit": "544086382", "delDomain": "playwire-d.openx.net" }, "pubmatic": { "publisherId": "158326", - "adSlot": "1957467" + "adSlot": "3029233" }, "rhythmone": { "placementId": "213696", @@ -29,15 +32,12 @@ "path": "mvo" }, "rubicon": { - "accountId": 15526, - "siteId": 335554, - "zoneId": 1764488 + "accountId": 12556, + "siteId": 376280, + "zoneId": 2061954 }, "sonobi": { - "TagID": "12eb86143abb80d6a9c6" - }, - "oftmedia": { - "placementId": 19606578 + "TagID": "5200294cfc91869f267d" }, "triplelift": { "inventoryCode": "pwm_android_hdx_pb" diff --git a/stored_requests/data/by_id/stored_imps/game_over_android.json b/stored_requests/data/by_id/stored_imps/game_over_android.json index b4ccfbdfa18..01237cb4348 100644 --- a/stored_requests/data/by_id/stored_imps/game_over_android.json +++ b/stored_requests/data/by_id/stored_imps/game_over_android.json @@ -19,8 +19,11 @@ "emx_digital": { "tagid": "107133" }, + "oftmedia": { + "placementId": 19606578 + }, "openx": { - "unit": "541169197", + "unit": "544086382", "delDomain": "playwire-d.openx.net" }, "pubmatic": { @@ -33,15 +36,15 @@ "path": "mvo" }, "rubicon": { - "accountId": 15526, - "siteId": 335554, - "zoneId": 1764488 + "accountId": 12556, + "siteId": 376280, + "zoneId": 2061954 }, "sonobi": { - "TagID": "12eb86143abb80d6a9c6" + "TagID": "e67f243a24590037a75c" }, - "oftmedia": { - "placementId": 19606578 + "triplelift": { + "inventoryCode": "pwm_android_320x50_300x250_pb" } } } \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/game_over_ios.json b/stored_requests/data/by_id/stored_imps/game_over_ios.json index 2cd3aff8262..d20dab17d39 100644 --- a/stored_requests/data/by_id/stored_imps/game_over_ios.json +++ b/stored_requests/data/by_id/stored_imps/game_over_ios.json @@ -19,8 +19,11 @@ "emx_digital": { "tagid": "105251" }, + "oftmedia": { + "placementId": 19606578 + }, "openx": { - "unit": "541169197", + "unit": "544086381", "delDomain": "playwire-d.openx.net" }, "pubmatic": { @@ -33,15 +36,15 @@ "path": "mvo" }, "rubicon": { - "accountId": 15526, - "siteId": 335554, - "zoneId": 1764488 + "accountId": 12556, + "siteId": 376280, + "zoneId": 2061954 }, "sonobi": { "TagID": "12eb86143abb80d6a9c6" }, - "oftmedia": { - "placementId": 19606578 + "triplelift": { + "inventoryCode": "pwm_ios_320x50_300x250_pb" } } } \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/in_article_android.json b/stored_requests/data/by_id/stored_imps/in_article_android.json index 3f0ca3d138f..a6106c6e399 100644 --- a/stored_requests/data/by_id/stored_imps/in_article_android.json +++ b/stored_requests/data/by_id/stored_imps/in_article_android.json @@ -19,8 +19,11 @@ "emx_digital": { "tagid": "107133" }, + "oftmedia": { + "placementId": 19606578 + }, "openx": { - "unit": "541169197", + "unit": "544086382", "delDomain": "playwire-d.openx.net" }, "pubmatic": { @@ -33,15 +36,15 @@ "path": "mvo" }, "rubicon": { - "accountId": 15526, - "siteId": 335554, - "zoneId": 1764488 + "accountId": 12556, + "siteId": 376280, + "zoneId": 2061954 }, "sonobi": { - "TagID": "12eb86143abb80d6a9c6" + "TagID": "e67f243a24590037a75c" }, - "oftmedia": { - "placementId": 19606578 + "triplelift": { + "inventoryCode": "pwm_android_320x50_300x250_pb" } } } diff --git a/stored_requests/data/by_id/stored_imps/in_article_ios.json b/stored_requests/data/by_id/stored_imps/in_article_ios.json index 56989f02fc8..2e6f110d14e 100644 --- a/stored_requests/data/by_id/stored_imps/in_article_ios.json +++ b/stored_requests/data/by_id/stored_imps/in_article_ios.json @@ -19,8 +19,11 @@ "emx_digital": { "tagid": "105251" }, + "oftmedia": { + "placementId": 19606578 + }, "openx": { - "unit": "541169197", + "unit": "544086381", "delDomain": "playwire-d.openx.net" }, "pubmatic": { @@ -33,15 +36,15 @@ "path": "mvo" }, "rubicon": { - "accountId": 15526, - "siteId": 335554, - "zoneId": 1764488 + "accountId": 12556, + "siteId": 376280, + "zoneId": 2061954 }, "sonobi": { "TagID": "12eb86143abb80d6a9c6" }, - "oftmedia": { - "placementId": 19606578 + "triplelift": { + "inventoryCode": "pwm_ios_320x50_300x250_pb" } } } \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/play_screen_ios.json b/stored_requests/data/by_id/stored_imps/play_screen_ios.json index dd4bc8cf1e0..047ce6ac612 100644 --- a/stored_requests/data/by_id/stored_imps/play_screen_ios.json +++ b/stored_requests/data/by_id/stored_imps/play_screen_ios.json @@ -15,13 +15,16 @@ "emx_digital": { "tagid": "105251" }, + "oftmedia": { + "placementId": 19606578 + }, "openx": { - "unit": "541169197", + "unit": "544086381", "delDomain": "playwire-d.openx.net" }, "pubmatic": { "publisherId": "158326", - "adSlot": "1957464" + "adSlot": "3029490" }, "rhythmone": { "placementId": "213696", @@ -29,15 +32,12 @@ "path": "mvo" }, "rubicon": { - "accountId": 15526, - "siteId": 335554, - "zoneId": 1764488 + "accountId": 12556, + "siteId": 376280, + "zoneId": 2061954 }, "sonobi": { - "TagID": "12eb86143abb80d6a9c6" - }, - "oftmedia": { - "placementId": 19606578 + "TagID": "cc1338cc8e2c6e7864dd" }, "triplelift": { "inventoryCode": "pwm_ios_hdx_pb" From 15a53537d06576406496316cc25003454aeab91d Mon Sep 17 00:00:00 2001 From: tcreamer1 Date: Mon, 30 Aug 2021 16:22:41 -0400 Subject: [PATCH 053/125] add test units --- .../stored_imps/content_top_android_test.json | 46 +++++++++++++++++ .../stored_imps/game_over_android_test.json | 50 +++++++++++++++++++ .../by_id/stored_imps/game_over_ios_test.json | 50 +++++++++++++++++++ .../stored_imps/in_article_android_test.json | 50 +++++++++++++++++++ .../stored_imps/in_article_ios_test.json | 50 +++++++++++++++++++ .../stored_imps/play_screen_ios_test.json | 46 +++++++++++++++++ 6 files changed, 292 insertions(+) create mode 100644 stored_requests/data/by_id/stored_imps/content_top_android_test.json create mode 100644 stored_requests/data/by_id/stored_imps/game_over_android_test.json create mode 100644 stored_requests/data/by_id/stored_imps/game_over_ios_test.json create mode 100644 stored_requests/data/by_id/stored_imps/in_article_android_test.json create mode 100644 stored_requests/data/by_id/stored_imps/in_article_ios_test.json create mode 100644 stored_requests/data/by_id/stored_imps/play_screen_ios_test.json diff --git a/stored_requests/data/by_id/stored_imps/content_top_android_test.json b/stored_requests/data/by_id/stored_imps/content_top_android_test.json new file mode 100644 index 00000000000..f736e71321f --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/content_top_android_test.json @@ -0,0 +1,46 @@ +{ + "id": "content_top_android", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "appnexus": { + "placement_id": 19689655 + }, + "emx_digital": { + "tagid": "107133" + }, + "oftmedia": { + "placementId": 19606578 + }, + "openx": { + "unit": "544086382", + "delDomain": "playwire-d.openx.net" + }, + "pubmatic": { + "publisherId": "158326", + "adSlot": "3029233" + }, + "rhythmone": { + "placementId": "213696", + "zone": "1r", + "path": "mvo" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2061954 + }, + "sonobi": { + "TagID": "5200294cfc91869f267d" + }, + "triplelift": { + "inventoryCode": "playwire_app_hdx_android_prebid_TEST" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/game_over_android_test.json b/stored_requests/data/by_id/stored_imps/game_over_android_test.json new file mode 100644 index 00000000000..3f3b7831473 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/game_over_android_test.json @@ -0,0 +1,50 @@ +{ + "id": "game_over_android", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "appnexus": { + "placement_id": 19689655 + }, + "emx_digital": { + "tagid": "107133" + }, + "oftmedia": { + "placementId": 19606578 + }, + "openx": { + "unit": "544086382", + "delDomain": "playwire-d.openx.net" + }, + "pubmatic": { + "publisherId": "158326", + "adSlot": "1957467" + }, + "rhythmone": { + "placementId": "213696", + "zone": "1r", + "path": "mvo" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2061954 + }, + "sonobi": { + "TagID": "e67f243a24590037a75c" + }, + "triplelift": { + "inventoryCode": "playwire_app_hdx_android_prebid_TEST" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/game_over_ios_test.json b/stored_requests/data/by_id/stored_imps/game_over_ios_test.json new file mode 100644 index 00000000000..e860c73b1d2 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/game_over_ios_test.json @@ -0,0 +1,50 @@ +{ + "id": "game_over_ios", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "appnexus": { + "placement_id": 19689655 + }, + "emx_digital": { + "tagid": "105251" + }, + "oftmedia": { + "placementId": 19606578 + }, + "openx": { + "unit": "544086381", + "delDomain": "playwire-d.openx.net" + }, + "pubmatic": { + "publisherId": "158326", + "adSlot": "1957464" + }, + "rhythmone": { + "placementId": "213696", + "zone": "1r", + "path": "mvo" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2061954 + }, + "sonobi": { + "TagID": "12eb86143abb80d6a9c6" + }, + "triplelift": { + "inventoryCode": "playwire_app_hdx_ios_prebid_TEST" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/in_article_android_test.json b/stored_requests/data/by_id/stored_imps/in_article_android_test.json new file mode 100644 index 00000000000..44cf0a7faf4 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/in_article_android_test.json @@ -0,0 +1,50 @@ +{ + "id": "in_article_android", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "appnexus": { + "placement_id": 19689655 + }, + "emx_digital": { + "tagid": "107133" + }, + "oftmedia": { + "placementId": 19606578 + }, + "openx": { + "unit": "544086382", + "delDomain": "playwire-d.openx.net" + }, + "pubmatic": { + "publisherId": "158326", + "adSlot": "1957467" + }, + "rhythmone": { + "placementId": "213696", + "zone": "1r", + "path": "mvo" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2061954 + }, + "sonobi": { + "TagID": "e67f243a24590037a75c" + }, + "triplelift": { + "inventoryCode": "playwire_app_hdx_android_prebid_TEST" + } + } +} diff --git a/stored_requests/data/by_id/stored_imps/in_article_ios_test.json b/stored_requests/data/by_id/stored_imps/in_article_ios_test.json new file mode 100644 index 00000000000..c127a8236a0 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/in_article_ios_test.json @@ -0,0 +1,50 @@ +{ + "id": "in_article_ios", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "appnexus": { + "placement_id": 19689654 + }, + "emx_digital": { + "tagid": "105251" + }, + "oftmedia": { + "placementId": 19606578 + }, + "openx": { + "unit": "544086381", + "delDomain": "playwire-d.openx.net" + }, + "pubmatic": { + "publisherId": "158326", + "adSlot": "1957464" + }, + "rhythmone": { + "placementId": "213696", + "zone": "1r", + "path": "mvo" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2061954 + }, + "sonobi": { + "TagID": "12eb86143abb80d6a9c6" + }, + "triplelift": { + "inventoryCode": "playwire_app_hdx_ios_prebid_TEST" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/play_screen_ios_test.json b/stored_requests/data/by_id/stored_imps/play_screen_ios_test.json new file mode 100644 index 00000000000..c4a7c12b750 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/play_screen_ios_test.json @@ -0,0 +1,46 @@ +{ + "id": "play_screen_ios", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "appnexus": { + "placement_id": 19689655 + }, + "emx_digital": { + "tagid": "105251" + }, + "oftmedia": { + "placementId": 19606578 + }, + "openx": { + "unit": "544086381", + "delDomain": "playwire-d.openx.net" + }, + "pubmatic": { + "publisherId": "158326", + "adSlot": "3029490" + }, + "rhythmone": { + "placementId": "213696", + "zone": "1r", + "path": "mvo" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2061954 + }, + "sonobi": { + "TagID": "cc1338cc8e2c6e7864dd" + }, + "triplelift": { + "inventoryCode": "playwire_app_hdx_ios_prebid_TEST" + } + } +} \ No newline at end of file From da6d65ebc80746e958da50168070938084464d18 Mon Sep 17 00:00:00 2001 From: tcreamer Date: Thu, 13 Oct 2022 16:58:14 -0400 Subject: [PATCH 054/125] remove emx and oft bidders --- .../data/by_id/stored_imps/content_top_android.json | 8 +------- .../data/by_id/stored_imps/content_top_android_test.json | 8 +------- .../data/by_id/stored_imps/game_over_android.json | 8 +------- .../data/by_id/stored_imps/game_over_android_test.json | 8 +------- stored_requests/data/by_id/stored_imps/game_over_ios.json | 8 +------- .../data/by_id/stored_imps/game_over_ios_test.json | 8 +------- .../data/by_id/stored_imps/in_article_android.json | 6 ------ .../data/by_id/stored_imps/in_article_android_test.json | 6 ------ .../data/by_id/stored_imps/in_article_ios.json | 8 +------- .../data/by_id/stored_imps/in_article_ios_test.json | 8 +------- .../data/by_id/stored_imps/play_screen_ios.json | 8 +------- .../data/by_id/stored_imps/play_screen_ios_test.json | 8 +------- .../data/by_id/stored_requests/in_article_ios.json | 5 +---- 13 files changed, 11 insertions(+), 86 deletions(-) diff --git a/stored_requests/data/by_id/stored_imps/content_top_android.json b/stored_requests/data/by_id/stored_imps/content_top_android.json index c2b29e38b8b..354f79d988f 100644 --- a/stored_requests/data/by_id/stored_imps/content_top_android.json +++ b/stored_requests/data/by_id/stored_imps/content_top_android.json @@ -12,12 +12,6 @@ "appnexus": { "placement_id": 19689655 }, - "emx_digital": { - "tagid": "107133" - }, - "oftmedia": { - "placementId": 19606578 - }, "openx": { "unit": "544086382", "delDomain": "playwire-d.openx.net" @@ -43,4 +37,4 @@ "inventoryCode": "pwm_android_hdx_pb" } } -} \ No newline at end of file +} diff --git a/stored_requests/data/by_id/stored_imps/content_top_android_test.json b/stored_requests/data/by_id/stored_imps/content_top_android_test.json index f736e71321f..891916bad7c 100644 --- a/stored_requests/data/by_id/stored_imps/content_top_android_test.json +++ b/stored_requests/data/by_id/stored_imps/content_top_android_test.json @@ -12,12 +12,6 @@ "appnexus": { "placement_id": 19689655 }, - "emx_digital": { - "tagid": "107133" - }, - "oftmedia": { - "placementId": 19606578 - }, "openx": { "unit": "544086382", "delDomain": "playwire-d.openx.net" @@ -43,4 +37,4 @@ "inventoryCode": "playwire_app_hdx_android_prebid_TEST" } } -} \ No newline at end of file +} diff --git a/stored_requests/data/by_id/stored_imps/game_over_android.json b/stored_requests/data/by_id/stored_imps/game_over_android.json index 01237cb4348..f9bacc08f0e 100644 --- a/stored_requests/data/by_id/stored_imps/game_over_android.json +++ b/stored_requests/data/by_id/stored_imps/game_over_android.json @@ -16,12 +16,6 @@ "appnexus": { "placement_id": 19689655 }, - "emx_digital": { - "tagid": "107133" - }, - "oftmedia": { - "placementId": 19606578 - }, "openx": { "unit": "544086382", "delDomain": "playwire-d.openx.net" @@ -47,4 +41,4 @@ "inventoryCode": "pwm_android_320x50_300x250_pb" } } -} \ No newline at end of file +} diff --git a/stored_requests/data/by_id/stored_imps/game_over_android_test.json b/stored_requests/data/by_id/stored_imps/game_over_android_test.json index 3f3b7831473..45a63673913 100644 --- a/stored_requests/data/by_id/stored_imps/game_over_android_test.json +++ b/stored_requests/data/by_id/stored_imps/game_over_android_test.json @@ -16,12 +16,6 @@ "appnexus": { "placement_id": 19689655 }, - "emx_digital": { - "tagid": "107133" - }, - "oftmedia": { - "placementId": 19606578 - }, "openx": { "unit": "544086382", "delDomain": "playwire-d.openx.net" @@ -47,4 +41,4 @@ "inventoryCode": "playwire_app_hdx_android_prebid_TEST" } } -} \ No newline at end of file +} diff --git a/stored_requests/data/by_id/stored_imps/game_over_ios.json b/stored_requests/data/by_id/stored_imps/game_over_ios.json index d20dab17d39..8b9111dcefe 100644 --- a/stored_requests/data/by_id/stored_imps/game_over_ios.json +++ b/stored_requests/data/by_id/stored_imps/game_over_ios.json @@ -16,12 +16,6 @@ "appnexus": { "placement_id": 19689655 }, - "emx_digital": { - "tagid": "105251" - }, - "oftmedia": { - "placementId": 19606578 - }, "openx": { "unit": "544086381", "delDomain": "playwire-d.openx.net" @@ -47,4 +41,4 @@ "inventoryCode": "pwm_ios_320x50_300x250_pb" } } -} \ No newline at end of file +} diff --git a/stored_requests/data/by_id/stored_imps/game_over_ios_test.json b/stored_requests/data/by_id/stored_imps/game_over_ios_test.json index e860c73b1d2..78b3496a834 100644 --- a/stored_requests/data/by_id/stored_imps/game_over_ios_test.json +++ b/stored_requests/data/by_id/stored_imps/game_over_ios_test.json @@ -16,12 +16,6 @@ "appnexus": { "placement_id": 19689655 }, - "emx_digital": { - "tagid": "105251" - }, - "oftmedia": { - "placementId": 19606578 - }, "openx": { "unit": "544086381", "delDomain": "playwire-d.openx.net" @@ -47,4 +41,4 @@ "inventoryCode": "playwire_app_hdx_ios_prebid_TEST" } } -} \ No newline at end of file +} diff --git a/stored_requests/data/by_id/stored_imps/in_article_android.json b/stored_requests/data/by_id/stored_imps/in_article_android.json index a6106c6e399..6977485d867 100644 --- a/stored_requests/data/by_id/stored_imps/in_article_android.json +++ b/stored_requests/data/by_id/stored_imps/in_article_android.json @@ -16,12 +16,6 @@ "appnexus": { "placement_id": 19689655 }, - "emx_digital": { - "tagid": "107133" - }, - "oftmedia": { - "placementId": 19606578 - }, "openx": { "unit": "544086382", "delDomain": "playwire-d.openx.net" diff --git a/stored_requests/data/by_id/stored_imps/in_article_android_test.json b/stored_requests/data/by_id/stored_imps/in_article_android_test.json index 44cf0a7faf4..b4dd2a860c6 100644 --- a/stored_requests/data/by_id/stored_imps/in_article_android_test.json +++ b/stored_requests/data/by_id/stored_imps/in_article_android_test.json @@ -16,12 +16,6 @@ "appnexus": { "placement_id": 19689655 }, - "emx_digital": { - "tagid": "107133" - }, - "oftmedia": { - "placementId": 19606578 - }, "openx": { "unit": "544086382", "delDomain": "playwire-d.openx.net" diff --git a/stored_requests/data/by_id/stored_imps/in_article_ios.json b/stored_requests/data/by_id/stored_imps/in_article_ios.json index 2e6f110d14e..cd488fc1302 100644 --- a/stored_requests/data/by_id/stored_imps/in_article_ios.json +++ b/stored_requests/data/by_id/stored_imps/in_article_ios.json @@ -16,12 +16,6 @@ "appnexus": { "placement_id": 19689654 }, - "emx_digital": { - "tagid": "105251" - }, - "oftmedia": { - "placementId": 19606578 - }, "openx": { "unit": "544086381", "delDomain": "playwire-d.openx.net" @@ -47,4 +41,4 @@ "inventoryCode": "pwm_ios_320x50_300x250_pb" } } -} \ No newline at end of file +} diff --git a/stored_requests/data/by_id/stored_imps/in_article_ios_test.json b/stored_requests/data/by_id/stored_imps/in_article_ios_test.json index c127a8236a0..4545cca603e 100644 --- a/stored_requests/data/by_id/stored_imps/in_article_ios_test.json +++ b/stored_requests/data/by_id/stored_imps/in_article_ios_test.json @@ -16,12 +16,6 @@ "appnexus": { "placement_id": 19689654 }, - "emx_digital": { - "tagid": "105251" - }, - "oftmedia": { - "placementId": 19606578 - }, "openx": { "unit": "544086381", "delDomain": "playwire-d.openx.net" @@ -47,4 +41,4 @@ "inventoryCode": "playwire_app_hdx_ios_prebid_TEST" } } -} \ No newline at end of file +} diff --git a/stored_requests/data/by_id/stored_imps/play_screen_ios.json b/stored_requests/data/by_id/stored_imps/play_screen_ios.json index 047ce6ac612..60ae0da576f 100644 --- a/stored_requests/data/by_id/stored_imps/play_screen_ios.json +++ b/stored_requests/data/by_id/stored_imps/play_screen_ios.json @@ -12,12 +12,6 @@ "appnexus": { "placement_id": 19689655 }, - "emx_digital": { - "tagid": "105251" - }, - "oftmedia": { - "placementId": 19606578 - }, "openx": { "unit": "544086381", "delDomain": "playwire-d.openx.net" @@ -43,4 +37,4 @@ "inventoryCode": "pwm_ios_hdx_pb" } } -} \ No newline at end of file +} diff --git a/stored_requests/data/by_id/stored_imps/play_screen_ios_test.json b/stored_requests/data/by_id/stored_imps/play_screen_ios_test.json index c4a7c12b750..9535c49733e 100644 --- a/stored_requests/data/by_id/stored_imps/play_screen_ios_test.json +++ b/stored_requests/data/by_id/stored_imps/play_screen_ios_test.json @@ -12,12 +12,6 @@ "appnexus": { "placement_id": 19689655 }, - "emx_digital": { - "tagid": "105251" - }, - "oftmedia": { - "placementId": 19606578 - }, "openx": { "unit": "544086381", "delDomain": "playwire-d.openx.net" @@ -43,4 +37,4 @@ "inventoryCode": "playwire_app_hdx_ios_prebid_TEST" } } -} \ No newline at end of file +} diff --git a/stored_requests/data/by_id/stored_requests/in_article_ios.json b/stored_requests/data/by_id/stored_requests/in_article_ios.json index 6e0a46f4950..da0fe99555c 100644 --- a/stored_requests/data/by_id/stored_requests/in_article_ios.json +++ b/stored_requests/data/by_id/stored_requests/in_article_ios.json @@ -16,9 +16,6 @@ "appnexus": { "placement_id": 19689654 }, - "emx_digital": { - "tagid": "105251" - }, "openx": { "unit": "541169197", "delDomain": "playwire-d.openx.net" @@ -40,4 +37,4 @@ "TagID": "12eb86143abb80d6a9c6" } } -} \ No newline at end of file +} From 261403482a39f33115e3e9c2ad35708b41974688 Mon Sep 17 00:00:00 2001 From: tcreamer Date: Wed, 26 Oct 2022 14:35:11 -0400 Subject: [PATCH 055/125] fixing test reqs --- config/config.go | 1 + exchange/adapter_builders.go | 1 + openrtb_ext/bidders.go | 2 ++ openrtb_ext/bidders_validate_test.go | 2 +- static/bidder-info/playwire_ortb.yaml | 12 ++++++++++++ static/bidder-params/playwire_ortb.json | 13 +++++++++++++ 6 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 static/bidder-info/playwire_ortb.yaml create mode 100644 static/bidder-params/playwire_ortb.json diff --git a/config/config.go b/config/config.go index 729f3109cff..dc902bd0f16 100644 --- a/config/config.go +++ b/config/config.go @@ -1010,6 +1010,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.pangle.disabled", true) v.SetDefault("adapters.pgam.endpoint", "http://ghb.pgamssp.com/pbs/ortb") v.SetDefault("adapters.playwire.endpoint", "https://grid.bidswitch.net/sp_bid?sp=prebid") + v.SetDefault("adapters.playwire_ortb.endpoint", "https://grid.bidswitch.net/sp_bid?sp=prebid") v.SetDefault("adapters.pubmatic.endpoint", "https://hbopenbid.pubmatic.com/translator?source=prebid-server") v.SetDefault("adapters.pubnative.endpoint", "http://dsp.pubnative.net/bid/v1/request") v.SetDefault("adapters.pulsepoint.endpoint", "http://bid.contextweb.com/header/s/ortb/prebid-s2s") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 4b0eeb7b0e1..348747fadbd 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -251,6 +251,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderPangle: pangle.Builder, openrtb_ext.BidderPGAM: adtelligent.Builder, openrtb_ext.BidderPlaywire: playwire.Builder, + openrtb_ext.BidderPlaywireOrtb: playwire_ortb.Builder, openrtb_ext.BidderPubmatic: pubmatic.Builder, openrtb_ext.BidderPubnative: pubnative.Builder, openrtb_ext.BidderPulsepoint: pulsepoint.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 2f59e478fb4..05328ee4c92 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -181,6 +181,7 @@ const ( BidderPangle BidderName = "pangle" BidderPGAM BidderName = "pgam" BidderPlaywire BidderName = "playwire" + BidderPlaywireOrtb BidderName = "playwire_ortb" BidderPubmatic BidderName = "pubmatic" BidderPubnative BidderName = "pubnative" BidderPulsepoint BidderName = "pulsepoint" @@ -336,6 +337,7 @@ func CoreBidderNames() []BidderName { BidderPGAM, BidderPangle, BidderPlaywire, + BidderPlaywireOrtb, BidderPubmatic, BidderPubnative, BidderPulsepoint, diff --git a/openrtb_ext/bidders_validate_test.go b/openrtb_ext/bidders_validate_test.go index 2ee3dd7d806..f0a1d158773 100644 --- a/openrtb_ext/bidders_validate_test.go +++ b/openrtb_ext/bidders_validate_test.go @@ -55,7 +55,7 @@ func TestBidderUniquenessGatekeeping(t *testing.T) { } } - currentThreshold := 6 + currentThreshold := 11 measuredThreshold := minUniquePrefixLength(bidders) assert.NotZero(t, measuredThreshold, "BidderMap contains duplicate bidder name values.") diff --git a/static/bidder-info/playwire_ortb.yaml b/static/bidder-info/playwire_ortb.yaml new file mode 100644 index 00000000000..602f7d38b78 --- /dev/null +++ b/static/bidder-info/playwire_ortb.yaml @@ -0,0 +1,12 @@ +maintainer: + email: "na@playwire.com" +gvlVendorID: 686 +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video diff --git a/static/bidder-params/playwire_ortb.json b/static/bidder-params/playwire_ortb.json new file mode 100644 index 00000000000..dd4b9be09a7 --- /dev/null +++ b/static/bidder-params/playwire_ortb.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Playwire Adapter Params", + "description": "A schema which validates params accepted by the Playwire adapter", + "type": "object", + "properties": { + "uid": { + "type": "integer", + "description": "An ID which identifies this placement of the impression" + } + }, + "required": [] +} From 8b0eb5e7a96ecca185ee444963b7acfbe687b88b Mon Sep 17 00:00:00 2001 From: Tausif Al Hossain Date: Thu, 27 Oct 2022 11:24:55 -0400 Subject: [PATCH 056/125] New size based ad units added, updated ids for bidders --- .../stored_imps/banner_300x250_android.json | 47 +++++++++++++++++++ .../by_id/stored_imps/banner_300x250_ios.json | 47 +++++++++++++++++++ .../stored_imps/banner_320x100_android.json | 47 +++++++++++++++++++ .../by_id/stored_imps/banner_320x100_ios.json | 47 +++++++++++++++++++ .../stored_imps/banner_320x50_android.json | 47 +++++++++++++++++++ .../by_id/stored_imps/banner_320x50_ios.json | 47 +++++++++++++++++++ .../stored_imps/banner_728x90_android.json | 47 +++++++++++++++++++ .../by_id/stored_imps/banner_728x90_ios.json | 47 +++++++++++++++++++ .../interstitial_1024x768_android.json | 39 +++++++++++++++ .../interstitial_1024x768_ios.json | 39 +++++++++++++++ .../interstitial_320x480_android.json | 39 +++++++++++++++ .../stored_imps/interstitial_320x480_ios.json | 39 +++++++++++++++ .../interstitial_480x320_android.json | 40 ++++++++++++++++ .../stored_imps/interstitial_480x320_ios.json | 39 +++++++++++++++ .../interstitial_480x640v_android.json | 40 ++++++++++++++++ .../interstitial_480x640v_ios.json | 39 +++++++++++++++ .../interstitial_640x480v_android.json | 39 +++++++++++++++ .../interstitial_640x480v_ios.json | 39 +++++++++++++++ .../interstitial_768x1024_android.json | 39 +++++++++++++++ .../interstitial_768x1024_ios.json | 39 +++++++++++++++ 20 files changed, 846 insertions(+) create mode 100644 stored_requests/data/by_id/stored_imps/banner_300x250_android.json create mode 100644 stored_requests/data/by_id/stored_imps/banner_300x250_ios.json create mode 100644 stored_requests/data/by_id/stored_imps/banner_320x100_android.json create mode 100644 stored_requests/data/by_id/stored_imps/banner_320x100_ios.json create mode 100644 stored_requests/data/by_id/stored_imps/banner_320x50_android.json create mode 100644 stored_requests/data/by_id/stored_imps/banner_320x50_ios.json create mode 100644 stored_requests/data/by_id/stored_imps/banner_728x90_android.json create mode 100644 stored_requests/data/by_id/stored_imps/banner_728x90_ios.json create mode 100644 stored_requests/data/by_id/stored_imps/interstitial_1024x768_android.json create mode 100644 stored_requests/data/by_id/stored_imps/interstitial_1024x768_ios.json create mode 100644 stored_requests/data/by_id/stored_imps/interstitial_320x480_android.json create mode 100644 stored_requests/data/by_id/stored_imps/interstitial_320x480_ios.json create mode 100644 stored_requests/data/by_id/stored_imps/interstitial_480x320_android.json create mode 100644 stored_requests/data/by_id/stored_imps/interstitial_480x320_ios.json create mode 100644 stored_requests/data/by_id/stored_imps/interstitial_480x640v_android.json create mode 100644 stored_requests/data/by_id/stored_imps/interstitial_480x640v_ios.json create mode 100644 stored_requests/data/by_id/stored_imps/interstitial_640x480v_android.json create mode 100644 stored_requests/data/by_id/stored_imps/interstitial_640x480v_ios.json create mode 100644 stored_requests/data/by_id/stored_imps/interstitial_768x1024_android.json create mode 100644 stored_requests/data/by_id/stored_imps/interstitial_768x1024_ios.json diff --git a/stored_requests/data/by_id/stored_imps/banner_300x250_android.json b/stored_requests/data/by_id/stored_imps/banner_300x250_android.json new file mode 100644 index 00000000000..14720534145 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/banner_300x250_android.json @@ -0,0 +1,47 @@ +{ + "id": "banner_300x250_android", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165611" + }, + "triplelift": { + "inventoryCode": "pwm_android_300x250_pb" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369430 + }, + "sonobi": { + "TagID": "1b22f1a1d1a47a68b67b" + }, + "rhythmone": { + "placementId": "247568", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "544086382", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773200 + }, + "yieldmo": { + "placementId": "2986450036092510476" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/banner_300x250_ios.json b/stored_requests/data/by_id/stored_imps/banner_300x250_ios.json new file mode 100644 index 00000000000..b8d8ff53531 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/banner_300x250_ios.json @@ -0,0 +1,47 @@ +{ + "id": "banner_300x250_ios", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165608" + }, + "triplelift": { + "inventoryCode": "pwm_ios_300x250_pb" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369428 + }, + "sonobi": { + "TagID": "bc7f500b477b40d44f67" + }, + "rhythmone": { + "placementId": "247567", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "544086381", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773199 + }, + "yieldmo": { + "placementId": "2986450035966681355" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/banner_320x100_android.json b/stored_requests/data/by_id/stored_imps/banner_320x100_android.json new file mode 100644 index 00000000000..0cedc937d0e --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/banner_320x100_android.json @@ -0,0 +1,47 @@ +{ + "id": "banner_320x100_android", + "banner": { + "format": [ + { + "w": 320, + "h": 100 + } + ] + }, + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165611" + }, + "triplelift": { + "inventoryCode": "pwm_android_hdx_pb" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369430 + }, + "sonobi": { + "TagID": "c94aa51bb23253f97ee8" + }, + "rhythmone": { + "placementId": "247568", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "544086382", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773200 + }, + "yieldmo": { + "placementId": "2986450036092510476" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/banner_320x100_ios.json b/stored_requests/data/by_id/stored_imps/banner_320x100_ios.json new file mode 100644 index 00000000000..b8703a55ab3 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/banner_320x100_ios.json @@ -0,0 +1,47 @@ +{ + "id": "banner_320x100_ios", + "banner": { + "format": [ + { + "w": 320, + "h": 100 + } + ] + }, + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165608" + }, + "triplelift": { + "inventoryCode": "pwm_ios_320x50_300x250_pb" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369428 + }, + "sonobi": { + "TagID": "589fed9631ab52cbe0dc" + }, + "rhythmone": { + "placementId": "247567", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "544086381", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773199 + }, + "yieldmo": { + "placementId": "2986450035966681355" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/banner_320x50_android.json b/stored_requests/data/by_id/stored_imps/banner_320x50_android.json new file mode 100644 index 00000000000..dd83d868d24 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/banner_320x50_android.json @@ -0,0 +1,47 @@ +{ + "id": "banner_320x50_android", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165611" + }, + "triplelift": { + "inventoryCode": "pwm_android_320x50_300x250_pb" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369430 + }, + "sonobi": { + "TagID": "5200294cfc91869f267d" + }, + "rhythmone": { + "placementId": "247568", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "544086382", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773200 + }, + "yieldmo": { + "placementId": "2986450036092510476" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/banner_320x50_ios.json b/stored_requests/data/by_id/stored_imps/banner_320x50_ios.json new file mode 100644 index 00000000000..8d7ff85c220 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/banner_320x50_ios.json @@ -0,0 +1,47 @@ +{ + "id": "banner_320x50_ios", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165608" + }, + "triplelift": { + "inventoryCode": "pwm_ios_320x50_300x250_pb" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369428 + }, + "sonobi": { + "TagID": "cc1338cc8e2c6e7864dd" + }, + "rhythmone": { + "placementId": "247567", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "544086381", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773199 + }, + "yieldmo": { + "placementId": "2986450035966681355" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/banner_728x90_android.json b/stored_requests/data/by_id/stored_imps/banner_728x90_android.json new file mode 100644 index 00000000000..516eb0f7557 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/banner_728x90_android.json @@ -0,0 +1,47 @@ +{ + "id": "banner_728x90_android", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165611" + }, + "triplelift": { + "inventoryCode": "pwm_android_320x50_300x250_pb" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369430 + }, + "sonobi": { + "TagID": "5defb02096c7ca6be2ac" + }, + "rhythmone": { + "placementId": "247568", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "544086382", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773200 + }, + "yieldmo": { + "placementId": "2986450036092510476" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/banner_728x90_ios.json b/stored_requests/data/by_id/stored_imps/banner_728x90_ios.json new file mode 100644 index 00000000000..fa98f44e0c7 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/banner_728x90_ios.json @@ -0,0 +1,47 @@ +{ + "id": "banner_728x90_ios", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165608" + }, + "triplelift": { + "inventoryCode": "pwm_ios_320x50_300x250_pb" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369428 + }, + "sonobi": { + "TagID": "b79e15de7e04b90543d3" + }, + "rhythmone": { + "placementId": "247567", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "544086381", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773199 + }, + "yieldmo": { + "placementId": "2986450035966681355" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/interstitial_1024x768_android.json b/stored_requests/data/by_id/stored_imps/interstitial_1024x768_android.json new file mode 100644 index 00000000000..c537c83cda4 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/interstitial_1024x768_android.json @@ -0,0 +1,39 @@ +{ + "id": "interstitial_1024x768_android", + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165611" + }, + "triplelift": { + "inventoryCode": "Playwire_RON_Interstitial_Android" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369430 + }, + "sonobi": { + "TagID": "e75048f43948741bad48" + }, + "rhythmone": { + "placementId": "247568", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "558236878", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773200 + }, + "yieldmo": { + "placementId": "3124076396956033378" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/interstitial_1024x768_ios.json b/stored_requests/data/by_id/stored_imps/interstitial_1024x768_ios.json new file mode 100644 index 00000000000..d2c2197d06b --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/interstitial_1024x768_ios.json @@ -0,0 +1,39 @@ +{ + "id": "interstitial_1024x768_ios", + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165608" + }, + "triplelift": { + "inventoryCode": "Playwire_RON_Interstitial_IOS" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369428 + }, + "sonobi": { + "TagID": "e94911505445cf11c143" + }, + "rhythmone": { + "placementId": "247567", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "558236877", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773199 + }, + "yieldmo": { + "placementId": "3124076397123805539" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/interstitial_320x480_android.json b/stored_requests/data/by_id/stored_imps/interstitial_320x480_android.json new file mode 100644 index 00000000000..fce6ca03e5e --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/interstitial_320x480_android.json @@ -0,0 +1,39 @@ +{ + "id": "interstitial_320x480_android", + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165611" + }, + "triplelift": { + "inventoryCode": "Playwire_RON_Interstitial_Android" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369430 + }, + "sonobi": { + "TagID": "e75048f43948741bad48" + }, + "rhythmone": { + "placementId": "247568", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "558236878", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773200 + }, + "yieldmo": { + "placementId": "3124076396956033378" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/interstitial_320x480_ios.json b/stored_requests/data/by_id/stored_imps/interstitial_320x480_ios.json new file mode 100644 index 00000000000..4e2c80b58ba --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/interstitial_320x480_ios.json @@ -0,0 +1,39 @@ +{ + "id": "interstitial_320x480_ios", + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165608" + }, + "triplelift": { + "inventoryCode": "Playwire_RON_Interstitial_IOS" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369428 + }, + "sonobi": { + "TagID": "e94911505445cf11c143" + }, + "rhythmone": { + "placementId": "247567", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "558236877", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773199 + }, + "yieldmo": { + "placementId": "3124076397123805539" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/interstitial_480x320_android.json b/stored_requests/data/by_id/stored_imps/interstitial_480x320_android.json new file mode 100644 index 00000000000..95b38c83f0a --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/interstitial_480x320_android.json @@ -0,0 +1,40 @@ +{ + "id": "interstitial_480x320_android", + "interstitial": {}, + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165611" + }, + "triplelift": { + "inventoryCode": "Playwire_RON_Interstitial_Android" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369430 + }, + "sonobi": { + "TagID": "e75048f43948741bad48" + }, + "rhythmone": { + "placementId": "247568", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "558236878", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773200 + }, + "yieldmo": { + "placementId": "3124076396956033378" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/interstitial_480x320_ios.json b/stored_requests/data/by_id/stored_imps/interstitial_480x320_ios.json new file mode 100644 index 00000000000..08208df1fbe --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/interstitial_480x320_ios.json @@ -0,0 +1,39 @@ +{ + "id": "interstitial_480x320_ios", + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165608" + }, + "triplelift": { + "inventoryCode": "Playwire_RON_Interstitial_IOS" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369428 + }, + "sonobi": { + "TagID": "e94911505445cf11c143" + }, + "rhythmone": { + "placementId": "247567", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "558236877", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773199 + }, + "yieldmo": { + "placementId": "3124076397123805539" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/interstitial_480x640v_android.json b/stored_requests/data/by_id/stored_imps/interstitial_480x640v_android.json new file mode 100644 index 00000000000..f644bc07bdb --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/interstitial_480x640v_android.json @@ -0,0 +1,40 @@ +{ + "id": "interstitial_480x640v_android", + "interstitial": {}, + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165611" + }, + "triplelift": { + "inventoryCode": "Playwire_RON_Interstitial_Android" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369430 + }, + "sonobi": { + "TagID": "e75048f43948741bad48" + }, + "rhythmone": { + "placementId": "247568", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "558236878", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773200 + }, + "yieldmo": { + "placementId": "3124076396956033378" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/interstitial_480x640v_ios.json b/stored_requests/data/by_id/stored_imps/interstitial_480x640v_ios.json new file mode 100644 index 00000000000..d9c44258297 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/interstitial_480x640v_ios.json @@ -0,0 +1,39 @@ +{ + "id": "interstitial_480x640v_ios", + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165608" + }, + "triplelift": { + "inventoryCode": "Playwire_RON_Interstitial_IOS" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369428 + }, + "sonobi": { + "TagID": "e94911505445cf11c143" + }, + "rhythmone": { + "placementId": "247567", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "558236877", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773199 + }, + "yieldmo": { + "placementId": "3124076397123805539" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/interstitial_640x480v_android.json b/stored_requests/data/by_id/stored_imps/interstitial_640x480v_android.json new file mode 100644 index 00000000000..17cfeb67154 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/interstitial_640x480v_android.json @@ -0,0 +1,39 @@ +{ + "id": "interstitial_640x480v_android", + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165611" + }, + "triplelift": { + "inventoryCode": "Playwire_RON_Interstitial_Android" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369430 + }, + "sonobi": { + "TagID": "e75048f43948741bad48" + }, + "rhythmone": { + "placementId": "247568", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "558236878", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773200 + }, + "yieldmo": { + "placementId": "3124076396956033378" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/interstitial_640x480v_ios.json b/stored_requests/data/by_id/stored_imps/interstitial_640x480v_ios.json new file mode 100644 index 00000000000..00d54e76304 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/interstitial_640x480v_ios.json @@ -0,0 +1,39 @@ +{ + "id": "interstitial_640x480v_ios", + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165608" + }, + "triplelift": { + "inventoryCode": "Playwire_RON_Interstitial_IOS" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369428 + }, + "sonobi": { + "TagID": "e94911505445cf11c143" + }, + "rhythmone": { + "placementId": "247567", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "558236877", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773199 + }, + "yieldmo": { + "placementId": "3124076397123805539" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/interstitial_768x1024_android.json b/stored_requests/data/by_id/stored_imps/interstitial_768x1024_android.json new file mode 100644 index 00000000000..22366ee3d0e --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/interstitial_768x1024_android.json @@ -0,0 +1,39 @@ +{ + "id": "interstitial_768x1024_android", + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165611" + }, + "triplelift": { + "inventoryCode": "Playwire_RON_Interstitial_Android" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369430 + }, + "sonobi": { + "TagID": "e75048f43948741bad48" + }, + "rhythmone": { + "placementId": "247568", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "558236878", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773200 + }, + "yieldmo": { + "placementId": "3124076396956033378" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/interstitial_768x1024_ios.json b/stored_requests/data/by_id/stored_imps/interstitial_768x1024_ios.json new file mode 100644 index 00000000000..9a792dde409 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/interstitial_768x1024_ios.json @@ -0,0 +1,39 @@ +{ + "id": "interstitial_768x1024_ios", + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165608" + }, + "triplelift": { + "inventoryCode": "Playwire_RON_Interstitial_IOS" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369428 + }, + "sonobi": { + "TagID": "e94911505445cf11c143" + }, + "rhythmone": { + "placementId": "247567", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "558236877", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773199 + }, + "yieldmo": { + "placementId": "3124076397123805539" + } + } +} \ No newline at end of file From 5792c12b2663be53efa9df58b621cd45a709d6b4 Mon Sep 17 00:00:00 2001 From: tcreamer Date: Tue, 3 Jan 2023 10:32:04 -0500 Subject: [PATCH 057/125] upgrade versions to 0.236.0 --- .devcontainer/devcontainer.json | 13 +- .github/workflows/issue_prioritization.yml | 90 + .github/workflows/release.yml | 2 +- .github/workflows/security.yml | 2 +- .github/workflows/validate-merge.yml | 4 +- .github/workflows/validate.yml | 4 +- Dockerfile | 10 +- Makefile | 8 +- README.md | 2 +- account/account.go | 113 +- account/account_test.go | 174 +- adapters/33across/33across.go | 9 +- adapters/33across/33across_test.go | 2 +- adapters/33across/params_test.go | 2 +- adapters/aax/aax.go | 4 +- adapters/aax/aax_test.go | 7 +- adapters/aceex/aceex.go | 4 +- adapters/aceex/aceex_test.go | 4 +- adapters/acuityads/acuityads.go | 4 +- adapters/acuityads/acuityads_test.go | 4 +- adapters/adapterstest/adapter_test_util.go | 2 +- adapters/adapterstest/test_json.go | 48 +- adapters/adf/adf.go | 4 +- adapters/adf/adf_test.go | 2 +- adapters/adf/params_test.go | 2 +- adapters/adgeneration/adgeneration.go | 4 +- adapters/adgeneration/adgeneration_test.go | 10 +- adapters/adhese/adhese.go | 4 +- adapters/adhese/adhese_test.go | 4 +- adapters/adhese/utils.go | 2 +- adapters/adkernel/adkernel.go | 12 +- adapters/adkernel/adkernel_test.go | 4 +- adapters/adkernelAdn/adkernelAdn.go | 12 +- adapters/adkernelAdn/adkernelAdn_test.go | 4 +- adapters/adman/adman.go | 4 +- adapters/adman/adman_test.go | 2 +- adapters/admixer/admixer.go | 4 +- adapters/admixer/admixer_test.go | 2 +- adapters/admixer/params_test.go | 5 +- adapters/adnuntius/adnuntius.go | 157 +- adapters/adnuntius/adnuntius_test.go | 7 +- .../exemplary/simple-banner.json | 3 +- .../supplemental/banner-nil-check.json | 29 + .../supplemental/check-gdpr.json | 216 +- .../check-noCookies-parameter.json | 104 + .../supplemental/check-noCookies.json | 108 + .../supplemental/check-order-multi-imp.json | 162 ++ .../supplemental/check-userId.json | 200 +- .../supplemental/empty-regs-ext.json | 106 + .../supplemental/empty-regs.json | 105 + .../supplemental/height-error.json | 166 +- .../supplemental/native-error.json | 45 +- .../supplemental/send-header-information.json | 241 +- .../supplemental/size-check.json | 101 + .../supplemental/status-400.json | 109 +- .../supplemental/test-networks.json | 192 +- .../supplemental/video-error.json | 47 +- .../supplemental/width-error.json | 166 +- adapters/adnuntius/params_test.go | 2 +- adapters/adocean/adocean.go | 4 +- adapters/adocean/adocean_test.go | 4 +- adapters/adoppler/adoppler.go | 4 +- adapters/adoppler/adoppler_test.go | 2 +- adapters/adot/adot.go | 4 +- adapters/adot/adot_test.go | 6 +- adapters/adot/params_test.go | 2 +- adapters/adpone/adpone.go | 4 +- adapters/adpone/adpone_test.go | 2 +- adapters/adprime/adprime.go | 4 +- adapters/adprime/adprime_test.go | 2 +- adapters/adrino/adrino.go | 85 + adapters/adrino/adrino_test.go | 29 + .../adrino/adrinotest/exemplary/no-bid.json | 73 + .../adrinotest/exemplary/single-native.json | 115 + .../adrinotest/supplemental/unknown-hash.json | 73 + adapters/adrino/params_test.go | 51 + adapters/adtarget/adtarget.go | 4 +- adapters/adtarget/adtarget_test.go | 2 +- adapters/adtarget/params_test.go | 2 +- adapters/adtelligent/adtelligent.go | 4 +- adapters/adtelligent/adtelligent_test.go | 2 +- adapters/adtelligent/params_test.go | 2 +- adapters/adtrgtme/adtrgtme.go | 197 ++ adapters/adtrgtme/adtrgtme_test.go | 20 + .../adtrgtmetest/exemplary/banner-app.json | 146 ++ .../adtrgtmetest/exemplary/banner-web.json | 142 ++ .../supplemental/banner-app-headers-ipv6.json | 146 ++ .../supplemental/banner-web-headers-ipv6.json | 142 ++ .../supplemental/empty-seatbid-array.json | 125 + .../supplemental/invalid-response.json | 106 + .../supplemental/not-found-imp.json | 128 + .../supplemental/not-provided-app-id.json | 48 + .../supplemental/not-provided-site-id.json | 46 + .../not-provided-site-or-app.json | 37 + .../supplemental/status-code-bad-request.json | 87 + .../status-code-internal-server-error.json | 77 + .../supplemental/status-code-no-content.json | 72 + .../status-code-service-unavaliable.json | 77 + .../status-code-temporary-redirect.json | 77 + .../unsupported-bid-type-native.json | 118 + .../unsupported-bid-type-video.json | 128 + adapters/advangelists/advangelists.go | 12 +- adapters/advangelists/advangelists_test.go | 4 +- adapters/adview/adview.go | 4 +- adapters/adview/adview_test.go | 4 +- adapters/adxcg/adxcg.go | 4 +- adapters/adxcg/adxcg_test.go | 2 +- adapters/adyoulike/adyoulike.go | 4 +- adapters/adyoulike/adyoulike_test.go | 2 +- adapters/adyoulike/params_test.go | 2 +- adapters/aja/aja.go | 7 +- adapters/aja/aja_test.go | 2 +- adapters/algorix/algorix.go | 6 +- adapters/algorix/algorix_test.go | 7 +- adapters/amx/amx.go | 4 +- adapters/amx/amx_test.go | 14 +- adapters/apacdex/apacdex.go | 4 +- adapters/apacdex/apacdex_test.go | 2 +- adapters/apacdex/params_test.go | 2 +- adapters/applogy/applogy.go | 4 +- adapters/applogy/applogy_test.go | 2 +- adapters/appnexus/appnexus.go | 15 +- adapters/appnexus/appnexus_test.go | 6 +- adapters/appnexus/params_test.go | 2 +- adapters/appush/appush.go | 154 ++ adapters/appush/appush_test.go | 20 + .../appushtest/exemplary/endpointId.json | 133 + .../appushtest/exemplary/simple-banner.json | 133 + .../appushtest/exemplary/simple-native.json | 117 + .../appushtest/exemplary/simple-video.json | 128 + .../exemplary/simple-web-banner.json | 133 + .../supplemental/bad_media_type.json | 86 + .../appushtest/supplemental/bad_response.json | 84 + .../appushtest/supplemental/status-204.json | 79 + .../supplemental/status-not-200.json | 84 + adapters/appush/params_test.go | 47 + adapters/audienceNetwork/facebook.go | 9 +- adapters/audienceNetwork/facebook_test.go | 10 +- adapters/automatad/automatad.go | 77 + adapters/automatad/automatad_test.go | 20 + .../exemplary/simple-banner.json | 109 + .../supplemental/bad-request.json | 69 + .../supplemental/error-500-request.json | 69 + .../supplemental/no-content.json | 64 + adapters/automatad/params_test.go | 48 + adapters/avocet/avocet.go | 7 +- adapters/avocet/avocet_test.go | 9 +- adapters/axonix/axonix.go | 4 +- adapters/axonix/axonix_test.go | 7 +- adapters/axonix/params_test.go | 2 +- adapters/beachfront/beachfront.go | 55 +- adapters/beachfront/beachfront_test.go | 8 +- adapters/beintoo/beintoo.go | 4 +- adapters/beintoo/beintoo_test.go | 2 +- adapters/between/between.go | 4 +- adapters/between/between_test.go | 4 +- adapters/between/params_test.go | 2 +- adapters/beyondmedia/beyondmedia.go | 147 ++ adapters/beyondmedia/beyondmedia_test.go | 20 + .../exemplary/simple-banner.json | 133 + .../exemplary/simple-native.json | 117 + .../exemplary/simple-video.json | 128 + .../exemplary/simple-web-banner.json | 133 + .../supplemental/bad_media_type.json | 86 + .../supplemental/bad_response.json | 84 + .../supplemental/status-204.json | 79 + .../supplemental/status-not-200.json | 84 + adapters/beyondmedia/params_test.go | 45 + adapters/bidder.go | 12 +- adapters/bidmachine/bidmachine.go | 18 +- adapters/bidmachine/bidmachine_test.go | 4 +- adapters/bidmyadz/bidmyadz.go | 8 +- adapters/bidmyadz/bidmyadz_test.go | 5 +- adapters/bidscube/bidscube.go | 4 +- adapters/bidscube/bidscube_test.go | 2 +- adapters/bidstack/bidstack.go | 126 + adapters/bidstack/bidstack_test.go | 50 + .../exemplary/simple-app-video.json | 143 ++ .../supplemental/currency_rate_not_found.json | 54 + .../bidstacktest/supplemental/status-204.json | 78 + .../bidstacktest/supplemental/status-400.json | 83 + .../bidstacktest/supplemental/status-404.json | 83 + adapters/bidstack/params_test.go | 47 + adapters/bizzclick/bizzclick.go | 4 +- adapters/bizzclick/bizzclick_test.go | 4 +- adapters/bliink/bliink.go | 23 +- adapters/bliink/bliink_test.go | 5 +- .../exemplary/banner_native_video.json | 222 ++ adapters/blue/blue.go | 86 + adapters/blue/blue_test.go | 20 + .../bluetest/exemplary/simple-web-banner.json | 115 + .../204-response-from-target.json | 52 + .../400-response-from-target.json | 57 + .../500-response-from-target.json | 57 + .../bluetest/supplemental/bad_response.json | 57 + adapters/bmtm/brightmountainmedia.go | 4 +- adapters/bmtm/brightmountainmedia_test.go | 2 +- adapters/boldwin/boldwin.go | 154 ++ adapters/boldwin/boldwin_test.go | 20 + .../boldwintest/exemplary/endpointId.json | 133 + .../boldwintest/exemplary/simple-banner.json | 133 + .../boldwintest/exemplary/simple-native.json | 117 + .../boldwintest/exemplary/simple-video.json | 128 + .../exemplary/simple-web-banner.json | 133 + .../supplemental/bad_media_type.json | 86 + .../supplemental/bad_response.json | 84 + .../boldwintest/supplemental/status-204.json | 79 + .../supplemental/status-not-200.json | 84 + adapters/boldwin/params_test.go | 47 + adapters/brightroll/brightroll.go | 13 +- adapters/brightroll/brightroll_test.go | 8 +- adapters/brightroll/params_test.go | 2 +- adapters/ccx/ccx.go | 80 + adapters/ccx/ccx_test.go | 20 + .../ccx/ccxtest/exemplary/multi-banner.json | 181 ++ .../ccx/ccxtest/exemplary/simple-banner.json | 126 + .../204-response-from-target.json | 53 + .../400-response-from-target.json | 58 + .../500-response-from-target.json | 58 + adapters/ccx/params_test.go | 48 + adapters/coinzilla/coinzilla.go | 4 +- adapters/coinzilla/coinzilla_test.go | 2 +- adapters/colossus/colossus.go | 4 +- adapters/colossus/colossus_test.go | 2 +- adapters/compass/compass.go | 4 +- adapters/compass/compass_test.go | 2 +- adapters/connectad/connectad.go | 4 +- adapters/connectad/connectad_test.go | 2 +- adapters/consumable/adtypes.go | 2 +- adapters/consumable/consumable.go | 93 +- .../consumable/exemplary/simple-banner.json | 5 + .../simple-banner-content-meta.json | 128 + .../supplemental/simple-banner-coppa.json | 128 + .../supplemental/simple-banner-eids.json | 150 ++ .../supplemental/simple-banner-gdpr-2.json | 5 + .../supplemental/simple-banner-gdpr-3.json | 5 + .../supplemental/simple-banner-gdpr.json | 5 + .../simple-banner-no-impressionUrl.json | 5 + .../supplemental/simple-banner-schain.json | 145 ++ .../simple-banner-us-privacy.json | 5 + adapters/consumable/consumable_test.go | 2 +- adapters/consumable/params_test.go | 2 +- adapters/conversant/conversant.go | 21 +- adapters/conversant/conversant_test.go | 2 +- adapters/cpmstar/cpmstar.go | 4 +- adapters/cpmstar/cpmstar_test.go | 2 +- adapters/cpmstar/params_test.go | 2 +- adapters/criteo/criteo.go | 8 +- adapters/criteo/criteo_test.go | 1 + adapters/criteo/models.go | 14 +- adapters/criteo/models_test.go | 2 +- adapters/criteo/params_test.go | 2 +- adapters/datablocks/datablocks.go | 4 +- adapters/datablocks/datablocks_test.go | 4 +- adapters/decenterads/decenterads.go | 4 +- adapters/decenterads/decenterads_test.go | 2 +- adapters/deepintent/deepintent.go | 6 +- adapters/deepintent/deepintent_test.go | 2 +- adapters/dianomi/dianomi.go | 159 ++ adapters/dianomi/dianomi_test.go | 20 + .../dianomitest/exemplary/multi-format.json | 170 ++ .../dianomitest/exemplary/multi-native.json | 136 + ...gle-banner-pricetype-gross-extend-ext.json | 130 + .../single-banner-pricetype-gross.json | 120 + .../single-banner-pricetype-net.json | 120 + .../dianomitest/exemplary/single-banner.json | 113 + .../dianomitest/exemplary/single-native.json | 108 + .../dianomitest/exemplary/single-video.json | 111 + ...nners-different-pricetypes-extend-ext.json | 159 ++ .../two-banners-different-pricetypes.json | 149 ++ .../dianomitest/supplemental/bad-request.json | 48 + .../supplemental/empty-response.json | 42 + .../supplemental/invalid-imp-mediatype.json | 93 + .../supplemental/nobid-response.json | 49 + .../supplemental/server-error.json | 49 + .../supplemental/unparsable-response.json | 49 + adapters/dianomi/params_test.go | 60 + adapters/dmx/dmx.go | 9 +- adapters/dmx/dmx_test.go | 26 +- adapters/e_volution/evolution.go | 8 +- adapters/e_volution/evolution_test.go | 2 +- adapters/emx_digital/emx_digital.go | 11 +- adapters/emx_digital/emx_digital_test.go | 2 +- adapters/engagebdr/engagebdr.go | 4 +- adapters/engagebdr/engagebdr_test.go | 2 +- adapters/eplanning/eplanning.go | 7 +- adapters/eplanning/eplanning_test.go | 2 +- adapters/epom/epom.go | 4 +- adapters/epom/epom_test.go | 2 +- adapters/freewheelssp/freewheelssp.go | 103 + adapters/freewheelssp/freewheelssp_test.go | 19 + .../freewheelssptest/exemplary/multi-imp.json | 144 ++ .../exemplary/single-imp.json | 97 + .../supplemental/204-bid-response.json | 60 + .../supplemental/503-bid-response.json | 64 + adapters/gamma/gamma.go | 21 +- adapters/gamma/gamma_test.go | 2 +- adapters/gamma/params_test.go | 2 +- adapters/gamoshi/gamoshi.go | 4 +- adapters/gamoshi/gamoshi_test.go | 4 +- adapters/gamoshi/params_test.go | 2 +- adapters/grid/grid.go | 53 +- adapters/grid/grid_test.go | 2 +- .../gridtest/exemplary/multitype-native.json | 110 + .../gridtest/exemplary/native-as-string.json | 85 + .../gridtest/exemplary/simple-native.json | 85 + adapters/gumgum/gumgum.go | 4 +- adapters/gumgum/gumgum_test.go | 2 +- adapters/huaweiads/huaweiads.go | 451 ++-- adapters/huaweiads/huaweiads_test.go | 19 +- .../huaweiadstest/exemplary/banner1.json | 11 +- .../huaweiadstest/exemplary/banner2.json | 5 +- .../huaweiadstest/exemplary/banner3.json | 271 ++ .../exemplary/banner4_mccmnc.json | 269 ++ .../exemplary/banner5_user_geo.json | 272 ++ .../huaweiadstest/exemplary/banner6_imei.json | 262 ++ .../exemplary/bannerAppPromotionType.json | 227 ++ .../exemplary/bannerNonIntegerMccmnc.json | 269 ++ .../exemplary/bannerNotAppPromotionType.json | 227 ++ .../exemplary/bannerTestExtraInfo1.json | 5 +- .../exemplary/bannerTestExtraInfo2.json | 5 +- .../exemplary/bannerTestExtraInfo3.json | 5 +- .../exemplary/bannerWrongMccmnc.json | 269 ++ .../exemplary/interstitialBannerType.json | 228 ++ .../exemplary/interstitialVideoType.json | 324 +++ .../exemplary/nativeIncludeVideo.json | 7 +- .../exemplary/nativeSingleImage.json | 9 +- .../exemplary/nativeThreeImage.json | 7 +- .../nativeThreeImageIncludeIcon.json | 7 +- .../exemplary/rewardedVideo.json | 324 +++ .../exemplary/rewardedVideo1.json | 315 +++ .../exemplary/rewardedVideo2.json | 299 +++ .../exemplary/rewardedVideo3.json | 306 +++ .../exemplary/rewardedVideo4.json | 305 +++ .../huaweiadstest/exemplary/rollVideo.json | 329 +++ .../huaweiadstest/exemplary/video.json | 13 +- .../adtype_roll_missing_duration.json | 4 +- .../bad_request_incorrect_huawei_adtype1.json | 84 + .../bad_request_incorrect_huawei_adtype2.json | 78 + .../bad_request_incorrect_huawei_adtype3.json | 89 + .../bad_request_missing_all_type.json | 74 + .../bad_request_no_support_audio.json | 76 + .../supplemental/bad_response.json | 7 +- .../supplemental/bad_response_400.json | 5 +- .../supplemental/bad_response_503.json | 5 +- .../bad_response_dont_find_impid.json | 7 +- .../bad_response_incorrect_huawei_adtype.json | 227 ++ .../supplemental/bad_response_not_intent.json | 227 ++ .../supplemental/bad_response_not_native.json | 11 +- .../bad_response_retcode30_204.json | 5 +- .../bad_response_retcode_210.json | 5 +- .../bad_response_retcode_408.json | 5 +- .../bad_response_retcode_500.json | 5 +- .../supplemental/missing_deviceid1.json | 66 + .../supplemental/missing_deviceid2.json | 75 + .../supplemental/missing_deviceid3.json | 77 + .../supplemental/missing_native_request.json | 2 +- adapters/huaweiads/mcc_list.go | 243 ++ adapters/impactify/impactify.go | 10 +- adapters/impactify/impactify_test.go | 2 +- adapters/improvedigital/improvedigital.go | 79 +- .../improvedigital/improvedigital_test.go | 2 +- .../supplemental/dealid.json | 594 +++++ .../supplemental/rewarded-inventory.json | 177 ++ adapters/infoawarebidder.go | 28 +- adapters/infoawarebidder_test.go | 6 +- adapters/infytv/infytv.go | 90 + adapters/infytv/infytv_test.go | 18 + adapters/infytv/infytvtest/exemplary/app.json | 258 ++ .../infytv/infytvtest/exemplary/video.json | 280 ++ .../infytvtest/supplemental/bad-response.json | 202 ++ .../supplemental/empty-seatbid.json | 207 ++ .../infytvtest/supplemental/status-204.json | 196 ++ .../infytvtest/supplemental/status-400.json | 202 ++ .../infytvtest/supplemental/status-503.json | 195 ++ .../supplemental/unexpected-status.json | 201 ++ adapters/infytv/params_test.go | 44 + adapters/inmobi/inmobi.go | 4 +- adapters/inmobi/inmobi_test.go | 2 +- .../interactiveoffers/interactiveoffers.go | 4 +- .../interactiveoffers_test.go | 2 +- adapters/invibes/invibes.go | 4 +- adapters/invibes/invibes_test.go | 4 +- adapters/iqzone/iqzone.go | 4 +- adapters/iqzone/iqzone_test.go | 2 +- adapters/ix/ix.go | 111 +- adapters/ix/ix_test.go | 132 +- .../multi-format-with-ext-prebid-type.json | 154 ++ .../exemplary/multi-format-with-mtype.json | 150 ++ .../ix/ixtest/supplemental/bad-imp-id.json | 27 - adapters/jixie/jixie.go | 4 +- adapters/jixie/jixie_test.go | 2 +- adapters/kargo/kargo.go | 90 + adapters/kargo/kargo_test.go | 20 + .../kargo/kargotest/exemplary/banner.json | 207 ++ .../kargo/kargotest/exemplary/native.json | 220 ++ adapters/kargo/kargotest/exemplary/video.json | 224 ++ .../supplemental/status-bad-request.json | 36 + .../supplemental/status-no-content.json | 130 + adapters/kargo/params_test.go | 47 + adapters/kayzen/kayzen.go | 4 +- adapters/kayzen/kayzen_test.go | 4 +- adapters/kidoz/kidoz.go | 4 +- adapters/kidoz/kidoz_test.go | 8 +- adapters/krushmedia/krushmedia.go | 4 +- adapters/krushmedia/krushmedia_test.go | 4 +- adapters/kubient/kubient.go | 4 +- adapters/kubient/kubient_test.go | 2 +- adapters/lockerdome/lockerdome.go | 4 +- adapters/lockerdome/lockerdome_test.go | 2 +- adapters/lockerdome/params_test.go | 2 +- adapters/logicad/logicad.go | 9 +- adapters/logicad/logicad_test.go | 2 +- adapters/lunamedia/lunamedia.go | 10 +- adapters/lunamedia/lunamedia_test.go | 4 +- adapters/madvertise/madvertise.go | 13 +- adapters/madvertise/madvertise_test.go | 4 +- adapters/madvertise/params_test.go | 2 +- adapters/marsmedia/marsmedia.go | 4 +- adapters/marsmedia/marsmedia_test.go | 2 +- adapters/marsmedia/params_test.go | 2 +- adapters/medianet/medianet.go | 4 +- adapters/medianet/medianet_test.go | 7 +- adapters/mgid/mgid.go | 4 +- adapters/mgid/mgid_test.go | 2 +- adapters/mobfoxpb/mobfoxpb.go | 4 +- adapters/mobfoxpb/mobfoxpb_test.go | 2 +- adapters/mobilefuse/mobilefuse.go | 32 +- adapters/mobilefuse/mobilefuse_test.go | 4 +- .../exemplary/multi-format.json | 11 + .../mobilefusetest/exemplary/multi-imps.json | 7 +- .../exemplary/simple-banner.json | 7 +- .../exemplary/simple-video.json | 7 +- adapters/nanointeractive/nanointeractive.go | 4 +- .../nanointeractive/nanointeractive_test.go | 2 +- adapters/nanointeractive/params_test.go | 2 +- adapters/nextmillennium/nextmillennium.go | 43 +- .../nextmillennium/nextmillennium_test.go | 2 +- .../exemplary/banner-empty-group-id.json | 26 +- .../exemplary/banner-with-group-id.json | 36 + .../exemplary/banner-with-group-id_app.json | 31 +- .../exemplary/banner-with-only-width.json | 20 +- .../exemplary/banner-with-wh.json | 21 +- .../exemplary/banner-wo-domain.json | 28 +- .../exemplary/banner-wo-size.json | 26 +- .../nextmillenniumtest/exemplary/banner.json | 28 +- .../exemplary/empty-banner-obj.json | 15 +- .../supplemental/empty-seatbid.json | 28 +- .../supplemental/error-response.json | 24 +- .../supplemental/no-content.json | 24 +- adapters/ninthdecimal/ninthdecimal.go | 10 +- adapters/ninthdecimal/ninthdecimal_test.go | 4 +- adapters/nobid/nobid.go | 4 +- adapters/nobid/nobid_test.go | 2 +- adapters/onetag/onetag.go | 4 +- adapters/onetag/onetag_test.go | 4 +- adapters/openrtb_util.go | 26 +- adapters/openrtb_util_test.go | 52 +- adapters/openweb/openweb.go | 4 +- adapters/openweb/openweb_test.go | 2 +- adapters/openweb/params_test.go | 2 +- adapters/openx/openx.go | 6 +- adapters/openx/openx_test.go | 6 +- adapters/openx/params_test.go | 2 +- adapters/operaads/operaads.go | 4 +- adapters/operaads/operaads_test.go | 2 +- adapters/operaads/params_test.go | 2 +- adapters/orbidder/orbidder.go | 10 +- adapters/orbidder/orbidder_test.go | 9 +- adapters/orbidder/params_test.go | 5 +- adapters/outbrain/outbrain.go | 8 +- adapters/outbrain/outbrain_test.go | 2 +- adapters/pangle/pangle.go | 6 +- adapters/pangle/pangle_test.go | 2 +- .../exemplary/app_video_rewarded.json | 4 +- .../supplemental/appid_placementid_check.json | 4 +- adapters/playwire/playwire.go | 4 +- adapters/playwire_ortb/playwire_ortb.go | 4 +- adapters/pubmatic/params_test.go | 2 +- adapters/pubmatic/pubmatic.go | 288 ++- adapters/pubmatic/pubmatic_test.go | 487 +++- .../pubmatictest/exemplary/banner.json | 13 +- .../pubmatictest/exemplary/native.json | 115 + .../pubmatictest/exemplary/video.json | 7 +- .../pubmatictest/supplemental/app.json | 9 +- .../pubmatictest/supplemental/audio.json | 2 +- .../supplemental/dctrAndPmZoneID.json | 3 +- .../pubmatictest/supplemental/extra-bid.json | 225 ++ .../supplemental/gptSlotNameInImpExt.json | 3 +- .../gptSlotNameInImpExtPbAdslot.json | 3 +- .../pubmatictest/supplemental/impExtData.json | 156 ++ .../supplemental/multiplemedia.json | 3 + .../supplemental/native_invalid_adm.json | 121 + .../pubmatictest/supplemental/nilReqExt.json | 3 +- .../pubmatictest/supplemental/noAdSlot.json | 3 +- .../supplemental/pmZoneIDInKeywords.json | 3 +- .../supplemental/reqBidderParams.json | 8 + .../supplemental/trimPublisherID.json | 3 +- adapters/pubnative/pubnative.go | 4 +- adapters/pubnative/pubnative_test.go | 2 +- adapters/pulsepoint/pulsepoint.go | 4 +- adapters/pulsepoint/pulsepoint_test.go | 2 +- adapters/revcontent/revcontent.go | 4 +- adapters/revcontent/revcontent_test.go | 2 +- adapters/rhythmone/rhythmone.go | 4 +- adapters/rhythmone/rhythmone_test.go | 2 +- adapters/richaudience/richaudience.go | 4 +- adapters/richaudience/richaudience_test.go | 12 +- adapters/rtbhouse/rtbhouse.go | 4 +- adapters/rtbhouse/rtbhouse_test.go | 2 +- adapters/rubicon/rubicon.go | 353 ++- adapters/rubicon/rubicon_test.go | 299 +-- .../rubicontest/exemplary/simple-banner.json | 380 +++ .../rubicontest/exemplary/simple-native.json | 385 +++ adapters/sa_lunamedia/salunamedia.go | 8 +- adapters/sa_lunamedia/salunamedia_test.go | 2 +- adapters/seedingAlliance/params_test.go | 43 + adapters/seedingAlliance/seedingAlliance.go | 145 ++ .../seedingAlliance/seedingAlliance_test.go | 194 ++ .../seedingAlliancetest/exemplary/banner.json | 140 + .../seedingAlliancetest/exemplary/native.json | 109 + .../supplemental/invalid_tag_id.json | 30 + .../supplemental/status_bad_request.json | 72 + .../supplemental/status_no_content.json | 66 + .../supplemental/status_not_ok.json | 72 + adapters/sharethrough/sharethrough.go | 4 +- adapters/sharethrough/sharethrough_test.go | 5 +- adapters/silvermob/silvermob.go | 4 +- adapters/silvermob/silvermob_test.go | 4 +- adapters/smaato/native.go | 27 + adapters/smaato/native_test.go | 42 + adapters/smaato/smaato.go | 30 +- adapters/smaato/smaato_test.go | 12 +- .../exemplary/multiple-impressions.json | 4 +- .../exemplary/multiple-media-types.json | 4 +- .../smaato/smaatotest/exemplary/native.json | 150 ++ .../exemplary/simple-banner-app.json | 2 +- .../simple-banner-richMedia-app.json | 2 +- .../exemplary/simple-banner-richMedia.json | 2 +- .../smaatotest/exemplary/simple-banner.json | 2 +- .../smaatotest/exemplary/video-app.json | 2 +- .../smaato/smaatotest/exemplary/video.json | 2 +- .../supplemental/adtype-header-response.json | 2 +- .../supplemental/bad-adm-response.json | 2 +- .../bad-adtype-header-response.json | 2 +- .../bad-expires-header-response.json | 2 +- .../supplemental/bad-media-type-request.json | 4 +- .../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 | 2 +- .../videosupplemental/bad-adm-response.json | 2 +- .../bad-bid-ext-response.json | 2 +- adapters/smartadserver/params_test.go | 2 +- adapters/smartadserver/smartadserver.go | 9 +- adapters/smartadserver/smartadserver_test.go | 2 +- .../exemplary/native-app.json | 270 ++ .../exemplary/native-web.json | 273 ++ adapters/smarthub/smarthub.go | 4 +- adapters/smarthub/smarthub_test.go | 2 +- adapters/smartrtb/smartrtb.go | 4 +- adapters/smartrtb/smartrtb_test.go | 4 +- adapters/smartyads/smartyads.go | 4 +- adapters/smartyads/smartyads_test.go | 4 +- adapters/smilewanted/params_test.go | 2 +- adapters/smilewanted/smilewanted.go | 4 +- adapters/smilewanted/smilewanted_test.go | 2 +- adapters/sonobi/params_test.go | 5 +- adapters/sonobi/sonobi.go | 4 +- adapters/sonobi/sonobi_test.go | 2 +- adapters/sovrn/sovrn.go | 4 +- adapters/sovrn/sovrn_test.go | 2 +- adapters/sspBC/sspbc.go | 398 +++ adapters/sspBC/sspbc_test.go | 20 + .../exemplary/banner-fromtemplate.json | 152 ++ .../banner-preformatted-multiple-imps.json | 235 ++ .../banner-preformatted-onecode.json | 146 ++ .../exemplary/banner-preformatted.json | 152 ++ .../bad_request_without_site.json | 42 + .../sspbctest/supplemental/bad_response.json | 99 + .../bad_response_with_incorrect_impid.json | 135 + .../bad_response_without_adm.json | 129 + .../request_with_diffrent_siteid.json | 153 ++ ...request_with_incorrect_imp_bidder_ext.json | 148 ++ .../request_with_incorrect_imp_ext.json | 146 ++ ...request_with_standard_and_onecode_imp.json | 229 ++ .../supplemental/request_with_test.json | 155 ++ .../request_without_banner_format.json | 141 + .../supplemental/request_without_ext_id.json | 150 ++ .../request_without_ext_site_id.json | 151 ++ .../sspbctest/supplemental/status_204.json | 95 + .../sspbctest/supplemental/status_400.json | 100 + adapters/stroeerCore/params_test.go | 49 + adapters/stroeerCore/stroeercore.go | 117 + adapters/stroeerCore/stroeercore_test.go | 20 + .../exemplary/mobile-banner-single.json | 188 ++ .../exemplary/site-banner-multi.json | 188 ++ .../exemplary/site-banner-single.json | 132 + .../supplemental/bad-server-response.json | 59 + adapters/suntContent/params_test.go | 43 + adapters/suntContent/suntContent.go | 145 ++ adapters/suntContent/suntContent_test.go | 194 ++ .../suntContenttest/exemplary/banner.json | 140 + .../suntContenttest/exemplary/native.json | 109 + .../supplemental/invalid_tag_id.json | 30 + .../supplemental/status_bad_request.json | 72 + .../supplemental/status_no_content.json | 66 + .../supplemental/status_not_ok.json | 72 + adapters/synacormedia/params_test.go | 2 +- adapters/synacormedia/synacormedia.go | 4 +- adapters/synacormedia/synacormedia_test.go | 4 +- adapters/taboola/params_test.go | 51 + adapters/taboola/taboola.go | 173 ++ adapters/taboola/taboola_test.go | 22 + .../taboola/taboolatest/exemplary/banner.json | 152 ++ .../bidParamsOverrideRequestFields.json | 156 ++ .../supplemental/bidderServerError.json | 133 + .../supplemental/emptyReponseFromBidder.json | 101 + .../supplemental/emptySiteInRequest.json | 150 ++ .../incorrectResponseImpMapping.json | 132 + .../supplemental/multiImpressionsRequest.json | 206 ++ .../supplemental/noValidImpression.json | 47 + .../supplemental/optionalParamsProvided.json | 112 + .../supplemental/unexpectedStatusCode.json | 133 + adapters/tappx/tappx.go | 8 +- adapters/tappx/tappx_test.go | 8 +- .../single-banner-impression-extra.json | 2 +- ...ngle-banner-impression-future-feature.json | 2 +- .../exemplary/single-banner-impression.json | 2 +- .../exemplary/single-banner-site.json | 2 +- .../exemplary/single-video-impression.json | 2 +- .../exemplary/single-video-site.json | 2 +- .../tappxtest/supplemental/204status.json | 2 +- .../tappxtest/supplemental/bidfloor.json | 2 +- .../supplemental/http-err-status.json | 2 +- .../supplemental/http-err-status2.json | 2 +- adapters/telaria/telaria.go | 10 +- adapters/telaria/telaria_test.go | 4 +- adapters/trafficgate/trafficgate.go | 4 +- adapters/trafficgate/trafficgate_test.go | 4 +- adapters/triplelift/triplelift.go | 6 +- adapters/triplelift/triplelift_test.go | 2 +- .../supplemental/video-format-11.json | 118 + .../supplemental/video-format-12.json | 118 + .../supplemental/video-format-17.json | 118 + .../triplelift_native/triplelift_native.go | 4 +- .../triplelift_native_test.go | 6 +- adapters/ucfunnel/params_test.go | 5 +- adapters/ucfunnel/ucfunnel.go | 4 +- adapters/ucfunnel/ucfunnel_test.go | 6 +- adapters/unicorn/params_test.go | 29 +- adapters/unicorn/unicorn.go | 42 +- adapters/unicorn/unicorn_test.go | 2 +- .../banner-app-no-app-publisher.json | 228 ++ .../exemplary/banner-app-no-mediaid.json | 228 ++ .../exemplary/banner-app-no-publisherid.json | 229 ++ .../exemplary/banner-app-no-source.json | 15 +- .../exemplary/banner-app-with-ip.json | 15 +- .../exemplary/banner-app-with-ipv6.json | 15 +- .../exemplary/banner-app-without-ext.json | 13 +- .../banner-app-without-placementid.json | 15 +- .../unicorntest/exemplary/banner-app.json | 15 +- .../exemplary/banner-app_with_fpd.json | 15 +- .../exemplary/banner-app_with_no_fpd.json | 15 +- .../unicorn/unicorntest/supplemental/204.json | 15 +- .../unicorn/unicorntest/supplemental/400.json | 15 +- .../unicorn/unicorntest/supplemental/500.json | 15 +- .../supplemental/cannot-parse-accountid.json | 75 + .../supplemental/ccpa-is-enabled.json | 4 +- .../supplemental/coppa-is-enabled.json | 4 +- .../supplemental/gdpr-is-enabled.json | 4 +- .../supplemental/no-accountid.json | 74 + .../unicorntest/supplemental/no-app.json | 71 + .../supplemental/no-imp-ext-prebid.json | 4 +- .../supplemental/no-storedrequest-imp.json | 4 +- adapters/unruly/unruly.go | 4 +- adapters/unruly/unruly_test.go | 2 +- adapters/videobyte/params_test.go | 2 +- adapters/videobyte/videobyte.go | 4 +- adapters/videobyte/videobyte_test.go | 2 +- adapters/vidoomy/params_test.go | 2 +- adapters/vidoomy/vidoomy.go | 8 +- adapters/vidoomy/vidoomy_test.go | 4 +- adapters/visx/visx.go | 14 +- adapters/visx/visx_test.go | 2 +- .../visx/visxtest/exemplary/headers_ipv4.json | 177 ++ .../visx/visxtest/exemplary/headers_ipv6.json | 177 ++ adapters/vrtcal/params_test.go | 2 +- adapters/vrtcal/vrtcal.go | 4 +- adapters/vrtcal/vrtcal_test.go | 2 +- .../exemplary/web-simple-banner.json | 102 + .../exemplary/web-simple-video.json | 98 + adapters/yahoossp/yahoossp.go | 4 +- adapters/yahoossp/yahoossp_test.go | 4 +- adapters/yeahmobi/yeahmobi.go | 4 +- adapters/yeahmobi/yeahmobi_test.go | 4 +- adapters/yieldlab/params_test.go | 19 +- adapters/yieldlab/yieldlab.go | 108 +- adapters/yieldlab/yieldlab_test.go | 133 +- .../yieldlabtest/exemplary/banner.json | 7 +- .../yieldlab/yieldlabtest/exemplary/gdpr.json | 3 +- .../yieldlabtest/exemplary/mixed_types.json | 140 + .../exemplary/multiple_impressions.json | 4 +- .../yieldlabtest/exemplary/schain.json | 134 + .../exemplary/schain_multiple_nodes.json | 132 + .../yieldlabtest/exemplary/video.json | 9 - .../yieldlabtest/exemplary/video_app.json | 9 - adapters/yieldmo/params_test.go | 2 +- adapters/yieldmo/yieldmo.go | 4 +- adapters/yieldmo/yieldmo_test.go | 2 +- adapters/yieldone/yieldone.go | 7 +- adapters/yieldone/yieldone_test.go | 2 +- adapters/zeroclickfraud/zeroclickfraud.go | 4 +- .../zeroclickfraud/zeroclickfraud_test.go | 4 +- amp/parse.go | 184 +- amp/parse_test.go | 431 +++- analytics/config/config.go | 11 +- analytics/config/config_test.go | 2 +- analytics/core.go | 12 +- analytics/filesystem/file_module.go | 16 +- analytics/filesystem/file_module_test.go | 2 +- analytics/pubstack/config.go | 25 +- analytics/pubstack/config_test.go | 70 +- analytics/pubstack/configupdate.go | 61 + analytics/pubstack/configupdate_test.go | 105 + .../pubstack/eventchannel/eventchannel.go | 11 +- .../eventchannel/eventchannel_test.go | 168 +- .../pubstack/eventchannel/sender_test.go | 10 +- analytics/pubstack/helpers/json_test.go | 32 +- analytics/pubstack/pubstack_module.go | 113 +- analytics/pubstack/pubstack_module_test.go | 115 +- config/accounts.go | 185 +- config/accounts_test.go | 541 ++-- config/adapter.go | 80 +- config/bidderinfo.go | 518 +++- config/bidderinfo_test.go | 804 +++++- config/bidderinfo_validate_test.go | 161 -- config/config.go | 701 ++--- config/config_test.go | 2266 ++++++++++++++--- config/experiment.go | 91 + config/experiment_test.go | 134 + config/hooks.go | 34 + config/stored_requests.go | 192 +- config/stored_requests_test.go | 171 +- .../stroeerCore.yaml} | 0 currency/rate_converter.go | 4 +- currency/rate_converter_test.go | 4 +- currency/rates.go | 8 +- docs/adscertsigner.md | 90 + docs/developers/stored-requests.md | 47 +- endpoints/cookie_sync.go | 100 +- endpoints/cookie_sync_test.go | 400 ++- endpoints/events/account_test.go | 47 +- endpoints/events/event.go | 4 +- endpoints/events/event_test.go | 29 +- endpoints/events/vtrack.go | 5 +- endpoints/events/vtrack_test.go | 30 +- endpoints/info/bidders.go | 4 +- endpoints/info/bidders_detail.go | 27 +- endpoints/info/bidders_detail_test.go | 208 +- endpoints/info/bidders_test.go | 16 +- endpoints/openrtb2/amp_auction.go | 312 ++- endpoints/openrtb2/amp_auction_test.go | 693 ++++- endpoints/openrtb2/auction.go | 796 ++++-- endpoints/openrtb2/auction_benchmark_test.go | 23 +- endpoints/openrtb2/auction_test.go | 705 +++-- endpoints/openrtb2/interstitial.go | 13 +- endpoints/openrtb2/interstitial_test.go | 40 +- .../account-malformed/malformed-acct.json | 65 + .../addtl-consent-through-query.json | 92 + .../gdpr-ccpa-through-query.json | 137 + ...dpr-legacy-tcf2-consent-through-query.json | 142 ++ .../gdpr-tcf1-consent-through-query.json | 142 ++ .../gdpr-tcf2-consent-through-query.json | 142 ++ .../aliased-buyeruids.json | 31 +- .../amp/valid-supplementary/aliases.json | 71 + .../gdpr-no-consentstring.json | 35 +- .../valid-supplementary}/gdpr.json | 34 +- .../imp-with-stored-resp.json | 25 +- .../disabled/bad/bad-bidder.json | 2 +- .../invalid-whole/imp-ext-empty.json | 2 +- .../invalid-whole/imp-ext-invalid-params.json | 2 +- .../invalid-whole/imp-ext-unknown-bidder.json | 2 +- .../invalid-whole/regs-ext-gdpr-invalid.json | 2 +- .../invalid-whole/regs-ext-gdpr-string.json | 2 +- .../user-ext-eids-eids-uids-empty.json | 55 - .../invalid-whole/user-ext-eids-empty.json | 53 +- .../user-ext-eids-source-duplicate.json | 49 + .../user-ext-eids-source-empty.json | 61 +- .../user-ext-eids-source-unique.json | 59 - .../user-ext-eids-uids-id-empty.json | 65 +- ...y.json => user-ext-eids-uids-missing.json} | 51 +- .../valid-whole/hooks/auction_reject.json | 43 + ...dder-params-backward-compatible-merge.json | 21 +- endpoints/openrtb2/test_utils.go | 348 ++- endpoints/openrtb2/video_auction.go | 81 +- endpoints/openrtb2/video_auction_test.go | 142 +- endpoints/setuid.go | 52 +- endpoints/setuid_test.go | 256 +- endpoints/version_test.go | 4 +- errortypes/code.go | 3 + errortypes/errortypes.go | 18 + exchange/adapter_builders.go | 45 +- exchange/adapter_util.go | 45 +- exchange/adapter_util_test.go | 148 +- exchange/auction.go | 2 +- exchange/auction_test.go | 8 +- exchange/bidder.go | 160 +- exchange/bidder_test.go | 983 ++++++- exchange/bidder_validate_bids.go | 16 +- exchange/bidder_validate_bids_test.go | 71 +- exchange/events_test.go | 2 +- exchange/exchange.go | 184 +- exchange/exchange_test.go | 929 +++++-- exchange/exchangetest/aliases.json | 62 +- .../exchangetest/append-bidder-names.json | 103 +- .../exchangetest/bid-consolidation-test.json | 192 +- .../bid-ext-prebid-collision.json | 123 +- exchange/exchangetest/bid-ext.json | 123 +- exchange/exchangetest/bid-id-invalid.json | 61 +- exchange/exchangetest/bid-id-valid.json | 61 +- .../exchangetest/bidadjustmentfactors.json | 74 +- .../exchangetest/ccpa-featureflag-off.json | 55 +- .../exchangetest/ccpa-featureflag-on.json | 58 +- .../exchangetest/ccpa-nosale-any-bidder.json | 65 +- .../ccpa-nosale-specific-bidder.json | 65 +- exchange/exchangetest/debuglog_disabled.json | 103 +- exchange/exchangetest/debuglog_enabled.json | 103 +- .../debuglog_enabled_no_bids.json | 20 +- .../eidpermissions-allowed-alias.json | 162 +- .../exchangetest/eidpermissions-allowed.json | 159 +- .../exchangetest/eidpermissions-denied.json | 152 +- .../events-bid-account-off-request-off.json | 66 +- .../events-bid-account-off-request-on.json | 65 +- .../events-bid-account-on-request-off.json | 65 +- .../events-vast-account-off-request-off.json | 118 +- .../events-vast-account-off-request-on.json | 120 +- .../events-vast-account-on-request-off.json | 120 +- exchange/exchangetest/explicit-buyeruid.json | 25 +- .../extra-bids-with-aliases-adaptercode.json | 247 ++ exchange/exchangetest/extra-bids.json | 461 ++++ ...rstpartydata-imp-ext-multiple-bidders.json | 185 -- ...ydata-imp-ext-multiple-prebid-bidders.json | 290 ++- .../firstpartydata-imp-ext-one-bidder.json | 111 - ...stpartydata-imp-ext-one-prebid-bidder.json | 166 +- ...tpartydata-multibidder-config-invalid.json | 53 +- ...rstpartydata-multibidder-config-valid.json | 37 +- .../exchangetest/gdpr-geo-eu-off-device.json | 60 +- exchange/exchangetest/gdpr-geo-eu-off.json | 55 +- .../gdpr-geo-eu-on-featureflag-off.json | 55 +- exchange/exchangetest/gdpr-geo-eu-on.json | 55 +- exchange/exchangetest/gdpr-geo-usa-off.json | 55 +- exchange/exchangetest/gdpr-geo-usa-on.json | 55 +- .../exchangetest/include-brand-category.json | 155 +- .../exchangetest/lmt-featureflag-off.json | 55 +- exchange/exchangetest/lmt-featureflag-on.json | 58 +- .../exchangetest/passthrough_imp_only.json | 174 ++ .../passthrough_root_and_imp.json | 137 + .../exchangetest/passthrough_root_only.json | 131 + .../request-ext-prebid-filtering.json | 169 ++ .../request-imp-ext-prebid-filtering.json | 136 + .../request-multi-bidders-debug-info.json | 115 +- .../request-multi-bidders-one-no-resp.json | 137 +- exchange/exchangetest/request-no-user.json | 25 +- .../exchangetest/request-other-user-ext.json | 25 +- exchange/exchangetest/request-other-user.json | 25 +- .../exchangetest/request-user-no-prebid.json | 31 +- .../exchangetest/schain-host-and-request.json | 118 + exchange/exchangetest/schain-host-only.json | 92 + .../targeting-cache-vast-banner.json | 91 +- .../exchangetest/targeting-cache-vast.json | 113 +- .../exchangetest/targeting-cache-zero.json | 118 +- exchange/exchangetest/targeting-mobile.json | 314 +-- .../exchangetest/targeting-no-winners.json | 284 ++- .../exchangetest/targeting-only-winners.json | 272 +- .../exchangetest/targeting-with-winners.json | 304 ++- exchange/exchangetest/tmax.json | 29 +- exchange/exchangetest/tricky-userids.json | 89 +- exchange/gdpr.go | 21 +- exchange/gdpr_test.go | 108 +- exchange/targeting.go | 2 +- exchange/targeting_test.go | 36 +- exchange/utils.go | 251 +- exchange/utils_test.go | 845 ++++-- experiment/adscert/SignerLogger.go | 38 + experiment/adscert/inprocesssigner.go | 42 + experiment/adscert/inprocesssigner_test.go | 50 + experiment/adscert/remotesigner.go | 44 + experiment/adscert/remotesigner_test.go | 50 + experiment/adscert/signer.go | 51 + experiment/adscert/signer_test.go | 54 + firstpartydata/first_party_data.go | 101 +- firstpartydata/first_party_data_test.go | 27 +- .../req-empty-user-fpd-not-empty-user.json | 80 + ...obal-and-non-global-app-content-data.json} | 27 +- ...bal-and-non-global-site-content-data.json} | 27 +- ...wo-bidders-global-and-non-global-user.json | 27 +- ...ut-config-and-global-app-content-data.json | 187 ++ ...t-config-and-global-site-content-data.json | 192 ++ .../two-bidders-with-incorrect-fpd.json | 1 - ...=> bidder-fpd-site-content-data-only.json} | 51 +- ...lobal-and-no-bidder-fpd-site-app-user.json | 130 + ...d-no-bidder-fpd-site-content-app-user.json | 118 + .../tests/resolvefpd/global-fpd-only-app.json | 120 + ...pp-user.json => global-fpd-only-site.json} | 0 .../req-and-bidder-fpd-site-content.json | 147 ++ gdpr/aggregated_config.go | 56 +- gdpr/aggregated_config_test.go | 240 +- gdpr/basic_enforcement.go | 58 + gdpr/basic_enforcement_test.go | 240 ++ gdpr/full_enforcement.go | 97 + gdpr/full_enforcement_test.go | 928 +++++++ gdpr/gdpr.go | 45 +- gdpr/gdpr_test.go | 15 +- gdpr/impl.go | 277 +- gdpr/impl_test.go | 403 +-- gdpr/purpose_config.go | 38 + gdpr/purpose_config_test.go | 145 ++ gdpr/purpose_enforcer.go | 111 + gdpr/purpose_enforcer_test.go | 237 ++ gdpr/vendorlist-fetching.go | 4 +- go.mod | 60 +- go.sum | 190 +- hooks/empty_plan.go | 38 + hooks/empty_plan_test.go | 21 + hooks/hookanalytics/analytics.go | 45 + hooks/hookanalytics/analytics_test.go | 56 + hooks/hookexecution/context.go | 72 + hooks/hookexecution/errors.go | 65 + hooks/hookexecution/errors_test.go | 84 + hooks/hookexecution/execution.go | 312 +++ hooks/hookexecution/executor.go | 206 ++ hooks/hookexecution/executor_test.go | 1255 +++++++++ hooks/hookexecution/mocks_test.go | 200 ++ hooks/hookexecution/outcome.go | 67 + hooks/hookexecution/outcome_test.go | 51 + hooks/hookstage/allprocessedbidresponses.go | 28 + hooks/hookstage/auctionresponse.go | 30 + hooks/hookstage/bidderrequest.go | 29 + hooks/hookstage/entrypoint.go | 30 + hooks/hookstage/invocation.go | 34 + hooks/hookstage/mutation.go | 54 + hooks/hookstage/processedauctionrequest.go | 30 + hooks/hookstage/rawauctionrequest.go | 27 + hooks/hookstage/rawbidderresponse.go | 29 + hooks/plan.go | 206 ++ hooks/plan_test.go | 872 +++++++ hooks/repo.go | 158 ++ hooks/repo_test.go | 76 + main.go | 20 +- main_test.go | 10 +- metrics/config/metrics.go | 111 +- metrics/config/metrics_test.go | 42 +- metrics/go_metrics.go | 286 ++- metrics/go_metrics_test.go | 302 ++- metrics/metrics.go | 67 +- metrics/metrics_mock.go | 45 + metrics/prometheus/preload.go | 48 +- metrics/prometheus/prometheus.go | 219 +- metrics/prometheus/prometheus_test.go | 252 +- modules/builder.go | 7 + modules/generator/builder.tmpl | 21 + modules/generator/buildergen.go | 70 + modules/helpers.go | 73 + modules/modules.go | 78 + modules/modules_test.go | 120 + openrtb_ext/alternatebiddercodes.go | 58 + openrtb_ext/alternatebiddercodes_test.go | 180 ++ openrtb_ext/app.go | 7 +- openrtb_ext/bid.go | 3 + openrtb_ext/bid_request_video.go | 2 +- openrtb_ext/bidders.go | 67 +- openrtb_ext/bidders_test.go | 4 + openrtb_ext/bidders_validate_test.go | 2 +- openrtb_ext/convert_down.go | 179 ++ openrtb_ext/convert_down_test.go | 408 +++ openrtb_ext/convert_up.go | 181 ++ openrtb_ext/convert_up_test.go | 450 ++++ openrtb_ext/deal_tier.go | 2 +- openrtb_ext/deal_tier_test.go | 2 +- openrtb_ext/device.go | 2 +- openrtb_ext/imp.go | 15 +- openrtb_ext/imp_33across.go | 2 +- openrtb_ext/imp_adkernel.go | 2 +- openrtb_ext/imp_adkernelAdn.go | 2 +- openrtb_ext/imp_adnuntius.go | 5 +- openrtb_ext/imp_adrino.go | 5 + openrtb_ext/imp_adtarget.go | 2 +- openrtb_ext/imp_adtelligent.go | 2 +- openrtb_ext/imp_adtrgtme.go | 4 + openrtb_ext/imp_adyoulike.go | 2 +- openrtb_ext/imp_algorix.go | 2 +- openrtb_ext/imp_appnexus.go | 4 +- openrtb_ext/imp_appush.go | 6 + openrtb_ext/imp_avocet.go | 2 +- openrtb_ext/imp_beyondmedia.go | 5 + openrtb_ext/imp_bidstack.go | 5 + openrtb_ext/imp_boldwin.go | 6 + openrtb_ext/imp_brightroll.go | 2 +- openrtb_ext/imp_ccx.go | 5 + openrtb_ext/imp_consumable.go | 2 +- openrtb_ext/imp_criteo.go | 2 +- openrtb_ext/imp_datablocks.go | 2 +- openrtb_ext/imp_deepintent.go | 2 +- openrtb_ext/imp_dianomi.go | 10 + openrtb_ext/imp_engagebdr.go | 2 +- openrtb_ext/imp_eplanning.go | 2 +- openrtb_ext/imp_freewheelssp.go | 5 + openrtb_ext/imp_gamma.go | 2 +- openrtb_ext/imp_gamoshi.go | 2 +- openrtb_ext/imp_grid.go | 2 +- openrtb_ext/imp_gumgum.go | 6 +- openrtb_ext/imp_impactify.go | 2 +- openrtb_ext/imp_infytv.go | 6 + openrtb_ext/imp_ix.go | 2 +- openrtb_ext/imp_kargo.go | 5 + openrtb_ext/imp_kayzen.go | 2 +- openrtb_ext/imp_kubient.go | 2 +- openrtb_ext/imp_lockerdome.go | 2 +- openrtb_ext/imp_madvertise.go | 2 +- openrtb_ext/imp_marsmedia.go | 2 +- openrtb_ext/imp_mgid.go | 2 +- openrtb_ext/imp_mobilefuse.go | 2 +- openrtb_ext/imp_nanointeractive.go | 2 +- openrtb_ext/imp_openweb.go | 2 +- openrtb_ext/imp_openx.go | 2 +- openrtb_ext/imp_orbidder.go | 2 +- openrtb_ext/imp_outbrain.go | 2 +- openrtb_ext/imp_pubmatic.go | 4 +- openrtb_ext/imp_pulsepoint.go | 2 +- openrtb_ext/imp_rhythmone.go | 2 +- openrtb_ext/imp_rubicon.go | 6 +- openrtb_ext/imp_seedingAlliance.go | 5 + openrtb_ext/imp_silvermob.go | 2 +- openrtb_ext/imp_smaato.go | 2 +- openrtb_ext/imp_smartadserver.go | 2 +- openrtb_ext/imp_smartyads.go | 2 +- openrtb_ext/imp_sspbc.go | 7 + openrtb_ext/imp_stroeercore.go | 5 + openrtb_ext/imp_suntContent.go | 5 + openrtb_ext/imp_synacormedia.go | 2 +- openrtb_ext/imp_taboola.go | 10 + openrtb_ext/imp_triplelift.go | 2 +- openrtb_ext/imp_ucfunnel.go | 2 +- openrtb_ext/imp_unicorn.go | 8 +- openrtb_ext/imp_verizonmedia.go | 2 +- openrtb_ext/imp_videobyte.go | 2 +- openrtb_ext/imp_vrtcal.go | 2 +- openrtb_ext/imp_yahoossp.go | 2 +- openrtb_ext/imp_yeahmobi.go | 2 +- openrtb_ext/imp_yieldlab.go | 3 +- openrtb_ext/imp_yieldmo.go | 2 +- openrtb_ext/imp_yieldone.go | 2 +- openrtb_ext/imp_zeroclickfraud.go | 2 +- openrtb_ext/request.go | 73 +- openrtb_ext/request_test.go | 32 - openrtb_ext/request_wrapper.go | 799 ++++-- openrtb_ext/request_wrapper_test.go | 956 ++++++- openrtb_ext/response.go | 3 +- openrtb_ext/source.go | 4 +- openrtb_ext/user.go | 56 +- pbs/usersync.go | 1 + prebid_cache_client/client.go | 4 +- privacy/ccpa/consentwriter.go | 15 +- privacy/ccpa/consentwriter_test.go | 2 +- privacy/ccpa/policy.go | 46 +- privacy/ccpa/policy_test.go | 173 +- privacy/enforcement.go | 2 +- privacy/enforcement_test.go | 2 +- privacy/gdpr/consentwriter.go | 41 +- privacy/gdpr/consentwriter_test.go | 4 +- privacy/lmt/ios.go | 2 +- privacy/lmt/ios_test.go | 2 +- privacy/lmt/policy.go | 2 +- privacy/lmt/policy_test.go | 2 +- privacy/scrubber.go | 2 +- privacy/scrubber_test.go | 2 +- privacy/writer.go | 2 +- privacy/writer_test.go | 2 +- router/router.go | 134 +- router/router_test.go | 87 +- schain/schain.go | 6 +- schain/schain_test.go | 15 +- schain/schainwriter.go | 37 +- schain/schainwriter_test.go | 56 +- server/listener_test.go | 2 +- server/ssl/ssl.go | 4 +- static/bidder-info/33across.yaml | 1 + static/bidder-info/aax.yaml | 4 +- static/bidder-info/aceex.yaml | 1 + static/bidder-info/acuityads.yaml | 3 +- static/bidder-info/adf.yaml | 1 + static/bidder-info/adform.yaml | 1 + static/bidder-info/adgeneration.yaml | 2 +- static/bidder-info/adhese.yaml | 1 + static/bidder-info/adkernel.yaml | 4 +- static/bidder-info/adkernelAdn.yaml | 2 + static/bidder-info/adman.yaml | 2 + static/bidder-info/admixer.yaml | 1 + static/bidder-info/adnuntius.yaml | 2 + static/bidder-info/adocean.yaml | 3 +- static/bidder-info/adoppler.yaml | 1 + static/bidder-info/adot.yaml | 3 +- static/bidder-info/adpone.yaml | 3 +- static/bidder-info/adprime.yaml | 3 +- static/bidder-info/adrino.yaml | 8 + static/bidder-info/adtarget.yaml | 3 +- static/bidder-info/adtelligent.yaml | 3 +- static/bidder-info/adtrgtme.yaml | 10 + static/bidder-info/advangelists.yaml | 1 + static/bidder-info/adview.yaml | 1 + static/bidder-info/adxcg.yaml | 1 + static/bidder-info/adyoulike.yaml | 1 + static/bidder-info/aja.yaml | 1 + static/bidder-info/algorix.yaml | 1 + static/bidder-info/amx.yaml | 2 + static/bidder-info/apacdex.yaml | 1 + static/bidder-info/applogy.yaml | 1 + static/bidder-info/appnexus.yaml | 2 + static/bidder-info/appush.yaml | 16 + static/bidder-info/audienceNetwork.yaml | 4 +- static/bidder-info/automatad.yaml | 10 + static/bidder-info/avocet.yaml | 1 + static/bidder-info/axonix.yaml | 1 + static/bidder-info/beachfront.yaml | 3 + static/bidder-info/beintoo.yaml | 1 + static/bidder-info/between.yaml | 2 + static/bidder-info/beyondmedia.yaml | 15 + static/bidder-info/bidmachine.yaml | 3 +- static/bidder-info/bidmyadz.yaml | 1 + static/bidder-info/bidscube.yaml | 2 + static/bidder-info/bidstack.yaml | 10 + static/bidder-info/bizzclick.yaml | 1 + static/bidder-info/bliink.yaml | 3 +- static/bidder-info/{yssp.yaml => blue.yaml} | 9 +- static/bidder-info/bmtm.yaml | 3 +- static/bidder-info/boldwin.yaml | 15 + static/bidder-info/brightroll.yaml | 1 + static/bidder-info/ccx.yaml | 14 + static/bidder-info/coinzilla.yaml | 1 + static/bidder-info/colossus.yaml | 3 +- static/bidder-info/compass.yaml | 1 + static/bidder-info/connectad.yaml | 1 + static/bidder-info/consumable.yaml | 3 +- static/bidder-info/conversant.yaml | 1 + static/bidder-info/cpmstar.yaml | 3 +- static/bidder-info/criteo.yaml | 3 +- static/bidder-info/datablocks.yaml | 1 + static/bidder-info/decenterads.yaml | 1 + static/bidder-info/deepintent.yaml | 2 + static/bidder-info/dianomi.yaml | 23 + static/bidder-info/dmx.yaml | 3 +- static/bidder-info/e_volution.yaml | 2 + static/bidder-info/emx_digital.yaml | 1 + static/bidder-info/engagebdr.yaml | 1 + static/bidder-info/engagebdr_ortb.yaml | 2 +- static/bidder-info/eplanning.yaml | 1 + static/bidder-info/epom.yaml | 2 + static/bidder-info/freewheel-ssp.yaml | 16 + static/bidder-info/freewheelssp.yaml | 16 + static/bidder-info/gamma.yaml | 3 +- static/bidder-info/gamoshi.yaml | 1 + static/bidder-info/grid.yaml | 3 + static/bidder-info/groupm.yaml | 2 + static/bidder-info/gumgum.yaml | 1 + static/bidder-info/gumgum_ortb.yaml | 2 +- static/bidder-info/huaweiads.yaml | 5 +- static/bidder-info/impactify.yaml | 1 + static/bidder-info/improvedigital.yaml | 2 + static/bidder-info/infytv.yaml | 10 + static/bidder-info/inmobi.yaml | 2 + static/bidder-info/interactiveoffers.yaml | 1 + static/bidder-info/invibes.yaml | 4 +- static/bidder-info/iqzone.yaml | 3 +- static/bidder-info/ix.yaml | 3 + static/bidder-info/janet.yaml | 1 + static/bidder-info/jixie.yaml | 1 + static/bidder-info/kargo.yaml | 16 + static/bidder-info/kayzen.yaml | 1 + static/bidder-info/kidoz.yaml | 1 + static/bidder-info/krushmedia.yaml | 1 + static/bidder-info/kubient.yaml | 1 + static/bidder-info/lockerdome.yaml | 3 +- static/bidder-info/logicad.yaml | 3 +- static/bidder-info/lunamedia.yaml | 1 + static/bidder-info/madvertise.yaml | 1 + static/bidder-info/marsmedia.yaml | 1 + static/bidder-info/mediafuse.yaml | 9 +- static/bidder-info/medianet.yaml | 2 + static/bidder-info/mgid.yaml | 2 + static/bidder-info/mobfoxpb.yaml | 1 + static/bidder-info/mobilefuse.yaml | 2 + static/bidder-info/nanointeractive.yaml | 1 + static/bidder-info/nextmillennium.yaml | 3 +- static/bidder-info/ninthdecimal.yaml | 1 + static/bidder-info/nobid.yaml | 1 + static/bidder-info/oftmedia.yaml | 18 + static/bidder-info/onetag.yaml | 4 +- static/bidder-info/openweb.yaml | 3 +- static/bidder-info/openx.yaml | 2 + static/bidder-info/operaads.yaml | 1 + static/bidder-info/orbidder.yaml | 1 + static/bidder-info/outbrain.yaml | 1 + static/bidder-info/pangle.yaml | 1 + static/bidder-info/pgam.yaml | 1 + static/bidder-info/playwire.yaml | 2 +- static/bidder-info/playwire_ortb.yaml | 2 +- static/bidder-info/pubmatic.yaml | 3 + static/bidder-info/pubnative.yaml | 1 + static/bidder-info/pulsepoint.yaml | 1 + static/bidder-info/pulsepoint_ortb.yaml | 2 +- static/bidder-info/quantumdex.yaml | 1 + static/bidder-info/revcontent.yaml | 4 +- static/bidder-info/rhythmone.yaml | 3 +- static/bidder-info/richaudience.yaml | 4 + static/bidder-info/rtbhouse.yaml | 3 +- static/bidder-info/rubicon.yaml | 4 + static/bidder-info/sa_lunamedia.yaml | 1 + static/bidder-info/seedingAlliance.yaml | 13 + static/bidder-info/sharethrough.yaml | 1 + static/bidder-info/silvermob.yaml | 3 +- static/bidder-info/smaato.yaml | 4 + static/bidder-info/smartadserver.yaml | 5 +- static/bidder-info/smarthub.yaml | 3 +- static/bidder-info/smartrtb.yaml | 2 + static/bidder-info/smartyads.yaml | 1 + static/bidder-info/smilewanted.yaml | 3 +- static/bidder-info/sonobi.yaml | 1 + static/bidder-info/sovrn.yaml | 5 + static/bidder-info/sspBC.yaml | 13 + static/bidder-info/streamkey.yaml | 1 + static/bidder-info/stroeerCore.yaml | 20 + static/bidder-info/suntContent.yaml | 13 + static/bidder-info/synacormedia.yaml | 3 +- static/bidder-info/taboola.yaml | 15 + static/bidder-info/tappx.yaml | 1 + static/bidder-info/telaria.yaml | 3 +- static/bidder-info/trafficgate.yaml | 1 + static/bidder-info/triplelift.yaml | 4 +- static/bidder-info/triplelift_native.yaml | 3 + static/bidder-info/trustx.yaml | 3 +- static/bidder-info/ucfunnel.yaml | 1 + static/bidder-info/unicorn.yaml | 3 +- static/bidder-info/unruly.yaml | 3 +- static/bidder-info/valueimpression.yaml | 3 +- static/bidder-info/verizonmedia.yaml | 1 + static/bidder-info/videobyte.yaml | 1 + static/bidder-info/vidoomy.yaml | 1 + static/bidder-info/viewdeos.yaml | 3 +- static/bidder-info/visx.yaml | 1 + static/bidder-info/vrtcal.yaml | 7 + static/bidder-info/yahoossp.yaml | 1 + static/bidder-info/yeahmobi.yaml | 3 +- static/bidder-info/yieldlab.yaml | 1 + static/bidder-info/yieldmo.yaml | 3 +- static/bidder-info/yieldone.yaml | 1 + static/bidder-info/zeroclickfraud.yaml | 3 +- static/bidder-params/adnuntius.json | 4 + static/bidder-params/adrino.json | 16 + static/bidder-params/adtrgtme.json | 8 + static/bidder-params/appush.json | 23 + static/bidder-params/automatad.json | 18 + static/bidder-params/beyondmedia.json | 15 + static/bidder-params/bidstack.json | 23 + static/bidder-params/blue.json | 15 + static/bidder-params/boldwin.json | 23 + static/bidder-params/ccx.json | 13 + static/bidder-params/dianomi.json | 20 + static/bidder-params/freewheel-ssp.json | 15 + static/bidder-params/freewheelssp.json | 15 + static/bidder-params/infytv.json | 19 + static/bidder-params/kargo.json | 15 + static/bidder-params/mediafuse.json | 103 +- static/bidder-params/oftmedia.json | 26 + static/bidder-params/seedingAlliance.json | 16 + static/bidder-params/sspBC.json | 27 + static/bidder-params/stroeerCore.json | 14 + static/bidder-params/suntContent.json | 16 + static/bidder-params/taboola.json | 34 + static/bidder-params/unicorn.json | 4 +- static/bidder-params/yieldlab.json | 7 +- static/bidder-params/yssp.json | 19 - .../backends/db_fetcher/fetcher.go | 57 +- .../backends/db_fetcher/fetcher_test.go | 75 +- .../backends/db_provider/db_provider.go | 51 + .../backends/db_provider/db_provider_mock.go | 67 + .../backends/db_provider/db_provider_test.go | 154 ++ .../backends/db_provider/mysql_dbprovider.go | 151 ++ .../db_provider/mysql_dbprovider_test.go | 89 + .../db_provider/postgres_dbprovider.go | 138 + .../db_provider/postgres_dbprovider_test.go | 89 + .../backends/file_fetcher/fetcher.go | 6 +- .../backends/http_fetcher/fetcher.go | 52 +- stored_requests/config/config.go | 118 +- stored_requests/config/config_test.go | 57 +- stored_requests/data/by_id/accounts/test.json | 63 +- stored_requests/events/api/api.go | 6 +- .../events/{postgres => database}/database.go | 45 +- .../{postgres => database}/database_test.go | 27 +- stored_requests/events/http/http.go | 60 +- stored_responses/stored_responses.go | 61 +- stored_responses/stored_responses_test.go | 101 +- usersync/syncersbuilder.go | 2 +- usersync/syncersbuilder_test.go | 48 +- util/jsonutil/jsonutil.go | 4 +- util/task/func_runner.go | 15 + util/task/func_runner_test.go | 28 + version/version.go | 8 +- version/xprebidheader.go | 2 +- version/xprebidheader_test.go | 2 +- 1314 files changed, 71255 insertions(+), 11216 deletions(-) create mode 100644 .github/workflows/issue_prioritization.yml create mode 100644 adapters/adnuntius/adnuntiustest/supplemental/banner-nil-check.json create mode 100644 adapters/adnuntius/adnuntiustest/supplemental/check-noCookies-parameter.json create mode 100644 adapters/adnuntius/adnuntiustest/supplemental/check-noCookies.json create mode 100644 adapters/adnuntius/adnuntiustest/supplemental/check-order-multi-imp.json create mode 100644 adapters/adnuntius/adnuntiustest/supplemental/empty-regs-ext.json create mode 100644 adapters/adnuntius/adnuntiustest/supplemental/empty-regs.json create mode 100644 adapters/adnuntius/adnuntiustest/supplemental/size-check.json create mode 100644 adapters/adrino/adrino.go create mode 100644 adapters/adrino/adrino_test.go create mode 100644 adapters/adrino/adrinotest/exemplary/no-bid.json create mode 100644 adapters/adrino/adrinotest/exemplary/single-native.json create mode 100644 adapters/adrino/adrinotest/supplemental/unknown-hash.json create mode 100644 adapters/adrino/params_test.go create mode 100644 adapters/adtrgtme/adtrgtme.go create mode 100644 adapters/adtrgtme/adtrgtme_test.go create mode 100644 adapters/adtrgtme/adtrgtmetest/exemplary/banner-app.json create mode 100644 adapters/adtrgtme/adtrgtmetest/exemplary/banner-web.json create mode 100644 adapters/adtrgtme/adtrgtmetest/supplemental/banner-app-headers-ipv6.json create mode 100644 adapters/adtrgtme/adtrgtmetest/supplemental/banner-web-headers-ipv6.json create mode 100644 adapters/adtrgtme/adtrgtmetest/supplemental/empty-seatbid-array.json create mode 100644 adapters/adtrgtme/adtrgtmetest/supplemental/invalid-response.json create mode 100644 adapters/adtrgtme/adtrgtmetest/supplemental/not-found-imp.json create mode 100644 adapters/adtrgtme/adtrgtmetest/supplemental/not-provided-app-id.json create mode 100644 adapters/adtrgtme/adtrgtmetest/supplemental/not-provided-site-id.json create mode 100644 adapters/adtrgtme/adtrgtmetest/supplemental/not-provided-site-or-app.json create mode 100644 adapters/adtrgtme/adtrgtmetest/supplemental/status-code-bad-request.json create mode 100644 adapters/adtrgtme/adtrgtmetest/supplemental/status-code-internal-server-error.json create mode 100644 adapters/adtrgtme/adtrgtmetest/supplemental/status-code-no-content.json create mode 100644 adapters/adtrgtme/adtrgtmetest/supplemental/status-code-service-unavaliable.json create mode 100644 adapters/adtrgtme/adtrgtmetest/supplemental/status-code-temporary-redirect.json create mode 100644 adapters/adtrgtme/adtrgtmetest/supplemental/unsupported-bid-type-native.json create mode 100644 adapters/adtrgtme/adtrgtmetest/supplemental/unsupported-bid-type-video.json create mode 100644 adapters/appush/appush.go create mode 100644 adapters/appush/appush_test.go create mode 100644 adapters/appush/appushtest/exemplary/endpointId.json create mode 100644 adapters/appush/appushtest/exemplary/simple-banner.json create mode 100644 adapters/appush/appushtest/exemplary/simple-native.json create mode 100644 adapters/appush/appushtest/exemplary/simple-video.json create mode 100644 adapters/appush/appushtest/exemplary/simple-web-banner.json create mode 100644 adapters/appush/appushtest/supplemental/bad_media_type.json create mode 100644 adapters/appush/appushtest/supplemental/bad_response.json create mode 100644 adapters/appush/appushtest/supplemental/status-204.json create mode 100644 adapters/appush/appushtest/supplemental/status-not-200.json create mode 100644 adapters/appush/params_test.go create mode 100644 adapters/automatad/automatad.go create mode 100644 adapters/automatad/automatad_test.go create mode 100644 adapters/automatad/automatadtest/exemplary/simple-banner.json create mode 100644 adapters/automatad/automatadtest/supplemental/bad-request.json create mode 100644 adapters/automatad/automatadtest/supplemental/error-500-request.json create mode 100644 adapters/automatad/automatadtest/supplemental/no-content.json create mode 100644 adapters/automatad/params_test.go create mode 100644 adapters/beyondmedia/beyondmedia.go create mode 100644 adapters/beyondmedia/beyondmedia_test.go create mode 100644 adapters/beyondmedia/beyondmediatest/exemplary/simple-banner.json create mode 100644 adapters/beyondmedia/beyondmediatest/exemplary/simple-native.json create mode 100644 adapters/beyondmedia/beyondmediatest/exemplary/simple-video.json create mode 100644 adapters/beyondmedia/beyondmediatest/exemplary/simple-web-banner.json create mode 100644 adapters/beyondmedia/beyondmediatest/supplemental/bad_media_type.json create mode 100644 adapters/beyondmedia/beyondmediatest/supplemental/bad_response.json create mode 100644 adapters/beyondmedia/beyondmediatest/supplemental/status-204.json create mode 100644 adapters/beyondmedia/beyondmediatest/supplemental/status-not-200.json create mode 100644 adapters/beyondmedia/params_test.go create mode 100644 adapters/bidstack/bidstack.go create mode 100644 adapters/bidstack/bidstack_test.go create mode 100644 adapters/bidstack/bidstacktest/exemplary/simple-app-video.json create mode 100644 adapters/bidstack/bidstacktest/supplemental/currency_rate_not_found.json create mode 100644 adapters/bidstack/bidstacktest/supplemental/status-204.json create mode 100644 adapters/bidstack/bidstacktest/supplemental/status-400.json create mode 100644 adapters/bidstack/bidstacktest/supplemental/status-404.json create mode 100644 adapters/bidstack/params_test.go create mode 100644 adapters/bliink/bliinktest/exemplary/banner_native_video.json create mode 100644 adapters/blue/blue.go create mode 100644 adapters/blue/blue_test.go create mode 100644 adapters/blue/bluetest/exemplary/simple-web-banner.json create mode 100755 adapters/blue/bluetest/supplemental/204-response-from-target.json create mode 100755 adapters/blue/bluetest/supplemental/400-response-from-target.json create mode 100755 adapters/blue/bluetest/supplemental/500-response-from-target.json create mode 100644 adapters/blue/bluetest/supplemental/bad_response.json create mode 100644 adapters/boldwin/boldwin.go create mode 100644 adapters/boldwin/boldwin_test.go create mode 100644 adapters/boldwin/boldwintest/exemplary/endpointId.json create mode 100644 adapters/boldwin/boldwintest/exemplary/simple-banner.json create mode 100644 adapters/boldwin/boldwintest/exemplary/simple-native.json create mode 100644 adapters/boldwin/boldwintest/exemplary/simple-video.json create mode 100644 adapters/boldwin/boldwintest/exemplary/simple-web-banner.json create mode 100644 adapters/boldwin/boldwintest/supplemental/bad_media_type.json create mode 100644 adapters/boldwin/boldwintest/supplemental/bad_response.json create mode 100644 adapters/boldwin/boldwintest/supplemental/status-204.json create mode 100644 adapters/boldwin/boldwintest/supplemental/status-not-200.json create mode 100644 adapters/boldwin/params_test.go create mode 100644 adapters/ccx/ccx.go create mode 100644 adapters/ccx/ccx_test.go create mode 100644 adapters/ccx/ccxtest/exemplary/multi-banner.json create mode 100644 adapters/ccx/ccxtest/exemplary/simple-banner.json create mode 100644 adapters/ccx/ccxtest/supplemental/204-response-from-target.json create mode 100644 adapters/ccx/ccxtest/supplemental/400-response-from-target.json create mode 100644 adapters/ccx/ccxtest/supplemental/500-response-from-target.json create mode 100644 adapters/ccx/params_test.go create mode 100644 adapters/consumable/consumable/supplemental/simple-banner-content-meta.json create mode 100644 adapters/consumable/consumable/supplemental/simple-banner-coppa.json create mode 100644 adapters/consumable/consumable/supplemental/simple-banner-eids.json create mode 100644 adapters/consumable/consumable/supplemental/simple-banner-schain.json create mode 100644 adapters/dianomi/dianomi.go create mode 100644 adapters/dianomi/dianomi_test.go create mode 100644 adapters/dianomi/dianomitest/exemplary/multi-format.json create mode 100644 adapters/dianomi/dianomitest/exemplary/multi-native.json create mode 100644 adapters/dianomi/dianomitest/exemplary/single-banner-pricetype-gross-extend-ext.json create mode 100644 adapters/dianomi/dianomitest/exemplary/single-banner-pricetype-gross.json create mode 100644 adapters/dianomi/dianomitest/exemplary/single-banner-pricetype-net.json create mode 100644 adapters/dianomi/dianomitest/exemplary/single-banner.json create mode 100644 adapters/dianomi/dianomitest/exemplary/single-native.json create mode 100644 adapters/dianomi/dianomitest/exemplary/single-video.json create mode 100644 adapters/dianomi/dianomitest/exemplary/two-banners-different-pricetypes-extend-ext.json create mode 100644 adapters/dianomi/dianomitest/exemplary/two-banners-different-pricetypes.json create mode 100644 adapters/dianomi/dianomitest/supplemental/bad-request.json create mode 100644 adapters/dianomi/dianomitest/supplemental/empty-response.json create mode 100644 adapters/dianomi/dianomitest/supplemental/invalid-imp-mediatype.json create mode 100644 adapters/dianomi/dianomitest/supplemental/nobid-response.json create mode 100644 adapters/dianomi/dianomitest/supplemental/server-error.json create mode 100644 adapters/dianomi/dianomitest/supplemental/unparsable-response.json create mode 100644 adapters/dianomi/params_test.go create mode 100644 adapters/freewheelssp/freewheelssp.go create mode 100644 adapters/freewheelssp/freewheelssp_test.go create mode 100644 adapters/freewheelssp/freewheelssptest/exemplary/multi-imp.json create mode 100644 adapters/freewheelssp/freewheelssptest/exemplary/single-imp.json create mode 100644 adapters/freewheelssp/freewheelssptest/supplemental/204-bid-response.json create mode 100644 adapters/freewheelssp/freewheelssptest/supplemental/503-bid-response.json create mode 100644 adapters/grid/gridtest/exemplary/multitype-native.json create mode 100644 adapters/grid/gridtest/exemplary/native-as-string.json create mode 100644 adapters/grid/gridtest/exemplary/simple-native.json create mode 100644 adapters/huaweiads/huaweiadstest/exemplary/banner3.json create mode 100644 adapters/huaweiads/huaweiadstest/exemplary/banner4_mccmnc.json create mode 100644 adapters/huaweiads/huaweiadstest/exemplary/banner5_user_geo.json create mode 100644 adapters/huaweiads/huaweiadstest/exemplary/banner6_imei.json create mode 100644 adapters/huaweiads/huaweiadstest/exemplary/bannerAppPromotionType.json create mode 100644 adapters/huaweiads/huaweiadstest/exemplary/bannerNonIntegerMccmnc.json create mode 100644 adapters/huaweiads/huaweiadstest/exemplary/bannerNotAppPromotionType.json create mode 100644 adapters/huaweiads/huaweiadstest/exemplary/bannerWrongMccmnc.json create mode 100644 adapters/huaweiads/huaweiadstest/exemplary/interstitialBannerType.json create mode 100644 adapters/huaweiads/huaweiadstest/exemplary/interstitialVideoType.json create mode 100644 adapters/huaweiads/huaweiadstest/exemplary/rewardedVideo.json create mode 100644 adapters/huaweiads/huaweiadstest/exemplary/rewardedVideo1.json create mode 100644 adapters/huaweiads/huaweiadstest/exemplary/rewardedVideo2.json create mode 100644 adapters/huaweiads/huaweiadstest/exemplary/rewardedVideo3.json create mode 100644 adapters/huaweiads/huaweiadstest/exemplary/rewardedVideo4.json create mode 100644 adapters/huaweiads/huaweiadstest/exemplary/rollVideo.json create mode 100644 adapters/huaweiads/huaweiadstest/supplemental/bad_request_incorrect_huawei_adtype1.json create mode 100644 adapters/huaweiads/huaweiadstest/supplemental/bad_request_incorrect_huawei_adtype2.json create mode 100644 adapters/huaweiads/huaweiadstest/supplemental/bad_request_incorrect_huawei_adtype3.json create mode 100644 adapters/huaweiads/huaweiadstest/supplemental/bad_request_missing_all_type.json create mode 100644 adapters/huaweiads/huaweiadstest/supplemental/bad_request_no_support_audio.json create mode 100644 adapters/huaweiads/huaweiadstest/supplemental/bad_response_incorrect_huawei_adtype.json create mode 100644 adapters/huaweiads/huaweiadstest/supplemental/bad_response_not_intent.json create mode 100644 adapters/huaweiads/huaweiadstest/supplemental/missing_deviceid1.json create mode 100644 adapters/huaweiads/huaweiadstest/supplemental/missing_deviceid2.json create mode 100644 adapters/huaweiads/huaweiadstest/supplemental/missing_deviceid3.json create mode 100644 adapters/huaweiads/mcc_list.go create mode 100644 adapters/improvedigital/improvedigitaltest/supplemental/dealid.json create mode 100644 adapters/improvedigital/improvedigitaltest/supplemental/rewarded-inventory.json create mode 100644 adapters/infytv/infytv.go create mode 100644 adapters/infytv/infytv_test.go create mode 100644 adapters/infytv/infytvtest/exemplary/app.json create mode 100644 adapters/infytv/infytvtest/exemplary/video.json create mode 100644 adapters/infytv/infytvtest/supplemental/bad-response.json create mode 100644 adapters/infytv/infytvtest/supplemental/empty-seatbid.json create mode 100644 adapters/infytv/infytvtest/supplemental/status-204.json create mode 100644 adapters/infytv/infytvtest/supplemental/status-400.json create mode 100644 adapters/infytv/infytvtest/supplemental/status-503.json create mode 100644 adapters/infytv/infytvtest/supplemental/unexpected-status.json create mode 100644 adapters/infytv/params_test.go create mode 100644 adapters/ix/ixtest/exemplary/multi-format-with-ext-prebid-type.json create mode 100644 adapters/ix/ixtest/exemplary/multi-format-with-mtype.json create mode 100644 adapters/kargo/kargo.go create mode 100644 adapters/kargo/kargo_test.go create mode 100644 adapters/kargo/kargotest/exemplary/banner.json create mode 100644 adapters/kargo/kargotest/exemplary/native.json create mode 100644 adapters/kargo/kargotest/exemplary/video.json create mode 100644 adapters/kargo/kargotest/supplemental/status-bad-request.json create mode 100644 adapters/kargo/kargotest/supplemental/status-no-content.json create mode 100644 adapters/kargo/params_test.go create mode 100644 adapters/pubmatic/pubmatictest/exemplary/native.json create mode 100644 adapters/pubmatic/pubmatictest/supplemental/extra-bid.json create mode 100644 adapters/pubmatic/pubmatictest/supplemental/impExtData.json create mode 100644 adapters/pubmatic/pubmatictest/supplemental/native_invalid_adm.json create mode 100644 adapters/rubicon/rubicontest/exemplary/simple-banner.json create mode 100644 adapters/rubicon/rubicontest/exemplary/simple-native.json create mode 100644 adapters/seedingAlliance/params_test.go create mode 100644 adapters/seedingAlliance/seedingAlliance.go create mode 100644 adapters/seedingAlliance/seedingAlliance_test.go create mode 100644 adapters/seedingAlliance/seedingAlliancetest/exemplary/banner.json create mode 100644 adapters/seedingAlliance/seedingAlliancetest/exemplary/native.json create mode 100644 adapters/seedingAlliance/seedingAlliancetest/supplemental/invalid_tag_id.json create mode 100644 adapters/seedingAlliance/seedingAlliancetest/supplemental/status_bad_request.json create mode 100644 adapters/seedingAlliance/seedingAlliancetest/supplemental/status_no_content.json create mode 100644 adapters/seedingAlliance/seedingAlliancetest/supplemental/status_not_ok.json create mode 100644 adapters/smaato/native.go create mode 100644 adapters/smaato/native_test.go create mode 100644 adapters/smaato/smaatotest/exemplary/native.json create mode 100644 adapters/smartadserver/smartadservertest/exemplary/native-app.json create mode 100644 adapters/smartadserver/smartadservertest/exemplary/native-web.json create mode 100644 adapters/sspBC/sspbc.go create mode 100644 adapters/sspBC/sspbc_test.go create mode 100644 adapters/sspBC/sspbctest/exemplary/banner-fromtemplate.json create mode 100644 adapters/sspBC/sspbctest/exemplary/banner-preformatted-multiple-imps.json create mode 100644 adapters/sspBC/sspbctest/exemplary/banner-preformatted-onecode.json create mode 100644 adapters/sspBC/sspbctest/exemplary/banner-preformatted.json create mode 100644 adapters/sspBC/sspbctest/supplemental/bad_request_without_site.json create mode 100644 adapters/sspBC/sspbctest/supplemental/bad_response.json create mode 100644 adapters/sspBC/sspbctest/supplemental/bad_response_with_incorrect_impid.json create mode 100644 adapters/sspBC/sspbctest/supplemental/bad_response_without_adm.json create mode 100644 adapters/sspBC/sspbctest/supplemental/request_with_diffrent_siteid.json create mode 100644 adapters/sspBC/sspbctest/supplemental/request_with_incorrect_imp_bidder_ext.json create mode 100644 adapters/sspBC/sspbctest/supplemental/request_with_incorrect_imp_ext.json create mode 100644 adapters/sspBC/sspbctest/supplemental/request_with_standard_and_onecode_imp.json create mode 100644 adapters/sspBC/sspbctest/supplemental/request_with_test.json create mode 100644 adapters/sspBC/sspbctest/supplemental/request_without_banner_format.json create mode 100644 adapters/sspBC/sspbctest/supplemental/request_without_ext_id.json create mode 100644 adapters/sspBC/sspbctest/supplemental/request_without_ext_site_id.json create mode 100644 adapters/sspBC/sspbctest/supplemental/status_204.json create mode 100644 adapters/sspBC/sspbctest/supplemental/status_400.json create mode 100644 adapters/stroeerCore/params_test.go create mode 100644 adapters/stroeerCore/stroeercore.go create mode 100644 adapters/stroeerCore/stroeercore_test.go create mode 100644 adapters/stroeerCore/stroeercoretest/exemplary/mobile-banner-single.json create mode 100644 adapters/stroeerCore/stroeercoretest/exemplary/site-banner-multi.json create mode 100644 adapters/stroeerCore/stroeercoretest/exemplary/site-banner-single.json create mode 100644 adapters/stroeerCore/stroeercoretest/supplemental/bad-server-response.json create mode 100644 adapters/suntContent/params_test.go create mode 100644 adapters/suntContent/suntContent.go create mode 100644 adapters/suntContent/suntContent_test.go create mode 100644 adapters/suntContent/suntContenttest/exemplary/banner.json create mode 100644 adapters/suntContent/suntContenttest/exemplary/native.json create mode 100644 adapters/suntContent/suntContenttest/supplemental/invalid_tag_id.json create mode 100644 adapters/suntContent/suntContenttest/supplemental/status_bad_request.json create mode 100644 adapters/suntContent/suntContenttest/supplemental/status_no_content.json create mode 100644 adapters/suntContent/suntContenttest/supplemental/status_not_ok.json create mode 100644 adapters/taboola/params_test.go create mode 100644 adapters/taboola/taboola.go create mode 100644 adapters/taboola/taboola_test.go create mode 100644 adapters/taboola/taboolatest/exemplary/banner.json create mode 100644 adapters/taboola/taboolatest/supplemental/bidParamsOverrideRequestFields.json create mode 100644 adapters/taboola/taboolatest/supplemental/bidderServerError.json create mode 100644 adapters/taboola/taboolatest/supplemental/emptyReponseFromBidder.json create mode 100644 adapters/taboola/taboolatest/supplemental/emptySiteInRequest.json create mode 100644 adapters/taboola/taboolatest/supplemental/incorrectResponseImpMapping.json create mode 100644 adapters/taboola/taboolatest/supplemental/multiImpressionsRequest.json create mode 100644 adapters/taboola/taboolatest/supplemental/noValidImpression.json create mode 100644 adapters/taboola/taboolatest/supplemental/optionalParamsProvided.json create mode 100644 adapters/taboola/taboolatest/supplemental/unexpectedStatusCode.json create mode 100644 adapters/triplelift/triplelifttest/supplemental/video-format-11.json create mode 100644 adapters/triplelift/triplelifttest/supplemental/video-format-12.json create mode 100644 adapters/triplelift/triplelifttest/supplemental/video-format-17.json create mode 100644 adapters/unicorn/unicorntest/exemplary/banner-app-no-app-publisher.json create mode 100644 adapters/unicorn/unicorntest/exemplary/banner-app-no-mediaid.json create mode 100644 adapters/unicorn/unicorntest/exemplary/banner-app-no-publisherid.json create mode 100644 adapters/unicorn/unicorntest/supplemental/cannot-parse-accountid.json create mode 100644 adapters/unicorn/unicorntest/supplemental/no-accountid.json create mode 100644 adapters/unicorn/unicorntest/supplemental/no-app.json create mode 100644 adapters/visx/visxtest/exemplary/headers_ipv4.json create mode 100644 adapters/visx/visxtest/exemplary/headers_ipv6.json create mode 100644 adapters/vrtcal/vrtcaltest/exemplary/web-simple-banner.json create mode 100644 adapters/vrtcal/vrtcaltest/exemplary/web-simple-video.json create mode 100644 adapters/yieldlab/yieldlabtest/exemplary/mixed_types.json create mode 100644 adapters/yieldlab/yieldlabtest/exemplary/schain.json create mode 100644 adapters/yieldlab/yieldlabtest/exemplary/schain_multiple_nodes.json create mode 100644 analytics/pubstack/configupdate.go create mode 100644 analytics/pubstack/configupdate_test.go delete mode 100644 config/bidderinfo_validate_test.go create mode 100644 config/experiment.go create mode 100644 config/experiment_test.go create mode 100644 config/hooks.go rename config/test/{bidder-info/someBidder.yaml => bidder-info-valid/stroeerCore.yaml} (100%) create mode 100644 docs/adscertsigner.md create mode 100644 endpoints/openrtb2/sample-requests/account-malformed/malformed-acct.json create mode 100644 endpoints/openrtb2/sample-requests/amp/consent-through-query/addtl-consent-through-query.json create mode 100644 endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-ccpa-through-query.json create mode 100644 endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-legacy-tcf2-consent-through-query.json create mode 100644 endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-tcf1-consent-through-query.json create mode 100644 endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-tcf2-consent-through-query.json rename endpoints/openrtb2/sample-requests/{valid-whole/supplementary => amp/valid-supplementary}/aliased-buyeruids.json (70%) create mode 100644 endpoints/openrtb2/sample-requests/amp/valid-supplementary/aliases.json rename endpoints/openrtb2/sample-requests/{valid-whole/supplementary => amp/valid-supplementary}/gdpr-no-consentstring.json (64%) rename endpoints/openrtb2/sample-requests/{valid-whole/supplementary => amp/valid-supplementary}/gdpr.json (67%) rename endpoints/openrtb2/sample-requests/{valid-whole/supplementary => amp/valid-supplementary}/imp-with-stored-resp.json (76%) delete mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-eids-uids-empty.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-source-duplicate.json delete mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-source-unique.json rename endpoints/openrtb2/sample-requests/invalid-whole/{user-ext-eids-id-uids-empty.json => user-ext-eids-uids-missing.json} (53%) create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/hooks/auction_reject.json create mode 100644 exchange/exchangetest/extra-bids-with-aliases-adaptercode.json create mode 100644 exchange/exchangetest/extra-bids.json delete mode 100644 exchange/exchangetest/firstpartydata-imp-ext-multiple-bidders.json delete mode 100644 exchange/exchangetest/firstpartydata-imp-ext-one-bidder.json create mode 100644 exchange/exchangetest/passthrough_imp_only.json create mode 100644 exchange/exchangetest/passthrough_root_and_imp.json create mode 100644 exchange/exchangetest/passthrough_root_only.json create mode 100644 exchange/exchangetest/request-ext-prebid-filtering.json create mode 100644 exchange/exchangetest/request-imp-ext-prebid-filtering.json create mode 100644 exchange/exchangetest/schain-host-and-request.json create mode 100644 exchange/exchangetest/schain-host-only.json create mode 100644 experiment/adscert/SignerLogger.go create mode 100644 experiment/adscert/inprocesssigner.go create mode 100644 experiment/adscert/inprocesssigner_test.go create mode 100644 experiment/adscert/remotesigner.go create mode 100644 experiment/adscert/remotesigner_test.go create mode 100644 experiment/adscert/signer.go create mode 100644 experiment/adscert/signer_test.go create mode 100644 firstpartydata/tests/extractfpdforbidders/req-empty-user-fpd-not-empty-user.json rename firstpartydata/tests/extractfpdforbidders/{two-bidders-global-and-non-global-app-user.json => two-bidders-global-and-non-global-app-content-data.json} (83%) rename firstpartydata/tests/extractfpdforbidders/{two-bidders-global-and-non-global-site-user.json => two-bidders-global-and-non-global-site-content-data.json} (84%) create mode 100644 firstpartydata/tests/extractfpdforbidders/two-bidders-global-without-config-and-global-app-content-data.json create mode 100644 firstpartydata/tests/extractfpdforbidders/two-bidders-global-without-config-and-global-site-content-data.json rename firstpartydata/tests/resolvefpd/{global-and-bidder-fpd-site-content-data.json => bidder-fpd-site-content-data-only.json} (69%) create mode 100644 firstpartydata/tests/resolvefpd/global-and-no-bidder-fpd-site-app-user.json create mode 100644 firstpartydata/tests/resolvefpd/global-and-no-bidder-fpd-site-content-app-user.json create mode 100644 firstpartydata/tests/resolvefpd/global-fpd-only-app.json rename firstpartydata/tests/resolvefpd/{global-and-bidder-fpd-site-app-user.json => global-fpd-only-site.json} (100%) create mode 100644 firstpartydata/tests/resolvefpd/req-and-bidder-fpd-site-content.json create mode 100644 gdpr/basic_enforcement.go create mode 100644 gdpr/basic_enforcement_test.go create mode 100644 gdpr/full_enforcement.go create mode 100644 gdpr/full_enforcement_test.go create mode 100644 gdpr/purpose_config.go create mode 100644 gdpr/purpose_config_test.go create mode 100644 gdpr/purpose_enforcer.go create mode 100644 gdpr/purpose_enforcer_test.go create mode 100644 hooks/empty_plan.go create mode 100644 hooks/empty_plan_test.go create mode 100644 hooks/hookanalytics/analytics.go create mode 100644 hooks/hookanalytics/analytics_test.go create mode 100644 hooks/hookexecution/context.go create mode 100644 hooks/hookexecution/errors.go create mode 100644 hooks/hookexecution/errors_test.go create mode 100644 hooks/hookexecution/execution.go create mode 100644 hooks/hookexecution/executor.go create mode 100644 hooks/hookexecution/executor_test.go create mode 100644 hooks/hookexecution/mocks_test.go create mode 100644 hooks/hookexecution/outcome.go create mode 100644 hooks/hookexecution/outcome_test.go create mode 100644 hooks/hookstage/allprocessedbidresponses.go create mode 100644 hooks/hookstage/auctionresponse.go create mode 100644 hooks/hookstage/bidderrequest.go create mode 100644 hooks/hookstage/entrypoint.go create mode 100644 hooks/hookstage/invocation.go create mode 100644 hooks/hookstage/mutation.go create mode 100644 hooks/hookstage/processedauctionrequest.go create mode 100644 hooks/hookstage/rawauctionrequest.go create mode 100644 hooks/hookstage/rawbidderresponse.go create mode 100644 hooks/plan.go create mode 100644 hooks/plan_test.go create mode 100644 hooks/repo.go create mode 100644 hooks/repo_test.go create mode 100644 modules/builder.go create mode 100644 modules/generator/builder.tmpl create mode 100644 modules/generator/buildergen.go create mode 100644 modules/helpers.go create mode 100644 modules/modules.go create mode 100644 modules/modules_test.go create mode 100644 openrtb_ext/alternatebiddercodes.go create mode 100644 openrtb_ext/alternatebiddercodes_test.go create mode 100644 openrtb_ext/convert_down.go create mode 100644 openrtb_ext/convert_down_test.go create mode 100644 openrtb_ext/convert_up.go create mode 100644 openrtb_ext/convert_up_test.go create mode 100644 openrtb_ext/imp_adrino.go create mode 100644 openrtb_ext/imp_adtrgtme.go create mode 100644 openrtb_ext/imp_appush.go create mode 100644 openrtb_ext/imp_beyondmedia.go create mode 100644 openrtb_ext/imp_bidstack.go create mode 100644 openrtb_ext/imp_boldwin.go create mode 100644 openrtb_ext/imp_ccx.go create mode 100644 openrtb_ext/imp_dianomi.go create mode 100644 openrtb_ext/imp_freewheelssp.go create mode 100644 openrtb_ext/imp_infytv.go create mode 100644 openrtb_ext/imp_kargo.go create mode 100644 openrtb_ext/imp_seedingAlliance.go create mode 100644 openrtb_ext/imp_sspbc.go create mode 100644 openrtb_ext/imp_stroeercore.go create mode 100644 openrtb_ext/imp_suntContent.go create mode 100644 openrtb_ext/imp_taboola.go create mode 100644 static/bidder-info/adrino.yaml create mode 100644 static/bidder-info/adtrgtme.yaml create mode 100644 static/bidder-info/appush.yaml create mode 100644 static/bidder-info/automatad.yaml create mode 100644 static/bidder-info/beyondmedia.yaml create mode 100644 static/bidder-info/bidstack.yaml rename static/bidder-info/{yssp.yaml => blue.yaml} (55%) create mode 100644 static/bidder-info/boldwin.yaml create mode 100644 static/bidder-info/ccx.yaml create mode 100644 static/bidder-info/dianomi.yaml create mode 100644 static/bidder-info/freewheel-ssp.yaml create mode 100644 static/bidder-info/freewheelssp.yaml create mode 100644 static/bidder-info/infytv.yaml create mode 100644 static/bidder-info/kargo.yaml create mode 100644 static/bidder-info/oftmedia.yaml create mode 100644 static/bidder-info/seedingAlliance.yaml create mode 100644 static/bidder-info/sspBC.yaml create mode 100644 static/bidder-info/stroeerCore.yaml create mode 100644 static/bidder-info/suntContent.yaml create mode 100644 static/bidder-info/taboola.yaml create mode 100644 static/bidder-params/adrino.json create mode 100644 static/bidder-params/adtrgtme.json create mode 100644 static/bidder-params/appush.json create mode 100644 static/bidder-params/automatad.json create mode 100644 static/bidder-params/beyondmedia.json create mode 100644 static/bidder-params/bidstack.json create mode 100644 static/bidder-params/blue.json create mode 100644 static/bidder-params/boldwin.json create mode 100644 static/bidder-params/ccx.json create mode 100644 static/bidder-params/dianomi.json create mode 100644 static/bidder-params/freewheel-ssp.json create mode 100644 static/bidder-params/freewheelssp.json create mode 100644 static/bidder-params/infytv.json create mode 100644 static/bidder-params/kargo.json create mode 100644 static/bidder-params/oftmedia.json create mode 100644 static/bidder-params/seedingAlliance.json create mode 100644 static/bidder-params/sspBC.json create mode 100644 static/bidder-params/stroeerCore.json create mode 100644 static/bidder-params/suntContent.json create mode 100644 static/bidder-params/taboola.json delete mode 100644 static/bidder-params/yssp.json create mode 100644 stored_requests/backends/db_provider/db_provider.go create mode 100644 stored_requests/backends/db_provider/db_provider_mock.go create mode 100644 stored_requests/backends/db_provider/db_provider_test.go create mode 100644 stored_requests/backends/db_provider/mysql_dbprovider.go create mode 100644 stored_requests/backends/db_provider/mysql_dbprovider_test.go create mode 100644 stored_requests/backends/db_provider/postgres_dbprovider.go create mode 100644 stored_requests/backends/db_provider/postgres_dbprovider_test.go rename stored_requests/events/{postgres => database}/database.go (82%) rename stored_requests/events/{postgres => database}/database_test.go (96%) create mode 100644 util/task/func_runner.go create mode 100644 util/task/func_runner_test.go diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 210dbdb5b2f..f4ea099d9c8 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,7 +6,7 @@ "dockerfile": "Dockerfile", "args": { // Update the VARIANT arg to pick a version of Go - "VARIANT": "1.16", + "VARIANT": "1.19", // Options "INSTALL_NODE": "false", "NODE_VERSION": "lts/*" @@ -14,16 +14,21 @@ }, "containerEnv": { "GOPRIVATE": "${localEnv:GOPRIVATE}", + "GOFLAGS": "-buildvcs=false", "PBS_GDPR_DEFAULT_VALUE": "0" }, "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ], // Set *default* container specific settings.json values on container create. "settings": { - "terminal.integrated.shell.linux": "/bin/bash", - "go.useGoProxyToCheckForToolUpdates": false, + "terminal.integrated.profiles.linux": { + "bash": { + "path": "/usr/bin/bash" + } + }, + "terminal.integrated.defaultProfile.linux": "bash", + "go.toolsManagement.checkForUpdates": "off", "go.gopath": "/go", - //"go.toolsGopath": "/tmp/go", }, // Add the IDs of extensions you want installed when the container is created. diff --git a/.github/workflows/issue_prioritization.yml b/.github/workflows/issue_prioritization.yml new file mode 100644 index 00000000000..9a178577a1a --- /dev/null +++ b/.github/workflows/issue_prioritization.yml @@ -0,0 +1,90 @@ +name: Issue tracking +on: + issues: + types: + - opened + - labeled +jobs: + track_issue: + runs-on: ubuntu-latest + steps: + - name: Generate token + id: generate_token + uses: tibdex/github-app-token@36464acb844fc53b9b8b2401da68844f6b05ebb0 + with: + app_id: ${{ secrets.PBS_PROJECT_APP_ID }} + private_key: ${{ secrets.PBS_PROJECT_APP_PEM }} + + - name: Get project data + env: + GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} + ORGANIZATION: prebid + DATE_FIELD: Created on + PROJECT_NUMBER: 4 + run: | + gh api graphql -f query=' + query($org: String!, $number: Int!) { + organization(login: $org){ + projectNext(number: $number) { + id + fields(first:100) { + nodes { + id + name + settings + } + } + } + } + }' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json + + echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV + echo 'DATE_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "'"$DATE_FIELD"'") | .id' project_data.json) >> $GITHUB_ENV + + - name: Add issue to project + env: + GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} + ISSUE_ID: ${{ github.event.issue.node_id }} + run: | + gh api graphql -f query=' + mutation($project:ID!, $issue:ID!) { + addProjectNextItem(input: {projectId: $project, contentId: $issue}) { + projectNextItem { + id, + content { + ... on Issue { + createdAt + } + ... on PullRequest { + createdAt + } + } + } + } + }' -f project=$PROJECT_ID -f issue=$ISSUE_ID > issue_data.json + + echo 'ITEM_ID='$(jq '.data.addProjectNextItem.projectNextItem.id' issue_data.json) >> $GITHUB_ENV + echo 'ITEM_CREATION_DATE='$(jq '.data.addProjectNextItem.projectNextItem.content.createdAt' issue_data.json) >> $GITHUB_ENV + + - name: Set fields + env: + GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} + run: | + gh api graphql -f query=' + mutation ( + $project: ID! + $item: ID! + $date_field: ID! + $date_value: String! + ) { + set_creation_date: updateProjectNextItemField(input: { + projectId: $project + itemId: $item + fieldId: $date_field + value: $date_value + }) { + projectNextItem { + id + } + } + }' -f project=$PROJECT_ID -f item=$ITEM_ID -f date_field=$DATE_FIELD_ID -f date_value=$ITEM_CREATION_DATE --silent diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a14596263c3..8b4d73b302e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,7 +9,7 @@ jobs: release: name: Create Release if: github.event.base_ref == 'refs/heads/master' - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - name: Get Version id: get_version diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 570114b0497..b56ddad9142 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -9,7 +9,7 @@ on: jobs: build: name: Trivy - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 steps: - name: Checkout Code uses: actions/checkout@v2 diff --git a/.github/workflows/validate-merge.yml b/.github/workflows/validate-merge.yml index 9cf371ca168..5cf8cdb7550 100644 --- a/.github/workflows/validate-merge.yml +++ b/.github/workflows/validate-merge.yml @@ -6,13 +6,13 @@ on: jobs: validate-merge: - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 steps: - name: Install Go uses: actions/setup-go@v2 with: - go-version: 1.16.4 + go-version: 1.19.2 - name: Checkout Merged Branch uses: actions/checkout@v2 diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index fc36f45c61f..7d1fddc2979 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -10,8 +10,8 @@ jobs: validate: strategy: matrix: - go-version: [1.16.x, 1.17.x] - os: [ubuntu-18.04] + go-version: [1.18.x, 1.19.x] + os: [ubuntu-20.04] runs-on: ${{ matrix.os }} steps: diff --git a/Dockerfile b/Dockerfile index 9c869f6a762..80269c908df 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,10 @@ -FROM ubuntu:18.04 AS build +FROM ubuntu:20.04 AS build RUN apt-get update && \ apt-get -y upgrade && \ apt-get install -y wget WORKDIR /tmp -RUN wget https://dl.google.com/go/go1.16.4.linux-amd64.tar.gz && \ - tar -xf go1.16.4.linux-amd64.tar.gz && \ +RUN wget https://dl.google.com/go/go1.19.2.linux-amd64.tar.gz && \ + tar -xf go1.19.2.linux-amd64.tar.gz && \ mv go /usr/local RUN mkdir -p /app/prebid-server/ WORKDIR /app/prebid-server/ @@ -16,13 +16,13 @@ RUN apt-get update && \ apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* ENV CGO_ENABLED 0 COPY ./ ./ -RUN go mod vendor RUN go mod tidy +RUN go mod vendor ARG TEST="true" RUN if [ "$TEST" != "false" ]; then ./validate.sh ; fi 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 +FROM ubuntu:20.04 AS release LABEL maintainer="hans.hjort@xandr.com" WORKDIR /usr/local/bin/ COPY --from=build /app/prebid-server . diff --git a/Makefile b/Makefile index 8ffea91fe36..baf69cafbf8 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ # Makefile -all: deps test build +all: deps test build-modules build -.PHONY: deps test build image +.PHONY: deps test build-modules build image # deps will clean out the vendor directory and use go mod for a fresh install deps: @@ -18,6 +18,10 @@ else go test github.com/prebid/prebid-server/adapters/$(adapter) -bench=. endif +# build-modules generates modules/builder.go file which provides a list of all available modules +build-modules: + go generate modules/modules.go + # build will ensure all of our tests pass and then build the go binary build: test go build -mod=vendor ./... diff --git a/README.md b/README.md index dab5523e037..e88a4b265a5 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.16 or newer. +First install [Go](https://golang.org/doc/install) version 1.18 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/account/account.go b/account/account.go index 45506e93907..7d4abf043ea 100644 --- a/account/account.go +++ b/account/account.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" + "github.com/buger/jsonparser" "github.com/prebid/go-gdpr/consentconstants" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -51,7 +52,25 @@ func GetAccount(ctx context.Context, cfg *config.Configuration, fetcher stored_r completeJSON, err := jsonpatch.MergePatch(cfg.AccountDefaultsJSON(), accountJSON) if err == nil { err = json.Unmarshal(completeJSON, account) + + // this logic exists for backwards compatibility. If the initial unmarshal fails above, we attempt to + // resolve it by converting the GDPR enforce purpose fields and then attempting an unmarshal again before + // declaring a malformed account error. + // unmarshal fetched account to determine if it is well-formed + if _, ok := err.(*json.UnmarshalTypeError); ok { + // attempt to convert deprecated GDPR enforce purpose fields to resolve issue + completeJSON, err = ConvertGDPREnforcePurposeFields(completeJSON) + // unmarshal again to check if unmarshal error still exists after GDPR field conversion + err = json.Unmarshal(completeJSON, account) + + if _, ok := err.(*json.UnmarshalTypeError); ok { + return nil, []error{&errortypes.MalformedAcct{ + Message: fmt.Sprintf("The prebid-server account config for account id \"%s\" is malformed. Please reach out to the prebid server host.", accountID), + }} + } + } } + if err != nil { errs = append(errs, err) return nil, errs @@ -73,6 +92,13 @@ func GetAccount(ctx context.Context, cfg *config.Configuration, fetcher stored_r return account, nil } +// TCF2Enforcements maps enforcement algo string values to their integer representation and is +// used to limit string compares +var TCF2Enforcements = map[string]config.TCF2EnforcementAlgo{ + config.TCF2EnforceAlgoBasic: config.TCF2BasicEnforcement, + config.TCF2EnforceAlgoFull: config.TCF2FullEnforcement, +} + // setDerivedConfig modifies an account object by setting fields derived from other fields set in the account configuration func setDerivedConfig(account *config.Account) { account.GDPR.PurposeConfigs = map[consentconstants.Purpose]*config.AccountGDPRPurpose{ @@ -88,9 +114,16 @@ func setDerivedConfig(account *config.Account) { 10: &account.GDPR.Purpose10, } - // To look for a purpose's vendor exceptions in O(1) time, for each purpose we fill this hash table with bidders - // located in the VendorExceptions field of the GDPR.PurposeX struct for _, pc := range account.GDPR.PurposeConfigs { + // To minimize the number of string compares per request, we set the integer representation + // of the enforcement algorithm on each purpose config + pc.EnforceAlgoID = config.TCF2UndefinedEnforcement + if algo, exists := TCF2Enforcements[pc.EnforceAlgo]; exists { + pc.EnforceAlgoID = algo + } + + // To look for a purpose's vendor exceptions in O(1) time, for each purpose we fill this hash table with bidders + // located in the VendorExceptions field of the GDPR.PurposeX struct if pc.VendorExceptions == nil { continue } @@ -120,3 +153,79 @@ func setDerivedConfig(account *config.Account) { } } } + +// PatchAccount represents the GDPR portion of a publisher account configuration that can be mutated +// for backwards compatibility reasons +type PatchAccount struct { + GDPR map[string]*PatchAccountGDPRPurpose `json:"gdpr"` +} + +// PatchAccountGDPRPurpose represents account-specific GDPR purpose configuration data that can be mutated +// for backwards compatibility reasons +type PatchAccountGDPRPurpose struct { + EnforceAlgo string `json:"enforce_algo,omitempty"` + EnforcePurpose *bool `json:"enforce_purpose,omitempty"` +} + +// ConvertGDPREnforcePurposeFields is responsible for ensuring account GDPR config backwards compatibility +// given the recent type change of gdpr.purpose{1-10}.enforce_purpose from a string to a bool. This function +// iterates over each GDPR purpose config and sets enforce_purpose and enforce_algo to the appropriate +// bool and string values respectively if enforce_purpose is a string and enforce_algo is not set +func ConvertGDPREnforcePurposeFields(config []byte) (newConfig []byte, err error) { + gdprJSON, _, _, err := jsonparser.Get(config, "gdpr") + if err != nil && err == jsonparser.KeyPathNotFoundError { + return config, nil + } + if err != nil { + return nil, err + } + + newAccount := PatchAccount{ + GDPR: map[string]*PatchAccountGDPRPurpose{}, + } + + for i := 1; i <= 10; i++ { + purposeName := fmt.Sprintf("purpose%d", i) + + enforcePurpose, purposeDataType, _, err := jsonparser.Get(gdprJSON, purposeName, "enforce_purpose") + if err != nil && err == jsonparser.KeyPathNotFoundError { + continue + } + if err != nil { + return nil, err + } + if purposeDataType != jsonparser.String { + continue + } + + _, _, _, err = jsonparser.Get(gdprJSON, purposeName, "enforce_algo") + if err != nil && err != jsonparser.KeyPathNotFoundError { + return nil, err + } + if err == nil { + continue + } + + newEnforcePurpose := false + if string(enforcePurpose) == "full" { + newEnforcePurpose = true + } + + newAccount.GDPR[purposeName] = &PatchAccountGDPRPurpose{ + EnforceAlgo: "full", + EnforcePurpose: &newEnforcePurpose, + } + } + + patchConfig, err := json.Marshal(newAccount) + if err != nil { + return nil, err + } + + newConfig, err = jsonpatch.MergePatch(config, patchConfig) + if err != nil { + return nil, err + } + + return newConfig, nil +} diff --git a/account/account_test.go b/account/account_test.go index 2b7f6a4e394..de905043841 100644 --- a/account/account_test.go +++ b/account/account_test.go @@ -6,6 +6,7 @@ import ( "fmt" "testing" + "github.com/buger/jsonparser" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/metrics" @@ -15,8 +16,10 @@ import ( ) var mockAccountData = map[string]json.RawMessage{ - "valid_acct": json.RawMessage(`{"disabled":false}`), - "disabled_acct": json.RawMessage(`{"disabled":true}`), + "valid_acct": json.RawMessage(`{"disabled":false}`), + "disabled_acct": json.RawMessage(`{"disabled":true}`), + "malformed_acct": json.RawMessage(`{"disabled":"invalid type"}`), + "gdpr_convert_acct": json.RawMessage(`{"disabled":false,"gdpr":{"purpose5":{"enforce_purpose":"full"}}}`), } type mockAccountFetcher struct { @@ -66,6 +69,24 @@ func TestGetAccount(t *testing.T) { {accountID: "disabled_acct", required: true, disabled: false, err: &errortypes.BlacklistedAcct{}}, {accountID: "disabled_acct", required: false, disabled: true, err: &errortypes.BlacklistedAcct{}}, {accountID: "disabled_acct", required: true, disabled: true, err: &errortypes.BlacklistedAcct{}}, + + // pubID given and matches a host account with Disabled: false and GDPR purpose data to convert + {accountID: "gdpr_convert_acct", required: false, disabled: false, err: nil}, + {accountID: "gdpr_convert_acct", required: true, disabled: false, err: nil}, + {accountID: "gdpr_convert_acct", required: false, disabled: true, err: nil}, + {accountID: "gdpr_convert_acct", required: true, disabled: true, err: nil}, + + // pubID given and matches a host account that has a malformed config + {accountID: "malformed_acct", required: false, disabled: false, err: &errortypes.MalformedAcct{}}, + {accountID: "malformed_acct", required: true, disabled: false, err: &errortypes.MalformedAcct{}}, + {accountID: "malformed_acct", required: false, disabled: true, err: &errortypes.MalformedAcct{}}, + {accountID: "malformed_acct", required: true, disabled: true, err: &errortypes.MalformedAcct{}}, + + // account not provided (does not exist) + {accountID: "", required: false, disabled: false, err: nil}, + {accountID: "", required: true, disabled: false, err: nil}, + {accountID: "", required: false, disabled: true, err: &errortypes.BlacklistedAcct{}}, + {accountID: "", required: true, disabled: true, err: &errortypes.AcctRequired{}}, } for _, test := range testCases { @@ -100,6 +121,8 @@ func TestSetDerivedConfig(t *testing.T) { purpose1VendorExceptions []openrtb_ext.BidderName feature1VendorExceptions []openrtb_ext.BidderName basicEnforcementVendors []string + enforceAlgo string + wantEnforceAlgoID config.TCF2EnforcementAlgo }{ { description: "Nil purpose 1 vendor exceptions", @@ -137,6 +160,16 @@ func TestSetDerivedConfig(t *testing.T) { description: "Multiple basic enforcement vendors", basicEnforcementVendors: []string{"appnexus", "rubicon"}, }, + { + description: "Basic Enforcement algorithm", + enforceAlgo: config.TCF2EnforceAlgoBasic, + wantEnforceAlgoID: config.TCF2BasicEnforcement, + }, + { + description: "Full Enforcement algorithm", + enforceAlgo: config.TCF2EnforceAlgoFull, + wantEnforceAlgoID: config.TCF2FullEnforcement, + }, } for _, tt := range tests { @@ -144,6 +177,7 @@ func TestSetDerivedConfig(t *testing.T) { GDPR: config.AccountGDPR{ Purpose1: config.AccountGDPRPurpose{ VendorExceptions: tt.purpose1VendorExceptions, + EnforceAlgo: tt.enforceAlgo, }, SpecialFeature1: config.AccountGDPRSpecialFeature{ VendorExceptions: tt.feature1VendorExceptions, @@ -172,5 +206,141 @@ func TestSetDerivedConfig(t *testing.T) { assert.ElementsMatch(t, purpose1ExceptionMapKeys, tt.purpose1VendorExceptions, tt.description) assert.ElementsMatch(t, feature1ExceptionMapKeys, tt.feature1VendorExceptions, tt.description) assert.ElementsMatch(t, basicEnforcementMapKeys, tt.basicEnforcementVendors, tt.description) + + assert.Equal(t, account.GDPR.Purpose1.EnforceAlgoID, tt.wantEnforceAlgoID, tt.description) + } +} + +func TestConvertGDPREnforcePurposeFields(t *testing.T) { + enforcePurposeNo := `{"enforce_purpose":"no"}` + enforcePurposeNoMapped := `{"enforce_algo":"full", "enforce_purpose":false}` + enforcePurposeFull := `{"enforce_purpose":"full"}` + enforcePurposeFullMapped := `{"enforce_algo":"full", "enforce_purpose":true}` + + tests := []struct { + description string + giveConfig []byte + wantConfig []byte + wantErr error + }{ + { + description: "config is nil", + giveConfig: nil, + wantConfig: nil, + wantErr: nil, + }, + { + description: "config is empty - no gdpr key", + giveConfig: []byte(``), + wantConfig: []byte(``), + wantErr: nil, + }, + { + description: "gdpr present but empty", + giveConfig: []byte(`{"gdpr": {}}`), + wantConfig: []byte(`{"gdpr": {}}`), + wantErr: nil, + }, + { + description: "gdpr present but invalid", + giveConfig: []byte(`{"gdpr": {`), + wantConfig: nil, + wantErr: jsonparser.MalformedJsonError, + }, + { + description: "gdpr.purpose1 present but empty", + giveConfig: []byte(`{"gdpr":{"purpose1":{}}}`), + wantConfig: []byte(`{"gdpr":{"purpose1":{}}}`), + wantErr: nil, + }, + { + description: "gdpr.purpose1.enforce_purpose is set to bool", + giveConfig: []byte(`{"gdpr":{"purpose1":{"enforce_purpose":true}}}`), + wantConfig: []byte(`{"gdpr":{"purpose1":{"enforce_purpose":true}}}`), + wantErr: nil, + }, + { + description: "gdpr.purpose1.enforce_purpose is set to string full", + giveConfig: []byte(`{"gdpr":{"purpose1":{"enforce_purpose":"full"}}}`), + wantConfig: []byte(`{"gdpr":{"purpose1":{"enforce_algo":"full", "enforce_purpose":true}}}`), + wantErr: nil, + }, + { + description: "gdpr.purpose1.enforce_purpose is set to string no", + giveConfig: []byte(`{"gdpr":{"purpose1":{"enforce_purpose":"no"}}}`), + wantConfig: []byte(`{"gdpr":{"purpose1":{"enforce_algo":"full", "enforce_purpose":false}}}`), + wantErr: nil, + }, + { + description: "gdpr.purpose1.enforce_purpose is set to string no and other fields are untouched during conversion", + giveConfig: []byte(`{"gdpr":{"purpose1":{"enforce_purpose":"no", "enforce_vendors":true}}}`), + wantConfig: []byte(`{"gdpr":{"purpose1":{"enforce_algo":"full", "enforce_purpose":false, "enforce_vendors":true}}}`), + wantErr: nil, + }, + { + description: "gdpr.purpose1.enforce_purpose is set but invalid", + giveConfig: []byte(`{"gdpr":{"purpose1":{"enforce_purpose":}}}`), + wantConfig: nil, + wantErr: jsonparser.MalformedJsonError, + }, + { + description: "gdpr.purpose1.enforce_algo is set", + giveConfig: []byte(`{"gdpr":{"purpose1":{"enforce_algo":"full"}}}`), + wantConfig: []byte(`{"gdpr":{"purpose1":{"enforce_algo":"full"}}}`), + wantErr: nil, + }, + { + description: "gdpr.purpose1.enforce_purpose is set to string and enforce_algo is set", + giveConfig: []byte(`{"gdpr":{"purpose1":{"enforce_algo":"full", "enforce_purpose":"full"}}}`), + wantConfig: []byte(`{"gdpr":{"purpose1":{"enforce_algo":"full", "enforce_purpose":"full"}}}`), + wantErr: nil, + }, + { + description: "gdpr.purpose1.enforce_purpose is set to string and enforce_algo is set but invalid", + giveConfig: []byte(`{"gdpr":{"purpose1":{"enforce_algo":, "enforce_purpose":"full"}}}`), + wantConfig: nil, + wantErr: jsonparser.MalformedJsonError, + }, + { + description: "gdpr.purpose{1-10}.enforce_purpose are set to strings no and full alternating", + giveConfig: []byte(`{"gdpr":{` + + `"purpose1":` + enforcePurposeNo + + `,"purpose2":` + enforcePurposeFull + + `,"purpose3":` + enforcePurposeNo + + `,"purpose4":` + enforcePurposeFull + + `,"purpose5":` + enforcePurposeNo + + `,"purpose6":` + enforcePurposeFull + + `,"purpose7":` + enforcePurposeNo + + `,"purpose8":` + enforcePurposeFull + + `,"purpose9":` + enforcePurposeNo + + `,"purpose10":` + enforcePurposeFull + + `}}`), + wantConfig: []byte(`{"gdpr":{` + + `"purpose1":` + enforcePurposeNoMapped + + `,"purpose2":` + enforcePurposeFullMapped + + `,"purpose3":` + enforcePurposeNoMapped + + `,"purpose4":` + enforcePurposeFullMapped + + `,"purpose5":` + enforcePurposeNoMapped + + `,"purpose6":` + enforcePurposeFullMapped + + `,"purpose7":` + enforcePurposeNoMapped + + `,"purpose8":` + enforcePurposeFullMapped + + `,"purpose9":` + enforcePurposeNoMapped + + `,"purpose10":` + enforcePurposeFullMapped + + `}}`), + wantErr: nil, + }, + } + + for _, tt := range tests { + newConfig, err := ConvertGDPREnforcePurposeFields(tt.giveConfig) + if tt.wantErr != nil { + assert.Error(t, err, tt.description) + } + + if len(tt.wantConfig) == 0 { + assert.Equal(t, tt.wantConfig, newConfig, tt.description) + } else { + assert.JSONEq(t, string(tt.wantConfig), string(newConfig), tt.description) + } } } diff --git a/adapters/33across/33across.go b/adapters/33across/33across.go index b6c16b20fbd..04b089b66f4 100644 --- a/adapters/33across/33across.go +++ b/adapters/33across/33across.go @@ -5,7 +5,8 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/adcom1" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -47,7 +48,7 @@ type bidExt struct { } type bidTtxExt struct { - MediaType string `json:mediaType,omitempty` + MediaType string `json:"mediaType,omitempty"` } // MakeRequests create the object for TTX Reqeust. @@ -261,7 +262,7 @@ func validateVideoParams(video *openrtb2.Video, prod string) (*openrtb2.Video, e videoCopy.Placement = 1 if videoCopy.StartDelay == nil { - videoCopy.StartDelay = openrtb2.StartDelay.Ptr(0) + videoCopy.StartDelay = adcom1.StartDelay.Ptr(0) } } @@ -277,7 +278,7 @@ func getBidType(ext bidExt) openrtb_ext.BidType { } // Builder builds a new instance of the 33Across adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &TtxAdapter{ endpoint: config.Endpoint, } diff --git a/adapters/33across/33across_test.go b/adapters/33across/33across_test.go index 97703735d9c..bdc546a9627 100644 --- a/adapters/33across/33across_test.go +++ b/adapters/33across/33across_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.Bidder33Across, config.Adapter{ - Endpoint: "http://ssc.33across.com"}) + Endpoint: "http://ssc.33across.com"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/33across/params_test.go b/adapters/33across/params_test.go index 0c7cde18216..2d488c4148c 100644 --- a/adapters/33across/params_test.go +++ b/adapters/33across/params_test.go @@ -9,7 +9,7 @@ import ( // This file actually intends to test static/bidder-params/33across.json // -// These also validate the format of the external API: request.imp[i].ext.33across +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.33across // TestValidParams makes sure that the 33across schema accepts all imp.ext fields which we intend to support. func TestValidParams(t *testing.T) { diff --git a/adapters/aax/aax.go b/adapters/aax/aax.go index 1a15ebd3a4f..30f37bb60b6 100644 --- a/adapters/aax/aax.go +++ b/adapters/aax/aax.go @@ -6,7 +6,7 @@ import ( "net/http" "net/url" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -86,7 +86,7 @@ func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest } // Builder builds a new instance of the Aax adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { url := buildEndpoint(config.Endpoint, config.ExtraAdapterInfo) return &adapter{ endpoint: url, diff --git a/adapters/aax/aax_test.go b/adapters/aax/aax_test.go index 7ff6e610f20..6a5eaed5dfe 100644 --- a/adapters/aax/aax_test.go +++ b/adapters/aax/aax_test.go @@ -1,9 +1,10 @@ package aax import ( - "github.com/stretchr/testify/assert" "testing" + "github.com/stretchr/testify/assert" + "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" @@ -13,7 +14,7 @@ func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAax, config.Adapter{ Endpoint: "https://example.aax.media/rtb/prebid", ExtraAdapterInfo: "http://localhost:8080/extrnal_url", - }) + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -24,7 +25,7 @@ func TestJsonSamples(t *testing.T) { func TestEndpointTemplateMalformed(t *testing.T) { _, buildErr := Builder(openrtb_ext.BidderAax, config.Adapter{ - Endpoint: "{{Malformed}}"}) + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Nil(t, buildErr) } diff --git a/adapters/aceex/aceex.go b/adapters/aceex/aceex.go index abf748a7ead..a487da7bc1b 100644 --- a/adapters/aceex/aceex.go +++ b/adapters/aceex/aceex.go @@ -6,7 +6,7 @@ import ( "net/http" "text/template" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -19,7 +19,7 @@ type adapter struct { } // 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) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (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) diff --git a/adapters/aceex/aceex_test.go b/adapters/aceex/aceex_test.go index 4970f652bd0..ec0e0fec710 100644 --- a/adapters/aceex/aceex_test.go +++ b/adapters/aceex/aceex_test.go @@ -11,7 +11,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAceex, config.Adapter{ - Endpoint: "http://us.example.com/bid?uqhash={{.AccountID}}"}) + Endpoint: "http://us.example.com/bid?uqhash={{.AccountID}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -22,7 +22,7 @@ func TestJsonSamples(t *testing.T) { func TestEndpointTemplateMalformed(t *testing.T) { _, buildErr := Builder(openrtb_ext.BidderAceex, config.Adapter{ - Endpoint: "{{Malformed}}"}) + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Error(t, buildErr) } diff --git a/adapters/acuityads/acuityads.go b/adapters/acuityads/acuityads.go index 84a754fe753..69224d93ae6 100644 --- a/adapters/acuityads/acuityads.go +++ b/adapters/acuityads/acuityads.go @@ -6,7 +6,7 @@ import ( "net/http" "text/template" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -19,7 +19,7 @@ type AcuityAdsAdapter struct { } // Builder builds a new instance of the AcuityAds adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (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) diff --git a/adapters/acuityads/acuityads_test.go b/adapters/acuityads/acuityads_test.go index 8cc50637374..ea9d4f24352 100644 --- a/adapters/acuityads/acuityads_test.go +++ b/adapters/acuityads/acuityads_test.go @@ -11,7 +11,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAcuityAds, config.Adapter{ - Endpoint: "http://{{.Host}}.example.com/bid?token={{.AccountID}}"}) + Endpoint: "http://{{.Host}}.example.com/bid?token={{.AccountID}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -22,7 +22,7 @@ func TestJsonSamples(t *testing.T) { func TestEndpointTemplateMalformed(t *testing.T) { _, buildErr := Builder(openrtb_ext.BidderAcuityAds, config.Adapter{ - Endpoint: "{{Malformed}}"}) + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Error(t, buildErr) } diff --git a/adapters/adapterstest/adapter_test_util.go b/adapters/adapterstest/adapter_test_util.go index 25d43a138fb..bfcdfe9f7ae 100644 --- a/adapters/adapterstest/adapter_test_util.go +++ b/adapters/adapterstest/adapter_test_util.go @@ -8,7 +8,7 @@ import ( "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" ) // OrtbMockService Represents a scaffolded OpenRTB service. diff --git a/adapters/adapterstest/test_json.go b/adapters/adapterstest/test_json.go index 6d2533df82a..6eab9a48720 100644 --- a/adapters/adapterstest/test_json.go +++ b/adapters/adapterstest/test_json.go @@ -3,57 +3,55 @@ package adapterstest import ( "encoding/json" "fmt" - "io/ioutil" + "net/http" + "os" "regexp" "testing" "github.com/mitchellh/copystructure" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/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" - - "net/http" ) // RunJSONBidderTest is a helper method intended to unit test Bidders' adapters. // It requires that: // -// 1. Bidders communicate with external servers over HTTP. -// 2. The HTTP request bodies are legal JSON. +// 1. Bidders communicate with external servers over HTTP. +// 2. The HTTP request bodies are legal JSON. // // Although the project does not require it, we _strongly_ recommend that all Bidders write tests using this. // Doing so has the following benefits: // -// 1. This includes some basic tests which confirm that your Bidder is "well-behaved" for all the input samples. -// For example, "no nil bids are allowed in the returned array". -// These tests are tedious to write, but help prevent bugs during auctions. +// 1. This includes some basic tests which confirm that your Bidder is "well-behaved" for all the input samples. +// For example, "no nil bids are allowed in the returned array". +// These tests are tedious to write, but help prevent bugs during auctions. // -// 2. In the future, we plan to auto-generate documentation from the "exemplary" test files. -// Those docs will teach publishers how to use your Bidder, which should encourage adoption. +// 2. In the future, we plan to auto-generate documentation from the "exemplary" test files. +// Those docs will teach publishers how to use your Bidder, which should encourage adoption. // // To use this method, create *.json files in the following directories: // // adapters/{bidder}/{bidder}test/exemplary: // -// These show "ideal" BidRequests for your Bidder. If possible, configure your servers to return the same -// expected responses forever. If your server responds appropriately, our future auto-generated documentation -// can guarantee Publishers that your adapter works as documented. +// These show "ideal" BidRequests for your Bidder. If possible, configure your servers to return the same +// expected responses forever. If your server responds appropriately, our future auto-generated documentation +// can guarantee Publishers that your adapter works as documented. // // adapters/{bidder}/{bidder}test/supplemental: // -// Fill this with *.json files which are useful test cases, but are not appropriate for public example docs. -// For example, a file in this directory might make sure that a mobile-only Bidder returns errors on non-mobile requests. +// Fill this with *.json files which are useful test cases, but are not appropriate for public example docs. +// For example, a file in this directory might make sure that a mobile-only Bidder returns errors on non-mobile requests. // // Then create a test in your adapters/{bidder}/{bidder}_test.go file like so: // -// func TestJsonSamples(t *testing.T) { -// adapterstest.RunJSONBidderTest(t, "{bidder}test", instanceOfYourBidder) -// } -// +// func TestJsonSamples(t *testing.T) { +// adapterstest.RunJSONBidderTest(t, "{bidder}test", instanceOfYourBidder) +// } func RunJSONBidderTest(t *testing.T, rootDir string, bidder adapters.Bidder) { runTests(t, fmt.Sprintf("%s/exemplary", rootDir), bidder, false, false, false) runTests(t, fmt.Sprintf("%s/supplemental", rootDir), bidder, true, false, false) @@ -65,7 +63,7 @@ func RunJSONBidderTest(t *testing.T, rootDir string, bidder adapters.Bidder) { // runTests runs all the *.json files in a directory. If allowErrors is false, and one of the test files // expects errors from the bidder, then the test will fail. func runTests(t *testing.T, directory string, bidder adapters.Bidder, allowErrors, isAmpTest, isVideoTest bool) { - if specFiles, err := ioutil.ReadDir(directory); err == nil { + if specFiles, err := os.ReadDir(directory); err == nil { for _, specFile := range specFiles { fileName := fmt.Sprintf("%s/%s", directory, specFile.Name()) specData, err := loadFile(fileName) @@ -83,7 +81,7 @@ func runTests(t *testing.T, directory string, bidder adapters.Bidder, allowError // LoadFile reads and parses a file as a test case. If something goes wrong, it returns an error. func loadFile(filename string) (*testSpec, error) { - specData, err := ioutil.ReadFile(filename) + specData, err := os.ReadFile(filename) if err != nil { return nil, fmt.Errorf("Failed to read file %s: %v", filename, err) } @@ -207,6 +205,7 @@ type expectedBidResponse struct { type expectedBid struct { Bid json.RawMessage `json:"bid"` Type string `json:"type"` + Seat string `json:"seat"` } // --------------------------------------- @@ -314,6 +313,7 @@ func diffBids(t *testing.T, description string, actual *adapters.TypedBid, expec return } + assert.Equal(t, string(expected.Seat), string(actual.Seat), fmt.Sprintf(`%s.seat "%s" does not match expected "%s."`, description, string(actual.Seat), string(expected.Seat))) assert.Equal(t, string(expected.Type), string(actual.BidType), fmt.Sprintf(`%s.type "%s" does not match expected "%s."`, description, string(actual.BidType), string(expected.Type))) assert.NoError(t, diffOrtbBids(fmt.Sprintf("%s.bid", description), actual.Bid, expected.Bid)) } @@ -367,9 +367,9 @@ func diffJson(description string, actual []byte, expected []byte) error { // testMakeRequestsImpl asserts the resulting values of the bidder's `MakeRequests()` implementation // against the expected JSON-defined results and ensures we do not encounter data races in the process. // To assert no data races happen we make use of: -// 1) A shallow copy of the unmarshalled openrtb2.BidRequest that will provide reference values to +// 1. A shallow copy of the unmarshalled openrtb2.BidRequest that will provide reference values to // shared memory that we don't want the adapters' implementation of `MakeRequests()` to modify. -// 2) A deep copy that will preserve the original values of all the fields. This copy remains untouched +// 2. A deep copy that will preserve the original values of all the fields. This copy remains untouched // by the adapters' processes and serves as reference of what the shared memory values should still // be after the `MakeRequests()` call. func testMakeRequestsImpl(t *testing.T, filename string, spec *testSpec, bidder adapters.Bidder, reqInfo *adapters.ExtraRequestInfo) []*adapters.RequestData { diff --git a/adapters/adf/adf.go b/adapters/adf/adf.go index bc54f78f187..7a1e766dcf2 100644 --- a/adapters/adf/adf.go +++ b/adapters/adf/adf.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -22,7 +22,7 @@ type adfRequestExt struct { } // Builder builds a new instance of the Adf adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &adapter{ endpoint: config.Endpoint, } diff --git a/adapters/adf/adf_test.go b/adapters/adf/adf_test.go index ab348db36ae..bf8af8d6845 100644 --- a/adapters/adf/adf_test.go +++ b/adapters/adf/adf_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAdf, config.Adapter{ - Endpoint: "https://adx.adform.net/adx/openrtb"}) + Endpoint: "https://adx.adform.net/adx/openrtb"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/adf/params_test.go b/adapters/adf/params_test.go index d46c6528da0..0b05519df3b 100644 --- a/adapters/adf/params_test.go +++ b/adapters/adf/params_test.go @@ -9,7 +9,7 @@ import ( // This file actually intends to test static/bidder-params/adf.json // -// These also validate the format of the external API: request.imp[i].ext.adf +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.adf // TestValidParams makes sure that the adform schema accepts all imp.ext fields which we intend to support. func TestValidParams(t *testing.T) { diff --git a/adapters/adgeneration/adgeneration.go b/adapters/adgeneration/adgeneration.go index 55fafde1d85..1035aca8afc 100644 --- a/adapters/adgeneration/adgeneration.go +++ b/adapters/adgeneration/adgeneration.go @@ -10,7 +10,7 @@ import ( "strconv" "strings" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -284,7 +284,7 @@ func removeWrapper(ad string) string { } // Builder builds a new instance of the Adgeneration adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &AdgenerationAdapter{ config.Endpoint, "1.0.3", diff --git a/adapters/adgeneration/adgeneration_test.go b/adapters/adgeneration/adgeneration_test.go index c186f19f4c5..7194b92b848 100644 --- a/adapters/adgeneration/adgeneration_test.go +++ b/adapters/adgeneration/adgeneration_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" @@ -14,7 +14,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAdgeneration, config.Adapter{ - Endpoint: "https://d.socdm.com/adsv/v1"}) + Endpoint: "https://d.socdm.com/adsv/v1"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -25,7 +25,7 @@ func TestJsonSamples(t *testing.T) { func TestGetRequestUri(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAdgeneration, config.Adapter{ - Endpoint: "https://d.socdm.com/adsv/v1"}) + Endpoint: "https://d.socdm.com/adsv/v1"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -133,7 +133,7 @@ func TestGetSizes(t *testing.T) { func TestGetCurrency(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAdgeneration, config.Adapter{ - Endpoint: "https://d.socdm.com/adsv/v1"}) + Endpoint: "https://d.socdm.com/adsv/v1"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -204,7 +204,7 @@ func TestCreateAd(t *testing.T) { func TestMakeBids(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAdgeneration, config.Adapter{ - Endpoint: "https://d.socdm.com/adsv/v1"}) + Endpoint: "https://d.socdm.com/adsv/v1"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/adhese/adhese.go b/adapters/adhese/adhese.go index b23b49b3774..99879c8739f 100644 --- a/adapters/adhese/adhese.go +++ b/adapters/adhese/adhese.go @@ -11,7 +11,7 @@ import ( "text/template" "github.com/golang/glog" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -269,7 +269,7 @@ func ContainsAny(raw string, keys []string) bool { } // Builder builds a new instance of the Adhese adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (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) diff --git a/adapters/adhese/adhese_test.go b/adapters/adhese/adhese_test.go index 3a9d28ee5e9..d09a29ee9bd 100644 --- a/adapters/adhese/adhese_test.go +++ b/adapters/adhese/adhese_test.go @@ -11,7 +11,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAdhese, config.Adapter{ - Endpoint: "https://ads-{{.AccountID}}.adhese.com/json"}) + Endpoint: "https://ads-{{.AccountID}}.adhese.com/json"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -22,7 +22,7 @@ func TestJsonSamples(t *testing.T) { func TestEndpointTemplateMalformed(t *testing.T) { _, buildErr := Builder(openrtb_ext.BidderAdhese, config.Adapter{ - Endpoint: "{{Malformed}}"}) + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Error(t, buildErr) } diff --git a/adapters/adhese/utils.go b/adapters/adhese/utils.go index 5a81bb83bd5..e555a1b9c3e 100644 --- a/adapters/adhese/utils.go +++ b/adapters/adhese/utils.go @@ -1,6 +1,6 @@ package adhese -import "github.com/mxmCherry/openrtb/v15/openrtb2" +import "github.com/prebid/openrtb/v17/openrtb2" type AdheseOriginData struct { Priority string `json:"priority"` diff --git a/adapters/adkernel/adkernel.go b/adapters/adkernel/adkernel.go index 183c31eedf4..09db512daed 100644 --- a/adapters/adkernel/adkernel.go +++ b/adapters/adkernel/adkernel.go @@ -7,7 +7,7 @@ import ( "strconv" "text/template" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -19,7 +19,7 @@ type adkernelAdapter struct { EndpointTemplate *template.Template } -//MakeRequests prepares request information for prebid-server core +// MakeRequests prepares request information for prebid-server core func (adapter *adkernelAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)) if len(request.Imp) == 0 { @@ -84,7 +84,7 @@ func validateImpression(imp *openrtb2.Imp, impExt *openrtb_ext.ExtImpAdkernel) e return nil } -//Group impressions by AdKernel-specific parameter `zoneId` +// Group impressions by AdKernel-specific parameter `zoneId` func dispatchImpressions(imps []openrtb2.Imp, impsExt []openrtb_ext.ExtImpAdkernel) (map[openrtb_ext.ExtImpAdkernel][]openrtb2.Imp, []error) { res := make(map[openrtb_ext.ExtImpAdkernel][]openrtb2.Imp) errors := make([]error, 0) @@ -104,7 +104,7 @@ func dispatchImpressions(imps []openrtb2.Imp, impsExt []openrtb_ext.ExtImpAdkern return res, errors } -//Alter impression info to comply with adkernel platform requirements +// Alter impression info to comply with adkernel platform requirements func compatImpression(imp *openrtb2.Imp) error { imp.Ext = nil //do not forward ext to adkernel platform if imp.Banner != nil { @@ -204,7 +204,7 @@ func (adapter *adkernelAdapter) buildEndpointURL(params *openrtb_ext.ExtImpAdker return macros.ResolveMacros(adapter.EndpointTemplate, endpointParams) } -//MakeBids translates adkernel bid response to prebid-server specific format +// MakeBids translates adkernel bid response to prebid-server specific format func (adapter *adkernelAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil @@ -262,7 +262,7 @@ func newBadServerResponseError(message string) error { } // Builder builds a new instance of the Adkernel adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { urlTemplate, err := template.New("endpointTemplate").Parse(config.Endpoint) if err != nil { return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) diff --git a/adapters/adkernel/adkernel_test.go b/adapters/adkernel/adkernel_test.go index 543455f85f7..ae35f712400 100644 --- a/adapters/adkernel/adkernel_test.go +++ b/adapters/adkernel/adkernel_test.go @@ -11,7 +11,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAdkernel, config.Adapter{ - Endpoint: "https://pbs.adksrv.com/hb?zone={{.ZoneID}}"}) + Endpoint: "https://pbs.adksrv.com/hb?zone={{.ZoneID}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -22,7 +22,7 @@ func TestJsonSamples(t *testing.T) { func TestEndpointTemplateMalformed(t *testing.T) { _, buildErr := Builder(openrtb_ext.BidderAdkernel, config.Adapter{ - Endpoint: "{{Malformed}}"}) + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Error(t, buildErr) } diff --git a/adapters/adkernelAdn/adkernelAdn.go b/adapters/adkernelAdn/adkernelAdn.go index 2c29e69a55d..c5c0abba7df 100644 --- a/adapters/adkernelAdn/adkernelAdn.go +++ b/adapters/adkernelAdn/adkernelAdn.go @@ -7,7 +7,7 @@ import ( "strconv" "text/template" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -19,7 +19,7 @@ type adkernelAdnAdapter struct { EndpointTemplate *template.Template } -//MakeRequests prepares request information for prebid-server core +// MakeRequests prepares request information for prebid-server core func (adapter *adkernelAdnAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)) if len(request.Imp) == 0 { @@ -85,7 +85,7 @@ func validateImpression(imp *openrtb2.Imp, impExt *openrtb_ext.ExtImpAdkernelAdn return nil } -//Group impressions by AdKernel-specific parameters `pubId` & `host` +// Group impressions by AdKernel-specific parameters `pubId` & `host` func dispatchImpressions(imps []openrtb2.Imp, impsExt []openrtb_ext.ExtImpAdkernelAdn) (map[openrtb_ext.ExtImpAdkernelAdn][]openrtb2.Imp, []error) { res := make(map[openrtb_ext.ExtImpAdkernelAdn][]openrtb2.Imp) errors := make([]error, 0) @@ -105,7 +105,7 @@ func dispatchImpressions(imps []openrtb2.Imp, impsExt []openrtb_ext.ExtImpAdkern return res, errors } -//Alter impression info to comply with adkernel platform requirements +// Alter impression info to comply with adkernel platform requirements func compatImpression(imp *openrtb2.Imp) error { imp.Ext = nil //do not forward ext to adkernel platform if imp.Banner != nil { @@ -214,7 +214,7 @@ func (adapter *adkernelAdnAdapter) buildEndpointURL(params *openrtb_ext.ExtImpAd return macros.ResolveMacros(adapter.EndpointTemplate, endpointParams) } -//MakeBids translates adkernel bid response to prebid-server specific format +// MakeBids translates adkernel bid response to prebid-server specific format func (adapter *adkernelAdnAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil @@ -272,7 +272,7 @@ func newBadServerResponseError(message string) error { } // Builder builds a new instance of the AdkernelAdn adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (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) diff --git a/adapters/adkernelAdn/adkernelAdn_test.go b/adapters/adkernelAdn/adkernelAdn_test.go index 945a415f43a..651d82be3b6 100644 --- a/adapters/adkernelAdn/adkernelAdn_test.go +++ b/adapters/adkernelAdn/adkernelAdn_test.go @@ -11,7 +11,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAdkernelAdn, config.Adapter{ - Endpoint: "https://pbs2.adksrv.com/rtbpub?account={{.PublisherID}}"}) + Endpoint: "https://pbs2.adksrv.com/rtbpub?account={{.PublisherID}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -22,7 +22,7 @@ func TestJsonSamples(t *testing.T) { func TestEndpointTemplateMalformed(t *testing.T) { _, buildErr := Builder(openrtb_ext.BidderAdkernelAdn, config.Adapter{ - Endpoint: "{{Malformed}}"}) + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Error(t, buildErr) } diff --git a/adapters/adman/adman.go b/adapters/adman/adman.go index fd31bc9d14f..d6597f85fe2 100644 --- a/adapters/adman/adman.go +++ b/adapters/adman/adman.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -18,7 +18,7 @@ type AdmanAdapter struct { } // Builder builds a new instance of the Adman adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &AdmanAdapter{ URI: config.Endpoint, } diff --git a/adapters/adman/adman_test.go b/adapters/adman/adman_test.go index ceee5f21bd6..608232cc4b8 100644 --- a/adapters/adman/adman_test.go +++ b/adapters/adman/adman_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAdman, config.Adapter{ - Endpoint: "http://pub.admanmedia.com/?c=o&m=ortb"}) + Endpoint: "http://pub.admanmedia.com/?c=o&m=ortb"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/admixer/admixer.go b/adapters/admixer/admixer.go index 5008b0ce5c6..26ed307b607 100644 --- a/adapters/admixer/admixer.go +++ b/adapters/admixer/admixer.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -17,7 +17,7 @@ type AdmixerAdapter struct { } // Builder builds a new instance of the Admixer adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &AdmixerAdapter{ endpoint: config.Endpoint, } diff --git a/adapters/admixer/admixer_test.go b/adapters/admixer/admixer_test.go index d994847c1ed..766f890cdf7 100644 --- a/adapters/admixer/admixer_test.go +++ b/adapters/admixer/admixer_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAdmixer, config.Adapter{ - Endpoint: "http://inv-nets.admixer.net/pbs.aspx"}) + Endpoint: "http://inv-nets.admixer.net/pbs.aspx"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/admixer/params_test.go b/adapters/admixer/params_test.go index 11f3feb0657..bfa75a4884f 100644 --- a/adapters/admixer/params_test.go +++ b/adapters/admixer/params_test.go @@ -2,13 +2,14 @@ package admixer import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/openrtb_ext" ) // This file actually intends to test static/bidder-params/admixer.json // -// These also validate the format of the external API: request.imp[i].ext.admixer +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.admixer // TestValidParams makes sure that the admixer schema accepts all imp.ext fields which we intend to support. func TestValidParams(t *testing.T) { diff --git a/adapters/adnuntius/adnuntius.go b/adapters/adnuntius/adnuntius.go index da66e4433ec..3b01dab5e9f 100644 --- a/adapters/adnuntius/adnuntius.go +++ b/adapters/adnuntius/adnuntius.go @@ -8,7 +8,8 @@ import ( "strconv" "strings" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/buger/jsonparser" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -18,35 +19,43 @@ import ( type QueryString map[string]string type adapter struct { - time timeutil.Time - endpoint string + time timeutil.Time + endpoint string + extraInfo string } type adnAdunit struct { - AuId string `json:"auId"` - TargetId string `json:"targetId"` + AuId string `json:"auId"` + TargetId string `json:"targetId"` + Dimensions [][]int64 `json:"dimensions,omitempty"` } -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 extDeviceAdnuntius struct { + NoCookies bool `json:"noCookies,omitempty"` +} + +type AdUnit 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 AdnResponse struct { + AdUnits []AdUnit +} type adnMetaData struct { Usi string `json:"usi,omitempty"` } @@ -56,12 +65,16 @@ type adnRequest struct { Context string `json:"context,omitempty"` } +type RequestExt struct { + Bidder adnAdunit `json:"bidder"` +} + 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) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &adapter{ time: &timeutil.RealTime{}, endpoint: config.Endpoint, @@ -90,8 +103,9 @@ func setHeaders(ortbRequest openrtb2.BidRequest) http.Header { return headers } -func makeEndpointUrl(ortbRequest openrtb2.BidRequest, a *adapter) (string, []error) { +func makeEndpointUrl(ortbRequest openrtb2.BidRequest, a *adapter, noCookies bool) (string, []error) { uri, err := url.Parse(a.endpoint) + endpointUrl := a.endpoint if err != nil { return "", []error{fmt.Errorf("failed to parse Adnuntius endpoint: %v", err)} } @@ -101,35 +115,72 @@ func makeEndpointUrl(ortbRequest openrtb2.BidRequest, a *adapter) (string, []err return "", []error{fmt.Errorf("failed to parse Adnuntius endpoint: %v", err)} } + if !noCookies { + var deviceExt extDeviceAdnuntius + if ortbRequest.Device != nil && ortbRequest.Device.Ext != nil { + if err := json.Unmarshal(ortbRequest.Device.Ext, &deviceExt); err != nil { + return "", []error{fmt.Errorf("failed to parse Adnuntius endpoint: %v", err)} + } + } + + if deviceExt.NoCookies { + noCookies = true + } + + } + _, offset := a.time.Now().Zone() tzo := -offset / minutesInHour q := uri.Query() - if gdpr != "" && consent != "" { + if gdpr != "" { + endpointUrl = a.extraInfo q.Set("gdpr", gdpr) + } + + if consent != "" { q.Set("consentString", consent) } + + if noCookies { + q.Set("noCookies", "true") + } + q.Set("tzo", fmt.Sprint(tzo)) q.Set("format", "json") - url := a.endpoint + "?" + q.Encode() + url := endpointUrl + "?" + q.Encode() return url, nil } +func getImpSizes(imp openrtb2.Imp) [][]int64 { + + if len(imp.Banner.Format) > 0 { + sizes := make([][]int64, len(imp.Banner.Format)) + for i, format := range imp.Banner.Format { + sizes[i] = []int64{format.W, format.H} + } + + return sizes + } + + if imp.Banner.W != nil && imp.Banner.H != nil { + size := make([][]int64, 1) + size[0] = []int64{*imp.Banner.W, *imp.Banner.H} + return size + } + + return nil +} + /* - Generate the requests to Adnuntius to reduce the amount of requests going out. +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(ortbRequest) - - endpoint, err := makeEndpointUrl(ortbRequest, a) - if err != nil { - return nil, []error{&errortypes.BadInput{ - Message: fmt.Sprintf("failed to parse URL: %s", err), - }} - } + var noCookies bool = false for _, imp := range ortbRequest.Imp { if imp.Banner == nil { @@ -151,6 +202,10 @@ func (a *adapter) generateRequests(ortbRequest openrtb2.BidRequest) ([]*adapters }} } + if adnuntiusExt.NoCookies == true { + noCookies = true + } + network := defaultNetwork if adnuntiusExt.Network != "" { network = adnuntiusExt.Network @@ -159,11 +214,19 @@ func (a *adapter) generateRequests(ortbRequest openrtb2.BidRequest) ([]*adapters networkAdunitMap[network] = append( networkAdunitMap[network], adnAdunit{ - AuId: adnuntiusExt.Auid, - TargetId: fmt.Sprintf("%s-%s", adnuntiusExt.Auid, imp.ID), + AuId: adnuntiusExt.Auid, + TargetId: fmt.Sprintf("%s-%s", adnuntiusExt.Auid, imp.ID), + Dimensions: getImpSizes(imp), }) } + endpoint, err := makeEndpointUrl(ortbRequest, a, noCookies) + if err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("failed to parse URL: %s", err), + }} + } + site := defaultSite if ortbRequest.Site != nil && ortbRequest.Site.Page != "" { site = ortbRequest.Site.Page @@ -231,9 +294,10 @@ func (a *adapter) MakeBids(request *openrtb2.BidRequest, externalRequest *adapte } func getGDPR(request *openrtb2.BidRequest) (string, string, error) { + gdpr := "" var extRegs openrtb_ext.ExtRegs - if request.Regs != nil { + if request.Regs != nil && request.Regs.Ext != nil { if err := json.Unmarshal(request.Regs.Ext, &extRegs); err != nil { return "", "", fmt.Errorf("failed to parse ExtRegs in Adnuntius GDPR check: %v", err) } @@ -257,8 +321,23 @@ func getGDPR(request *openrtb2.BidRequest) (string, string, error) { func generateBidResponse(adnResponse *AdnResponse, request *openrtb2.BidRequest) (*adapters.BidderResponse, []error) { bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(adnResponse.AdUnits)) var currency string + adunitMap := map[string]AdUnit{} + + for _, adnRespAdunit := range adnResponse.AdUnits { + adunitMap[adnRespAdunit.TargetId] = adnRespAdunit + } + + for i, imp := range request.Imp { + + auId, _, _, err := jsonparser.Get(imp.Ext, "bidder", "auId") + if err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Error at Bidder auId: %s", err.Error()), + }} + } - for i, adunit := range adnResponse.AdUnits { + targetID := fmt.Sprintf("%s-%s", string(auId), imp.ID) + adunit := adunitMap[targetID] if len(adunit.Ads) > 0 { diff --git a/adapters/adnuntius/adnuntius_test.go b/adapters/adnuntius/adnuntius_test.go index 270f27a9366..27e006e10b2 100644 --- a/adapters/adnuntius/adnuntius_test.go +++ b/adapters/adnuntius/adnuntius_test.go @@ -13,13 +13,13 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAdnuntius, config.Adapter{ - Endpoint: "http://whatever.url"}) + Endpoint: "http://whatever.url"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) } assertTzo(t, bidder) - replaceRealTimeWithKnownTime(bidder) + AssignDefaultValues(bidder) adapterstest.RunJSONBidderTest(t, "adnuntiustest", bidder) } @@ -38,9 +38,10 @@ func (ft *FakeTime) Now() time.Time { return ft.time } -func replaceRealTimeWithKnownTime(bidder adapters.Bidder) { +func AssignDefaultValues(bidder adapters.Bidder) { bidderAdnuntius, _ := bidder.(*adapter) bidderAdnuntius.time = &FakeTime{ time: time.Date(2016, 1, 1, 12, 30, 15, 0, time.UTC), } + bidderAdnuntius.extraInfo = "http://gdpr.url" } diff --git a/adapters/adnuntius/adnuntiustest/exemplary/simple-banner.json b/adapters/adnuntius/adnuntiustest/exemplary/simple-banner.json index 6e943810aa9..3a50789e4dd 100644 --- a/adapters/adnuntius/adnuntiustest/exemplary/simple-banner.json +++ b/adapters/adnuntius/adnuntiustest/exemplary/simple-banner.json @@ -36,7 +36,8 @@ "adUnits": [ { "auId": "123", - "targetId": "123-test-imp-id" + "targetId": "123-test-imp-id", + "dimensions": [[300,250],[300,600]] } ], "context": "prebid.org", diff --git a/adapters/adnuntius/adnuntiustest/supplemental/banner-nil-check.json b/adapters/adnuntius/adnuntiustest/supplemental/banner-nil-check.json new file mode 100644 index 00000000000..ae6b7cd823b --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/supplemental/banner-nil-check.json @@ -0,0 +1,29 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "id": "1kjh3429kjh295jkl" + }, + "device":{ + "ext":{ + "noCookies" : true + } + }, + "imp": [ + { + "id": "test-imp-id", + "ext": { + "bidder": { + "auId": "123" + } + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "ignoring imp id=test-imp-id, Adnuntius supports only Banner", + "comparison": "literal" + } + ] +} diff --git a/adapters/adnuntius/adnuntiustest/supplemental/check-gdpr.json b/adapters/adnuntius/adnuntiustest/supplemental/check-gdpr.json index e195429cef9..c73d69bad83 100644 --- a/adapters/adnuntius/adnuntiustest/supplemental/check-gdpr.json +++ b/adapters/adnuntius/adnuntiustest/supplemental/check-gdpr.json @@ -1,109 +1,111 @@ { - "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" - } - ] + "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://gdpr.url?consentString=CONSENT_STRING&format=json&gdpr=1&tzo=0", + "body": { + "adUnits": [ + { + "auId": "123", + "targetId": "123-test-imp-id", + "dimensions": [[300,250],[300,600]] + } + ], + "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-noCookies-parameter.json b/adapters/adnuntius/adnuntiustest/supplemental/check-noCookies-parameter.json new file mode 100644 index 00000000000..b0d74565771 --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/supplemental/check-noCookies-parameter.json @@ -0,0 +1,104 @@ +{ + "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", + "noCookies": true + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.url?format=json&noCookies=true&tzo=0", + "body": { + "adUnits": [ + { + "auId": "123", + "targetId": "123-test-imp-id", + "dimensions": [[300,250],[300,600]] + } + ], + "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-noCookies.json b/adapters/adnuntius/adnuntiustest/supplemental/check-noCookies.json new file mode 100644 index 00000000000..f1ddd3f7d5a --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/supplemental/check-noCookies.json @@ -0,0 +1,108 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "id": "1kjh3429kjh295jkl" + }, + "device":{ + "ext":{ + "noCookies" : true + } + }, + "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&noCookies=true&tzo=0", + "body": { + "adUnits": [ + { + "auId": "123", + "targetId": "123-test-imp-id", + "dimensions": [[300,250],[300,600]] + } + ], + "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-order-multi-imp.json b/adapters/adnuntius/adnuntiustest/supplemental/check-order-multi-imp.json new file mode 100644 index 00000000000..035d16566ac --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/supplemental/check-order-multi-imp.json @@ -0,0 +1,162 @@ +{ + "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" + } + } + }, + { + "id": "test-imp-id-2", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "auId": "456" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.url?format=json&tzo=0", + "body": { + "adUnits": [ + { + "auId": "123", + "targetId": "123-test-imp-id", + "dimensions": [[300,250],[300,600]] + }, + { + "auId": "456", + "targetId": "456-test-imp-id-2", + "dimensions": [[300,250],[300,600]] + } + ], + "context": "prebid.org", + "metaData": {} + } + }, + "mockResponse": { + "status": 200, + "body": { + "adUnits": [ + { + "auId": "0000000000000456", + "targetId": "456-test-imp-id2", + "html": "", + "responseId": "adn-rsp-900646517", + "ads": [ + { + "destinationUrls": { + "url": "http://www.google.com" + }, + "bid": { + "amount": 20.0, + "currency": "NOK" + }, + "adId": "adn-id-1559784095", + "creativeWidth": "980", + "creativeHeight": "240", + "creativeId": "jn9hpzvlsf8cpdmm", + "lineItemId": "q7y9qm5b0xt9htrv" + } + ] + }, + { + "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" + } + ] + } + ] + } + } + } + ], + "expectedBidResponse": + { + "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" + }, + { + "bid": { + "id": "adn-id-1559784095", + "impid": "test-imp-id-2", + "price": 20000, + "adm": "", + "adid": "adn-id-1559784095", + "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 index 8305bcdb59a..4b8e6de346e 100644 --- a/adapters/adnuntius/adnuntiustest/supplemental/check-userId.json +++ b/adapters/adnuntius/adnuntiustest/supplemental/check-userId.json @@ -1,101 +1,103 @@ { - "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" - } - ] + "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", + "dimensions": [[300,250],[300,600]] + } + ], + "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/empty-regs-ext.json b/adapters/adnuntius/adnuntiustest/supplemental/empty-regs-ext.json new file mode 100644 index 00000000000..f3aebd99621 --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/supplemental/empty-regs-ext.json @@ -0,0 +1,106 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "id": "1kjh3429kjh295jkl" + }, + "regs": { + "ext": {} + }, + "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", + "dimensions": [[300,250],[300,600]] + } + ], + "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/empty-regs.json b/adapters/adnuntius/adnuntiustest/supplemental/empty-regs.json new file mode 100644 index 00000000000..06593630c43 --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/supplemental/empty-regs.json @@ -0,0 +1,105 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "id": "1kjh3429kjh295jkl" + }, + "regs": { + }, + "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", + "dimensions": [[300,250],[300,600]] + } + ], + "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 index ba388f1deea..1f213a43b6a 100644 --- a/adapters/adnuntius/adnuntiustest/supplemental/height-error.json +++ b/adapters/adnuntius/adnuntiustest/supplemental/height-error.json @@ -1,84 +1,88 @@ { - "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" - } - } - } - ] - }, + "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", + "dimensions": [[300,250],[300,600]] + } + ], + "context": "prebid.org", + "metaData": { - "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" - } - ] + } + } + }, + "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 index a8203e581ef..cf21ba0ef99 100644 --- a/adapters/adnuntius/adnuntiustest/supplemental/native-error.json +++ b/adapters/adnuntius/adnuntiustest/supplemental/native-error.json @@ -1,25 +1,24 @@ { - "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" - } - ] + "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/send-header-information.json b/adapters/adnuntius/adnuntiustest/supplemental/send-header-information.json index 96ddff858ac..bcfecfa8e98 100644 --- a/adapters/adnuntius/adnuntiustest/supplemental/send-header-information.json +++ b/adapters/adnuntius/adnuntiustest/supplemental/send-header-information.json @@ -1,122 +1,123 @@ { - "mockBidRequest": { - "id": "test-request-id", - "user": { - "id": "1kjh3429kjh295jkl", - "ext": { - "consent": "CONSENT_STRING" - } - }, - "device": { - "ip": "158.174.81.143", - "ua": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Mobile Safari/537.36" - }, - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "ext": { - "bidder": { - "auId": "123" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "headers": { - "Content-Type": [ - "application/json;charset=utf-8" - ], - "Accept": [ - "application/json" - ], - "X-Forwarded-For": [ - "158.174.81.143" - ], - "User-Agent": [ - "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Mobile Safari/537.36" - ] - }, - "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" - } - ] + "mockBidRequest": { + "id": "test-request-id", + "user": { + "id": "1kjh3429kjh295jkl", + "ext": { + } + }, + "device": { + "ip": "158.174.81.143", + "ua": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Mobile Safari/537.36" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "auId": "123" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Forwarded-For": [ + "158.174.81.143" + ], + "User-Agent": [ + "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Mobile Safari/537.36" + ] + }, + "uri": "http://whatever.url?format=json&tzo=0", + "body": { + "adUnits": [ + { + "auId": "123", + "targetId": "123-test-imp-id", + "dimensions": [[300,250],[300,600]] + } + ], + "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/size-check.json b/adapters/adnuntius/adnuntiustest/supplemental/size-check.json new file mode 100644 index 00000000000..c05428c123f --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/supplemental/size-check.json @@ -0,0 +1,101 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "id": "1kjh3429kjh295jkl" + }, + "device":{ + "ext":{ + "noCookies" : true + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner":{ + "w": 300, + "h": 600 + } + , + "ext": { + "bidder": { + "auId": "123" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.url?format=json&noCookies=true&tzo=0", + "body": { + "adUnits": [ + { + "auId": "123", + "targetId": "123-test-imp-id", + "dimensions": [[300,600]] + } + ], + "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/status-400.json b/adapters/adnuntius/adnuntiustest/supplemental/status-400.json index c6da39dadd5..f8407b1de5b 100644 --- a/adapters/adnuntius/adnuntiustest/supplemental/status-400.json +++ b/adapters/adnuntius/adnuntiustest/supplemental/status-400.json @@ -1,54 +1,59 @@ { - "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" - } - ] + "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", + "dimensions": [[300,250],[300,600]] + } + ], + "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 index 727cf19498f..2e0f0afcbbd 100644 --- a/adapters/adnuntius/adnuntiustest/supplemental/test-networks.json +++ b/adapters/adnuntius/adnuntiustest/supplemental/test-networks.json @@ -1,97 +1,101 @@ { - "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" - } - } - } - ] - }, + "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", + "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" - } - ] + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.url?format=json&tzo=0", + "body": { + "adUnits": [ + { + "auId": "123", + "targetId": "123-test-imp-id", + "dimensions": [[300,250],[300,600]] + } + ], + "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/video-error.json b/adapters/adnuntius/adnuntiustest/supplemental/video-error.json index 357cb165f36..ed22dc540bf 100644 --- a/adapters/adnuntius/adnuntiustest/supplemental/video-error.json +++ b/adapters/adnuntius/adnuntiustest/supplemental/video-error.json @@ -1,26 +1,25 @@ { - "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" - } - ] + "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 index 2659841197f..1fd8a83f9dd 100644 --- a/adapters/adnuntius/adnuntiustest/supplemental/width-error.json +++ b/adapters/adnuntius/adnuntiustest/supplemental/width-error.json @@ -1,84 +1,88 @@ { - "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" - } - } - } - ] - }, + "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", + "dimensions": [[300,250],[300,600]] + } + ], + "context": "prebid.org", + "metaData": { - "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" - } - ] + } + } + }, + "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 index dd8fafc79a9..c3b42018340 100644 --- a/adapters/adnuntius/params_test.go +++ b/adapters/adnuntius/params_test.go @@ -8,7 +8,7 @@ import ( ) // 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 +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.adnuntius // TestValidParams makes sure that the adnuntius schema accepts all imp.ext fields which we intend to support. func TestValidParams(t *testing.T) { diff --git a/adapters/adocean/adocean.go b/adapters/adocean/adocean.go index d13104b14d7..30dd54e2192 100644 --- a/adapters/adocean/adocean.go +++ b/adapters/adocean/adocean.go @@ -13,7 +13,7 @@ import ( "strings" "text/template" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -60,7 +60,7 @@ type requestData struct { } // Builder builds a new instance of the AdOcean adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { endpointTemplate, err := template.New("endpointTemplate").Parse(config.Endpoint) if err != nil { return nil, errors.New("Unable to parse endpoint template") diff --git a/adapters/adocean/adocean_test.go b/adapters/adocean/adocean_test.go index 531204cdec3..47fe58483a5 100644 --- a/adapters/adocean/adocean_test.go +++ b/adapters/adocean/adocean_test.go @@ -11,7 +11,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAdOcean, config.Adapter{ - Endpoint: "https://{{.Host}}"}) + Endpoint: "https://{{.Host}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -22,7 +22,7 @@ func TestJsonSamples(t *testing.T) { func TestEndpointTemplateMalformed(t *testing.T) { _, buildErr := Builder(openrtb_ext.BidderAdOcean, config.Adapter{ - Endpoint: "{{Malformed}}"}) + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Error(t, buildErr) } diff --git a/adapters/adoppler/adoppler.go b/adapters/adoppler/adoppler.go index 20e88c45a52..406e1fb8eb3 100644 --- a/adapters/adoppler/adoppler.go +++ b/adapters/adoppler/adoppler.go @@ -8,7 +8,7 @@ import ( "net/url" "text/template" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -37,7 +37,7 @@ type AdopplerAdapter struct { } // Builder builds a new instance of the Adoppler adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (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) diff --git a/adapters/adoppler/adoppler_test.go b/adapters/adoppler/adoppler_test.go index fb5cb22bab5..9f026b2f29c 100644 --- a/adapters/adoppler/adoppler_test.go +++ b/adapters/adoppler/adoppler_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAdoppler, config.Adapter{ - Endpoint: "http://{{.AccountID}}.trustedmarketplace.com/processHeaderBid/{{.AdUnit}}"}) + Endpoint: "http://{{.AccountID}}.trustedmarketplace.com/processHeaderBid/{{.AdUnit}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/adot/adot.go b/adapters/adot/adot.go index 55aa0b954a8..73f9adb2dc9 100644 --- a/adapters/adot/adot.go +++ b/adapters/adot/adot.go @@ -7,7 +7,7 @@ import ( "strconv" "strings" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -27,7 +27,7 @@ type bidExt struct { } // Builder builds a new instance of the Adot adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &adapter{ endpoint: config.Endpoint, } diff --git a/adapters/adot/adot_test.go b/adapters/adot/adot_test.go index 99bf9bd968c..d321c6bc3b7 100644 --- a/adapters/adot/adot_test.go +++ b/adapters/adot/adot_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" @@ -16,7 +16,7 @@ const testsBidderEndpoint = "https://dsp.adotmob.com/headerbidding{PUBLISHER_PAT func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAdot, config.Adapter{ - Endpoint: testsBidderEndpoint}) + Endpoint: testsBidderEndpoint}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -38,7 +38,7 @@ func TestMediaTypeError(t *testing.T) { func TestBidResponseNoContent(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAdot, config.Adapter{ - Endpoint: "https://dsp.adotmob.com/headerbidding{PUBLISHER_PATH}/bidrequest"}) + Endpoint: "https://dsp.adotmob.com/headerbidding{PUBLISHER_PATH}/bidrequest"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/adot/params_test.go b/adapters/adot/params_test.go index bc458641694..6760419b470 100644 --- a/adapters/adot/params_test.go +++ b/adapters/adot/params_test.go @@ -9,7 +9,7 @@ import ( // This file actually intends to test static/bidder-params/adot.json // -// These also validate the format of the external API: request.imp[i].ext.adot +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.adot // TestValidParams makes sure that the adot schema accepts all imp.ext fields which we intend to support. func TestValidParams(t *testing.T) { diff --git a/adapters/adpone/adpone.go b/adapters/adpone/adpone.go index 972f1576ac1..43a45c6528a 100644 --- a/adapters/adpone/adpone.go +++ b/adapters/adpone/adpone.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" @@ -14,7 +14,7 @@ import ( ) // Builder builds a new instance of the Adpone adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &adponeAdapter{ endpoint: config.Endpoint, } diff --git a/adapters/adpone/adpone_test.go b/adapters/adpone/adpone_test.go index 78ad51ba095..7b01a382587 100644 --- a/adapters/adpone/adpone_test.go +++ b/adapters/adpone/adpone_test.go @@ -13,7 +13,7 @@ const testsBidderEndpoint = "http://localhost/prebid_server" func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAdpone, config.Adapter{ - Endpoint: testsBidderEndpoint}) + Endpoint: testsBidderEndpoint}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/adprime/adprime.go b/adapters/adprime/adprime.go index c907380db8d..5e4ea2c9dad 100644 --- a/adapters/adprime/adprime.go +++ b/adapters/adprime/adprime.go @@ -6,7 +6,7 @@ import ( "net/http" "strings" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -19,7 +19,7 @@ type AdprimeAdapter struct { } // Builder builds a new instance of the Adprime adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &AdprimeAdapter{ URI: config.Endpoint, } diff --git a/adapters/adprime/adprime_test.go b/adapters/adprime/adprime_test.go index 113233ee507..e5cf7df8df5 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/pserver"}) + Endpoint: "http://delta.adprime.com/pserver"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/adrino/adrino.go b/adapters/adrino/adrino.go new file mode 100644 index 00000000000..496550e0531 --- /dev/null +++ b/adapters/adrino/adrino.go @@ -0,0 +1,85 @@ +package adrino + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/prebid/openrtb/v17/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type adapter struct { + endpoint string +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var errs []error + + reqJson, err := json.Marshal(request) + if err != nil { + errs = append(errs, err) + return nil, errs + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + + return []*adapters.RequestData{{ + Method: "POST", + Uri: a.endpoint, + Body: reqJson, + Headers: headers, + }}, errs +} + +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + var errs []error + + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + + if response.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), + }} + } + + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), + }} + } + + var bidResp openrtb2.BidResponse + + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponse() + bidResponse.Currency = bidResp.Cur + + for _, seatBid := range bidResp.SeatBid { + for i := range seatBid.Bid { + bidType := openrtb_ext.BidTypeNative // our adserver supports only native ads + b := &adapters.TypedBid{ + Bid: &seatBid.Bid[i], + BidType: bidType, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + return bidResponse, errs +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} diff --git a/adapters/adrino/adrino_test.go b/adapters/adrino/adrino_test.go new file mode 100644 index 00000000000..7566f3ed499 --- /dev/null +++ b/adapters/adrino/adrino_test.go @@ -0,0 +1,29 @@ +package adrino + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "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.BidderAdrino, config.Adapter{ + Endpoint: "https://prd-prebid-bidder.adrino.io/openrtb/bid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "adrinotest", bidder) +} + +func TestEndpointTemplateMalformed(t *testing.T) { + _, buildErr := Builder(openrtb_ext.BidderAdrino, config.Adapter{ + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + assert.Nil(t, buildErr) +} diff --git a/adapters/adrino/adrinotest/exemplary/no-bid.json b/adapters/adrino/adrinotest/exemplary/no-bid.json new file mode 100644 index 00000000000..66b64e14690 --- /dev/null +++ b/adapters/adrino/adrinotest/exemplary/no-bid.json @@ -0,0 +1,73 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "ext": { + "bidder": { + "hash": "123456" + } + }, + "native": { + "request": "{json string}", + "ver": "1.2" + } + } + ], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://prd-prebid-bidder.adrino.io/openrtb/bid", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ] + }, + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "ext": { + "bidder": { + "hash": "123456" + } + }, + "native": { + "request": "{json string}", + "ver": "1.2" + } + } + ], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/adrino/adrinotest/exemplary/single-native.json b/adapters/adrino/adrinotest/exemplary/single-native.json new file mode 100644 index 00000000000..41e4b5ef3ef --- /dev/null +++ b/adapters/adrino/adrinotest/exemplary/single-native.json @@ -0,0 +1,115 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "ext": { + "bidder": { + "hash": "123456" + } + }, + "native": { + "request": "{json string}", + "ver": "1.2" + } + } + ], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://prd-prebid-bidder.adrino.io/openrtb/bid", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ] + }, + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "ext": { + "bidder": { + "hash": "123456" + } + }, + "native": { + "request": "{json string}", + "ver": "1.2" + } + } + ], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{json response string}", + "crid": "test-creative-id", + "ext": { + "prebid": { + "type": "native" + } + } + } + ] + } + ], + "cur": "PLN" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "PLN", + "bids": [ + { + "bid": { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{json response string}", + "crid": "test-creative-id", + "ext": { + "prebid": { + "type": "native" + } + } + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/adrino/adrinotest/supplemental/unknown-hash.json b/adapters/adrino/adrinotest/supplemental/unknown-hash.json new file mode 100644 index 00000000000..e678b5267b1 --- /dev/null +++ b/adapters/adrino/adrinotest/supplemental/unknown-hash.json @@ -0,0 +1,73 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "ext": { + "bidder": { + "hash": "some-unknown-hash" + } + }, + "native": { + "request": "{json string}", + "ver": "1.2" + } + } + ], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://prd-prebid-bidder.adrino.io/openrtb/bid", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ] + }, + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "ext": { + "bidder": { + "hash": "some-unknown-hash" + } + }, + "native": { + "request": "{json string}", + "ver": "1.2" + } + } + ], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/adrino/params_test.go b/adapters/adrino/params_test.go new file mode 100644 index 00000000000..f82f08ce9e0 --- /dev/null +++ b/adapters/adrino/params_test.go @@ -0,0 +1,51 @@ +package adrino + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +// This file actually intends to test static/bidder-params/adrino.json +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.BidderAdrino, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected adrino 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.BidderAdrino, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"hash":"abc123"}`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{"hash":""}`, + `{"aid":123, "placementId":"123", "siteId":"321"}`, +} diff --git a/adapters/adtarget/adtarget.go b/adapters/adtarget/adtarget.go index 24befbfec0f..10fd1ad4204 100644 --- a/adapters/adtarget/adtarget.go +++ b/adapters/adtarget/adtarget.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -184,7 +184,7 @@ func validateImpressionAndSetExt(imp *openrtb2.Imp) (int, error) { } // Builder builds a new instance of the Adtarget adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &AdtargetAdapter{ endpoint: config.Endpoint, } diff --git a/adapters/adtarget/adtarget_test.go b/adapters/adtarget/adtarget_test.go index ed21aef0828..2ee45041b09 100644 --- a/adapters/adtarget/adtarget_test.go +++ b/adapters/adtarget/adtarget_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAdtarget, config.Adapter{ - Endpoint: "http://ghb.console.adtarget.com.tr/pbs/ortb"}) + Endpoint: "http://ghb.console.adtarget.com.tr/pbs/ortb"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/adtarget/params_test.go b/adapters/adtarget/params_test.go index b128d11c9cf..4c39639fb7b 100644 --- a/adapters/adtarget/params_test.go +++ b/adapters/adtarget/params_test.go @@ -8,7 +8,7 @@ import ( ) // This file actually intends to test static/bidder-params/adtarget.json -// These also validate the format of the external API: request.imp[i].ext.adtarget +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.adtarget // TestValidParams makes sure that the adtarget schema accepts all imp.ext fields which we intend to support. func TestValidParams(t *testing.T) { diff --git a/adapters/adtelligent/adtelligent.go b/adapters/adtelligent/adtelligent.go index 244742db504..4a5b54ff726 100644 --- a/adapters/adtelligent/adtelligent.go +++ b/adapters/adtelligent/adtelligent.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -185,7 +185,7 @@ func validateImpression(imp *openrtb2.Imp) (int, error) { } // Builder builds a new instance of the Adtelligent adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &AdtelligentAdapter{ endpoint: config.Endpoint, } diff --git a/adapters/adtelligent/adtelligent_test.go b/adapters/adtelligent/adtelligent_test.go index 503b30b576a..948710387b3 100644 --- a/adapters/adtelligent/adtelligent_test.go +++ b/adapters/adtelligent/adtelligent_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAdtelligent, config.Adapter{ - Endpoint: "http://ghb.adtelligent.com/pbs/ortb"}) + Endpoint: "http://ghb.adtelligent.com/pbs/ortb"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/adtelligent/params_test.go b/adapters/adtelligent/params_test.go index eb2aab0d437..227920b25b4 100644 --- a/adapters/adtelligent/params_test.go +++ b/adapters/adtelligent/params_test.go @@ -8,7 +8,7 @@ import ( ) // This file actually intends to test static/bidder-params/adtelligent.json -// These also validate the format of the external API: request.imp[i].ext.adtelligent +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.adtelligent // TestValidParams makes sure that the adtelligent schema accepts all imp.ext fields which we intend to support. func TestValidParams(t *testing.T) { diff --git a/adapters/adtrgtme/adtrgtme.go b/adapters/adtrgtme/adtrgtme.go new file mode 100644 index 00000000000..29ca8ece4bb --- /dev/null +++ b/adapters/adtrgtme/adtrgtme.go @@ -0,0 +1,197 @@ +package adtrgtme + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/prebid/openrtb/v17/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 Adtrgtme adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +func (v *adapter) MakeRequests( + openRTBRequest *openrtb2.BidRequest, + requestInfo *adapters.ExtraRequestInfo, +) ( + []*adapters.RequestData, + []error, +) { + var requests []*adapters.RequestData + var errors []error + + requestCopy := *openRTBRequest + + for _, imp := range openRTBRequest.Imp { + requestCopy.Imp = []openrtb2.Imp{imp} + + requestJSON, err := json.Marshal(openRTBRequest) + if err != nil { + errors = append(errors, err) + continue + } + + requestURI, err := v.buildRequestURI(&requestCopy) + if err != nil { + errors = append(errors, err) + continue + } + + requestData := &adapters.RequestData{ + Method: http.MethodPost, + Uri: requestURI, + Body: requestJSON, + Headers: makeRequestHeaders(&requestCopy), + } + + requests = append(requests, requestData) + } + + return requests, errors +} + +func (v *adapter) buildRequestURI(openRTBRequest *openrtb2.BidRequest) (string, error) { + if openRTBRequest.Site != nil { + if openRTBRequest.Site.ID != "" { + return fmt.Sprintf("%s?s=%s&prebid", v.endpoint, openRTBRequest.Site.ID), nil + } + return "", &errortypes.BadInput{ + Message: "request.Site.ID is not provided", + } + } else if openRTBRequest.App != nil { + if openRTBRequest.App.ID != "" { + return fmt.Sprintf("%s?s=%s&prebid", v.endpoint, openRTBRequest.App.ID), nil + } + return "", &errortypes.BadInput{ + Message: "request.App.ID is not provided", + } + } + return "", &errortypes.BadInput{ + Message: "request.Site or request.App are not provided", + } +} + +func makeRequestHeaders(openRTBRequest *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 openRTBRequest.Device != nil { + if len(openRTBRequest.Device.UA) > 0 { + headers.Add("User-Agent", openRTBRequest.Device.UA) + } + + if len(openRTBRequest.Device.IPv6) > 0 { + headers.Add("X-Forwarded-For", openRTBRequest.Device.IPv6) + } + + if len(openRTBRequest.Device.IP) > 0 { + headers.Add("X-Forwarded-For", openRTBRequest.Device.IP) + } + } + return headers +} + +func (v *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 (v *adapter) MakeBids( + openRTBRequest *openrtb2.BidRequest, + requestToBidder *adapters.RequestData, + bidderRawResponse *adapters.ResponseData, +) ( + *adapters.BidderResponse, + []error, +) { + if bidderRawResponse.StatusCode == http.StatusNoContent { + return nil, nil + } + + httpStatusError := v.checkResponseStatusCodes(bidderRawResponse) + if httpStatusError != nil { + return nil, []error{httpStatusError} + } + + var response openrtb2.BidResponse + if err := json.Unmarshal(bidderRawResponse.Body, &response); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Bad Server Response", + }} + } + + if len(response.SeatBid) == 0 { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Empty SeatBid array", + }} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(openRTBRequest.Imp)) + bidResponse.Currency = response.Cur + for _, seatBid := range response.SeatBid { + for i, bid := range seatBid.Bid { + bidType, err := getMediaTypeForImp(bid.ImpID, openRTBRequest.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) { + for _, imp := range imps { + if imp.ID == impId { + if imp.Banner != nil { + return openrtb_ext.BidTypeBanner, nil + } else { + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Unsupported bidtype for bid: \"%s\"", impId), + } + } + } + } + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Failed to find impression: \"%s\"", impId), + } +} diff --git a/adapters/adtrgtme/adtrgtme_test.go b/adapters/adtrgtme/adtrgtme_test.go new file mode 100644 index 00000000000..91d9b233ffe --- /dev/null +++ b/adapters/adtrgtme/adtrgtme_test.go @@ -0,0 +1,20 @@ +package adtrgtme + +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.BidderAdtrgtme, config.Adapter{ + Endpoint: "http://localhost/ssp"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "adtrgtmetest", bidder) +} diff --git a/adapters/adtrgtme/adtrgtmetest/exemplary/banner-app.json b/adapters/adtrgtme/adtrgtmetest/exemplary/banner-app.json new file mode 100644 index 00000000000..406c1863efb --- /dev/null +++ b/adapters/adtrgtme/adtrgtmetest/exemplary/banner-app.json @@ -0,0 +1,146 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "at": 1, + "tmax": 300, + "imp": [ + { + "banner": { + "w": 300, + "h": 250 + }, + "id": "test-bid-id", + "bidfloor": 1, + "bidfloorcur": "USD", + "secure": 1 + } + ], + "app": { + "publisher": { + "id": "test-site-publisher-id" + }, + "cat": [ + "test-cat" + ], + "bundle": "com.app.test", + "name": "Test App", + "domain": "testapp.com", + "id": "123456789" + }, + "device": { + "ua": "test-device-user-agent", + "ip": "123.123.123.123", + "geo": { + "country": "TST" + }, + "os": "Android", + "language": "en" + }, + "user": { + "id": "test-user-id" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/ssp?s=123456789&prebid", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-device-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "body": { + "id": "test-req-id", + "at": 1, + "tmax": 300, + "imp": [ + { + "banner": { + "w": 300, + "h": 250 + }, + "id": "test-bid-id", + "bidfloor": 1, + "bidfloorcur": "USD", + "secure": 1 + } + ], + "app": { + "publisher": { + "id": "test-site-publisher-id" + }, + "cat": [ + "test-cat" + ], + "bundle": "com.app.test", + "name": "Test App", + "domain": "testapp.com", + "id": "123456789" + }, + "device": { + "ua": "test-device-user-agent", + "ip": "123.123.123.123", + "geo": { + "country": "TST" + }, + "os": "Android", + "language": "en" + }, + "user": { + "id": "test-user-id" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-req-id", + "seatbid": [ + { + "bid": [ + { + "id": "test-bid-id", + "impid": "test-bid-id", + "price": 1, + "adm": "
test
", + "crid": "test-creative-id" + } + ] + } + ], + "bidid": "test-seatbid-id", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-bid-id", + "impid": "test-bid-id", + "price": 1, + "adm": "
test
", + "crid": "test-creative-id" + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/adtrgtme/adtrgtmetest/exemplary/banner-web.json b/adapters/adtrgtme/adtrgtmetest/exemplary/banner-web.json new file mode 100644 index 00000000000..44f14aacd02 --- /dev/null +++ b/adapters/adtrgtme/adtrgtmetest/exemplary/banner-web.json @@ -0,0 +1,142 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "at": 1, + "tmax": 300, + "imp": [ + { + "banner": { + "w": 300, + "h": 250 + }, + "id": "test-bid-id", + "bidfloor": 1, + "bidfloorcur": "USD", + "secure": 1 + } + ], + "site": { + "id": "123456789", + "cat": [ + "test-cat" + ], + "page": "test-page-site", + "publisher": { + "id": "test-site-publisher-id" + } + }, + "device": { + "ua": "test-device-user-agent", + "ip": "123.123.123.123", + "geo": { + "country": "TST" + }, + "os": "Android", + "language": "en" + }, + "user": { + "id": "test-user-id" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/ssp?s=123456789&prebid", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-device-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "body": { + "id": "test-req-id", + "at": 1, + "tmax": 300, + "imp": [ + { + "banner": { + "w": 300, + "h": 250 + }, + "id": "test-bid-id", + "bidfloor": 1, + "bidfloorcur": "USD", + "secure": 1 + } + ], + "site": { + "id": "123456789", + "cat": [ + "test-cat" + ], + "page": "test-page-site", + "publisher": { + "id": "test-site-publisher-id" + } + }, + "device": { + "ua": "test-device-user-agent", + "ip": "123.123.123.123", + "geo": { + "country": "TST" + }, + "os": "Android", + "language": "en" + }, + "user": { + "id": "test-user-id" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-req-id", + "seatbid": [ + { + "bid": [ + { + "id": "test-bid-id", + "impid": "test-bid-id", + "price": 1, + "adm": "
test
", + "crid": "test-creative-id" + } + ] + } + ], + "bidid": "test-seatbid-id", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-bid-id", + "impid": "test-bid-id", + "price": 1, + "adm": "
test
", + "crid": "test-creative-id" + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/adtrgtme/adtrgtmetest/supplemental/banner-app-headers-ipv6.json b/adapters/adtrgtme/adtrgtmetest/supplemental/banner-app-headers-ipv6.json new file mode 100644 index 00000000000..7537a9029c9 --- /dev/null +++ b/adapters/adtrgtme/adtrgtmetest/supplemental/banner-app-headers-ipv6.json @@ -0,0 +1,146 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "at": 1, + "tmax": 300, + "imp": [ + { + "banner": { + "w": 300, + "h": 250 + }, + "id": "test-bid-id", + "bidfloor": 1, + "bidfloorcur": "USD", + "secure": 1 + } + ], + "app": { + "publisher": { + "id": "test-site-publisher-id" + }, + "cat": [ + "test-cat" + ], + "bundle": "com.app.test", + "name": "Test App", + "domain": "testapp.com", + "id": "123456789" + }, + "device": { + "ua": "test-device-user-agent", + "ipv6": "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "geo": { + "country": "TST" + }, + "os": "Android", + "language": "en" + }, + "user": { + "id": "test-user-id" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/ssp?s=123456789&prebid", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-device-user-agent" + ], + "X-Forwarded-For": [ + "2001:0db8:85a3:0000:0000:8a2e:0370:7334" + ] + }, + "body": { + "id": "test-req-id", + "at": 1, + "tmax": 300, + "imp": [ + { + "banner": { + "w": 300, + "h": 250 + }, + "id": "test-bid-id", + "bidfloor": 1, + "bidfloorcur": "USD", + "secure": 1 + } + ], + "app": { + "publisher": { + "id": "test-site-publisher-id" + }, + "cat": [ + "test-cat" + ], + "bundle": "com.app.test", + "name": "Test App", + "domain": "testapp.com", + "id": "123456789" + }, + "device": { + "ua": "test-device-user-agent", + "ipv6": "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "geo": { + "country": "TST" + }, + "os": "Android", + "language": "en" + }, + "user": { + "id": "test-user-id" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-req-id", + "seatbid": [ + { + "bid": [ + { + "id": "test-bid-id", + "impid": "test-bid-id", + "price": 1, + "adm": "
test
", + "crid": "test-creative-id" + } + ] + } + ], + "bidid": "test-seatbid-id", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-bid-id", + "impid": "test-bid-id", + "price": 1, + "adm": "
test
", + "crid": "test-creative-id" + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/adtrgtme/adtrgtmetest/supplemental/banner-web-headers-ipv6.json b/adapters/adtrgtme/adtrgtmetest/supplemental/banner-web-headers-ipv6.json new file mode 100644 index 00000000000..3ad7cf117a8 --- /dev/null +++ b/adapters/adtrgtme/adtrgtmetest/supplemental/banner-web-headers-ipv6.json @@ -0,0 +1,142 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "at": 1, + "tmax": 300, + "imp": [ + { + "banner": { + "w": 300, + "h": 250 + }, + "id": "test-bid-id", + "bidfloor": 1, + "bidfloorcur": "USD", + "secure": 1 + } + ], + "site": { + "id": "123456789", + "cat": [ + "test-cat" + ], + "page": "test-page-site", + "publisher": { + "id": "test-site-publisher-id" + } + }, + "device": { + "ua": "test-device-user-agent", + "ipv6": "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "geo": { + "country": "TST" + }, + "os": "Android", + "language": "en" + }, + "user": { + "id": "test-user-id" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/ssp?s=123456789&prebid", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-device-user-agent" + ], + "X-Forwarded-For": [ + "2001:0db8:85a3:0000:0000:8a2e:0370:7334" + ] + }, + "body": { + "id": "test-req-id", + "at": 1, + "tmax": 300, + "imp": [ + { + "banner": { + "w": 300, + "h": 250 + }, + "id": "test-bid-id", + "bidfloor": 1, + "bidfloorcur": "USD", + "secure": 1 + } + ], + "site": { + "id": "123456789", + "cat": [ + "test-cat" + ], + "page": "test-page-site", + "publisher": { + "id": "test-site-publisher-id" + } + }, + "device": { + "ua": "test-device-user-agent", + "ipv6": "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "geo": { + "country": "TST" + }, + "os": "Android", + "language": "en" + }, + "user": { + "id": "test-user-id" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-req-id", + "seatbid": [ + { + "bid": [ + { + "id": "test-bid-id", + "impid": "test-bid-id", + "price": 1, + "adm": "
test
", + "crid": "test-creative-id" + } + ] + } + ], + "bidid": "test-seatbid-id", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-bid-id", + "impid": "test-bid-id", + "price": 1, + "adm": "
test
", + "crid": "test-creative-id" + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/adtrgtme/adtrgtmetest/supplemental/empty-seatbid-array.json b/adapters/adtrgtme/adtrgtmetest/supplemental/empty-seatbid-array.json new file mode 100644 index 00000000000..0f2b55be12a --- /dev/null +++ b/adapters/adtrgtme/adtrgtmetest/supplemental/empty-seatbid-array.json @@ -0,0 +1,125 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "test-buyer-id" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "test-cat" + ], + "bundle": "com.app.test", + "name": "test-app", + "domain": "test-app.com", + "id": "123456789" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test-adtarget-test", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + } + } + ] + }, + "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://localhost/ssp?s=123456789&prebid", + "body": { + "id": "test-req-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "tagid": "test-adtarget-test" + } + ], + "app": { + "id": "123456789", + "name": "test-app", + "bundle": "com.app.test", + "domain": "test-app.com", + "cat": [ + "test-cat" + ], + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "test-buyer-id" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-resp-id", + "seatbid": [ + ], + "cur": "USD" + } + } + } + ], + "mockResponse": { + "status": 200, + "body": "invalid response" + }, + "expectedMakeBidsErrors": [ + { + "value": "Empty SeatBid array", + "comparison": "literal" + } + ] +} diff --git a/adapters/adtrgtme/adtrgtmetest/supplemental/invalid-response.json b/adapters/adtrgtme/adtrgtmetest/supplemental/invalid-response.json new file mode 100644 index 00000000000..b25ff67db9c --- /dev/null +++ b/adapters/adtrgtme/adtrgtmetest/supplemental/invalid-response.json @@ -0,0 +1,106 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "test-buyer-id" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.test", + "name": "test-app", + "domain": "test-app.com", + "id": "123456789" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test-adtarget-test", + "banner": { + "w": 320, + "h": 50 + } + } + ] + }, + "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://localhost/ssp?s=123456789&prebid", + "body": { + "id": "test-req-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 320, + "h": 50 + }, + "tagid": "test-adtarget-test" + } + ], + "app": { + "id": "123456789", + "name": "test-app", + "bundle": "com.app.test", + "domain": "test-app.com", + "cat": [ + "IAB22-1" + ], + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "test-buyer-id" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": "invalid response" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Bad Server Response", + "comparison": "literal" + } + ] +} diff --git a/adapters/adtrgtme/adtrgtmetest/supplemental/not-found-imp.json b/adapters/adtrgtme/adtrgtmetest/supplemental/not-found-imp.json new file mode 100644 index 00000000000..8ca9a407e73 --- /dev/null +++ b/adapters/adtrgtme/adtrgtmetest/supplemental/not-found-imp.json @@ -0,0 +1,128 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "test-user-id" + }, + "site": { + "id": "123456789", + "page": "mock-site.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "test-unsupported-imp-id", + "tagid": "test-adtarget-tag-id", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + } + } + ] + }, + "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://localhost/ssp?s=123456789&prebid", + "body": { + "id": "test-req-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "test-unsupported-imp-id", + "tagid": "test-adtarget-tag-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + } + } + ], + "site": { + "id": "123456789", + "page": "mock-site.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "test-user-id" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "test-imp-id", + "impid": "test-not-found-imp-id", + "price": 3.5, + "adm": "test-imp-mrkp", + "adomain": [ + "mock-site.com" + ], + "crid": "20", + "w": 1280, + "h": 720 + } + ], + "seat": "aceex" + } + ], + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Failed to find impression: \"test-not-found-imp-id\"", + "comparison": "literal" + } + ] +} diff --git a/adapters/adtrgtme/adtrgtmetest/supplemental/not-provided-app-id.json b/adapters/adtrgtme/adtrgtmetest/supplemental/not-provided-app-id.json new file mode 100644 index 00000000000..49c7924a8a0 --- /dev/null +++ b/adapters/adtrgtme/adtrgtmetest/supplemental/not-provided-app-id.json @@ -0,0 +1,48 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "at": 1, + "tmax": 300, + "imp": [ + { + "banner": { + "w": 300, + "h": 250 + }, + "id": "test-bid-id", + "bidfloor": 1, + "bidfloorcur": "USD", + "secure": 1 + } + ], + "device": { + "ua": "test-device-user-agent", + "ip": "123.123.123.123", + "geo": { + "country": "TST" + }, + "os": "Android", + "language": "en" + }, + "app": { + "publisher": { + "id": "test-site-publisher-id" + }, + "cat": [ + "test-cat" + ], + "bundle": "com.app.test", + "name": "Test App", + "domain": "testapp.com" + }, + "user": { + "id": "test-user-id" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "request.App.ID is not provided", + "comparison": "literal" + } + ] +} diff --git a/adapters/adtrgtme/adtrgtmetest/supplemental/not-provided-site-id.json b/adapters/adtrgtme/adtrgtmetest/supplemental/not-provided-site-id.json new file mode 100644 index 00000000000..bb38d8ef686 --- /dev/null +++ b/adapters/adtrgtme/adtrgtmetest/supplemental/not-provided-site-id.json @@ -0,0 +1,46 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "at": 1, + "tmax": 300, + "imp": [ + { + "banner": { + "w": 300, + "h": 250 + }, + "id": "test-bid-id", + "bidfloor": 1, + "bidfloorcur": "USD", + "secure": 1 + } + ], + "device": { + "ua": "test-device-user-agent", + "ip": "123.123.123.123", + "geo": { + "country": "TST" + }, + "os": "Android", + "language": "en" + }, + "site": { + "cat": [ + "test-cat" + ], + "page": "test-page-site", + "publisher": { + "id": "test-site-publisher-id" + } + }, + "user": { + "id": "test-user-id" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "request.Site.ID is not provided", + "comparison": "literal" + } + ] +} diff --git a/adapters/adtrgtme/adtrgtmetest/supplemental/not-provided-site-or-app.json b/adapters/adtrgtme/adtrgtmetest/supplemental/not-provided-site-or-app.json new file mode 100644 index 00000000000..b4320e1757c --- /dev/null +++ b/adapters/adtrgtme/adtrgtmetest/supplemental/not-provided-site-or-app.json @@ -0,0 +1,37 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "at": 1, + "tmax": 300, + "imp": [ + { + "banner": { + "w": 300, + "h": 250 + }, + "id": "test-bid-id", + "bidfloor": 1, + "bidfloorcur": "USD", + "secure": 1 + } + ], + "device": { + "ua": "test-device-user-agent", + "ip": "123.123.123.123", + "geo": { + "country": "TST" + }, + "os": "Android", + "language": "en" + }, + "user": { + "id": "test-user-id" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "request.Site or request.App are not provided", + "comparison": "literal" + } + ] +} diff --git a/adapters/adtrgtme/adtrgtmetest/supplemental/status-code-bad-request.json b/adapters/adtrgtme/adtrgtmetest/supplemental/status-code-bad-request.json new file mode 100644 index 00000000000..2aab35e5b5c --- /dev/null +++ b/adapters/adtrgtme/adtrgtmetest/supplemental/status-code-bad-request.json @@ -0,0 +1,87 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "tmax": 1000, + "user": { + "buyeruid": "test-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.test", + "name": "test-app", + "domain": "test-app.com", + "id": "123456789" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test-adtarget-tag-id", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/ssp?s=123456789&prebid", + "body": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "tagid": "test-adtarget-tag-id" + } + ], + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.test", + "name": "test-app", + "domain": "test-app.com", + "id": "123456789" + }, + "user": { + "buyeruid": "test-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 400 + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: [ 400 ]", + "comparison": "literal" + } + ] +} diff --git a/adapters/adtrgtme/adtrgtmetest/supplemental/status-code-internal-server-error.json b/adapters/adtrgtme/adtrgtmetest/supplemental/status-code-internal-server-error.json new file mode 100644 index 00000000000..ed8348f8e86 --- /dev/null +++ b/adapters/adtrgtme/adtrgtmetest/supplemental/status-code-internal-server-error.json @@ -0,0 +1,77 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "tmax": 1000, + "user": { + "buyeruid": "test-user" + }, + "site": { + "id": "123456789", + "page": "mock-site.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test-adtarget-tag-id", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/ssp?s=123456789&prebid", + "body": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test-adtarget-tag-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + } + } + ], + "site": { + "id": "123456789", + "page": "mock-site.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "test-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 500 + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: [ 500 ]. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/adtrgtme/adtrgtmetest/supplemental/status-code-no-content.json b/adapters/adtrgtme/adtrgtmetest/supplemental/status-code-no-content.json new file mode 100644 index 00000000000..ba886cf35ef --- /dev/null +++ b/adapters/adtrgtme/adtrgtmetest/supplemental/status-code-no-content.json @@ -0,0 +1,72 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "tmax": 1000, + "user": { + "buyeruid": "test-user" + }, + "site": { + "id": "123456789", + "page": "mock-site.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test-adtarget-tag-id", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/ssp?s=123456789&prebid", + "body": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test-adtarget-tag-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + } + } + ], + "site": { + "id": "123456789", + "page": "mock-site.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "test-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [] +} \ No newline at end of file diff --git a/adapters/adtrgtme/adtrgtmetest/supplemental/status-code-service-unavaliable.json b/adapters/adtrgtme/adtrgtmetest/supplemental/status-code-service-unavaliable.json new file mode 100644 index 00000000000..699117270c1 --- /dev/null +++ b/adapters/adtrgtme/adtrgtmetest/supplemental/status-code-service-unavaliable.json @@ -0,0 +1,77 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "tmax": 1000, + "user": { + "buyeruid": "test-user" + }, + "site": { + "id": "123456789", + "page": "mock-site.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test-adtarget-tag-id", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/ssp?s=123456789&prebid", + "body": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test-adtarget-tag-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + } + } + ], + "site": { + "id": "123456789", + "page": "mock-site.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "test-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/adtrgtme/adtrgtmetest/supplemental/status-code-temporary-redirect.json b/adapters/adtrgtme/adtrgtmetest/supplemental/status-code-temporary-redirect.json new file mode 100644 index 00000000000..159de604cea --- /dev/null +++ b/adapters/adtrgtme/adtrgtmetest/supplemental/status-code-temporary-redirect.json @@ -0,0 +1,77 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "tmax": 1000, + "user": { + "buyeruid": "test-user" + }, + "site": { + "id": "123456789", + "page": "mock-site.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test-adtarget-tag-id", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/ssp?s=123456789&prebid", + "body": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test-adtarget-tag-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + } + } + ], + "site": { + "id": "123456789", + "page": "mock-site.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "test-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 307 + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: [ 307 ]. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/adtrgtme/adtrgtmetest/supplemental/unsupported-bid-type-native.json b/adapters/adtrgtme/adtrgtmetest/supplemental/unsupported-bid-type-native.json new file mode 100644 index 00000000000..e776580965b --- /dev/null +++ b/adapters/adtrgtme/adtrgtmetest/supplemental/unsupported-bid-type-native.json @@ -0,0 +1,118 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "test-user-id" + }, + "site": { + "id": "123456789", + "page": "mock-site.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "test-unsupported-imp-id", + "tagid": "test-adtarget-tag-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}}}" + } + } + ] + }, + "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://localhost/ssp?s=123456789&prebid", + "body": { + "id": "test-req-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "test-unsupported-imp-id", + "tagid": "test-adtarget-tag-id", + "native": { + "ver": "1.1", + "request": "{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}" + } + } + ], + "site": { + "id": "123456789", + "page": "mock-site.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "test-user-id" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "test-imp-id", + "impid": "test-unsupported-imp-id", + "price": 3.5, + "adm": "test-imp-mrkp", + "adomain": [ + "mock-site.com" + ], + "crid": "20", + "w": 1280, + "h": 720 + } + ], + "seat": "aceex" + } + ], + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unsupported bidtype for bid: \"test-unsupported-imp-id\"", + "comparison": "literal" + } + ] +} diff --git a/adapters/adtrgtme/adtrgtmetest/supplemental/unsupported-bid-type-video.json b/adapters/adtrgtme/adtrgtmetest/supplemental/unsupported-bid-type-video.json new file mode 100644 index 00000000000..2394806dfdb --- /dev/null +++ b/adapters/adtrgtme/adtrgtmetest/supplemental/unsupported-bid-type-video.json @@ -0,0 +1,128 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "test-user-id" + }, + "site": { + "id": "123456789", + "page": "mock-site.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "test-unsupported-imp-id", + "tagid": "test-adtarget-tag-id", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + } + } + ] + }, + "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://localhost/ssp?s=123456789&prebid", + "body": { + "id": "test-req-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "test-unsupported-imp-id", + "tagid": "test-adtarget-tag-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + } + } + ], + "site": { + "id": "123456789", + "page": "mock-site.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "test-user-id" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "test-imp-id", + "impid": "test-unsupported-imp-id", + "price": 3.5, + "adm": "test-imp-mrkp", + "adomain": [ + "mock-site.com" + ], + "crid": "20", + "w": 1280, + "h": 720 + } + ], + "seat": "aceex" + } + ], + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unsupported bidtype for bid: \"test-unsupported-imp-id\"", + "comparison": "literal" + } + ] +} diff --git a/adapters/advangelists/advangelists.go b/adapters/advangelists/advangelists.go index d6fce56f487..865947b4bfd 100644 --- a/adapters/advangelists/advangelists.go +++ b/adapters/advangelists/advangelists.go @@ -6,7 +6,7 @@ import ( "net/http" "text/template" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -18,7 +18,7 @@ type AdvangelistsAdapter struct { EndpointTemplate *template.Template } -//MakeRequests prepares request information for prebid-server core +// MakeRequests prepares request information for prebid-server core func (adapter *AdvangelistsAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)) if len(request.Imp) == 0 { @@ -80,7 +80,7 @@ func validateImpression(imp *openrtb2.Imp, impExt *openrtb_ext.ExtImpAdvangelist return nil } -//Group impressions by advangelists-specific parameters `pubid +// Group impressions by advangelists-specific parameters `pubid func dispatchImpressions(imps []openrtb2.Imp, impsExt []openrtb_ext.ExtImpAdvangelists) (map[openrtb_ext.ExtImpAdvangelists][]openrtb2.Imp, []error) { res := make(map[openrtb_ext.ExtImpAdvangelists][]openrtb2.Imp) errors := make([]error, 0) @@ -100,7 +100,7 @@ func dispatchImpressions(imps []openrtb2.Imp, impsExt []openrtb_ext.ExtImpAdvang return res, errors } -//Alter impression info to comply with advangelists platform requirements +// Alter impression info to comply with advangelists platform requirements func compatImpression(imp *openrtb2.Imp) error { imp.Ext = nil //do not forward ext to advangelists platform if imp.Banner != nil { @@ -196,7 +196,7 @@ func (adapter *AdvangelistsAdapter) buildEndpointURL(params *openrtb_ext.ExtImpA return macros.ResolveMacros(adapter.EndpointTemplate, endpointParams) } -//MakeBids translates advangelists bid response to prebid-server specific format +// MakeBids translates advangelists bid response to prebid-server specific format func (adapter *AdvangelistsAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { var msg = "" if response.StatusCode == http.StatusNoContent { @@ -240,7 +240,7 @@ func getMediaTypeForImpID(impID string, imps []openrtb2.Imp) openrtb_ext.BidType } // Builder builds a new instance of the Advangelists adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (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) diff --git a/adapters/advangelists/advangelists_test.go b/adapters/advangelists/advangelists_test.go index 4219c1a0237..e4c5debaa79 100644 --- a/adapters/advangelists/advangelists_test.go +++ b/adapters/advangelists/advangelists_test.go @@ -11,7 +11,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAdvangelists, config.Adapter{ - Endpoint: "http://nep.advangelists.com/xp/get?pubid={{.PublisherID}}"}) + Endpoint: "http://nep.advangelists.com/xp/get?pubid={{.PublisherID}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -22,7 +22,7 @@ func TestJsonSamples(t *testing.T) { func TestEndpointTemplateMalformed(t *testing.T) { _, buildErr := Builder(openrtb_ext.BidderAdvangelists, config.Adapter{ - Endpoint: "{{Malformed}}"}) + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Error(t, buildErr) } diff --git a/adapters/adview/adview.go b/adapters/adview/adview.go index e987298139f..e072da6969c 100644 --- a/adapters/adview/adview.go +++ b/adapters/adview/adview.go @@ -7,7 +7,7 @@ import ( "strings" "text/template" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -20,7 +20,7 @@ type adapter struct { } // 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) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (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) diff --git a/adapters/adview/adview_test.go b/adapters/adview/adview_test.go index 5b872581c9a..d0c993cfb56 100644 --- a/adapters/adview/adview_test.go +++ b/adapters/adview/adview_test.go @@ -11,7 +11,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAdView, config.Adapter{ - Endpoint: "https://bid.adview.com/agent/thirdAdxService/{{.AccountID}}"}) + Endpoint: "https://bid.adview.com/agent/thirdAdxService/{{.AccountID}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -22,7 +22,7 @@ func TestJsonSamples(t *testing.T) { func TestEndpointTemplateMalformed(t *testing.T) { _, buildErr := Builder(openrtb_ext.BidderAdView, config.Adapter{ - Endpoint: "{{Malformed}}"}) + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Error(t, buildErr) } diff --git a/adapters/adxcg/adxcg.go b/adapters/adxcg/adxcg.go index e9f6c94bc40..bac83a6432f 100644 --- a/adapters/adxcg/adxcg.go +++ b/adapters/adxcg/adxcg.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -13,7 +13,7 @@ import ( ) // Builder builds a new instance of the Adxcg adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &adapter{ endpoint: config.Endpoint, } diff --git a/adapters/adxcg/adxcg_test.go b/adapters/adxcg/adxcg_test.go index d01e62f670c..aa5f955c372 100644 --- a/adapters/adxcg/adxcg_test.go +++ b/adapters/adxcg/adxcg_test.go @@ -13,7 +13,7 @@ const testsBidderEndpoint = "http://localhost/prebid_server" func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAdxcg, config.Adapter{ - Endpoint: testsBidderEndpoint}) + Endpoint: testsBidderEndpoint}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/adyoulike/adyoulike.go b/adapters/adyoulike/adyoulike.go index 8537c54da2d..4de52cfbd8c 100644 --- a/adapters/adyoulike/adyoulike.go +++ b/adapters/adyoulike/adyoulike.go @@ -7,14 +7,14 @@ import ( "strings" "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/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" ) -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { return &adapter{ endpoint: config.Endpoint, }, nil diff --git a/adapters/adyoulike/adyoulike_test.go b/adapters/adyoulike/adyoulike_test.go index e4d21a87fb5..d3000f673fc 100644 --- a/adapters/adyoulike/adyoulike_test.go +++ b/adapters/adyoulike/adyoulike_test.go @@ -13,7 +13,7 @@ const testsBidderEndpoint = "https://localhost/bid/4" func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAdyoulike, config.Adapter{ Endpoint: testsBidderEndpoint, - }) + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/adyoulike/params_test.go b/adapters/adyoulike/params_test.go index b57264b3dbd..8aebaf2844e 100644 --- a/adapters/adyoulike/params_test.go +++ b/adapters/adyoulike/params_test.go @@ -9,7 +9,7 @@ import ( // This file actually intends to test static/bidder-params/adyoulike.json // -// These also validate the format of the external API: request.imp[i].ext.adyoulike +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.adyoulike // TestValidParams makes sure that the adyoulike schema accepts all imp.ext fields which we intend to support. func TestValidParams(t *testing.T) { diff --git a/adapters/aja/aja.go b/adapters/aja/aja.go index 6e77fdd5685..66f331bf796 100644 --- a/adapters/aja/aja.go +++ b/adapters/aja/aja.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -124,11 +124,14 @@ func (a *AJAAdapter) MakeBids(bidReq *openrtb2.BidRequest, adapterReq *adapters. } } } + if bidResp.Cur != "" { + bidderResp.Currency = bidResp.Cur + } return bidderResp, errors } // Builder builds a new instance of the AJA adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &AJAAdapter{ endpoint: config.Endpoint, } diff --git a/adapters/aja/aja_test.go b/adapters/aja/aja_test.go index bab5419d889..75e35bedeb0 100644 --- a/adapters/aja/aja_test.go +++ b/adapters/aja/aja_test.go @@ -12,7 +12,7 @@ const testsBidderEndpoint = "https://localhost/bid/4" func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAJA, config.Adapter{ - Endpoint: testsBidderEndpoint}) + Endpoint: testsBidderEndpoint}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/algorix/algorix.go b/adapters/algorix/algorix.go index d24521b4c8d..81c073689f8 100644 --- a/adapters/algorix/algorix.go +++ b/adapters/algorix/algorix.go @@ -7,7 +7,7 @@ import ( "net/url" "text/template" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -28,7 +28,7 @@ type algorixResponseBidExt struct { } // Builder builds a new instance of the AlgoriX adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (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) @@ -135,7 +135,7 @@ func preProcess(request *openrtb2.BidRequest) { if err != nil { continue } - if impExt.Prebid != nil && impExt.Prebid.IsRewardedInventory == 1 { + if impExt.Prebid != nil && impExt.Prebid.IsRewardedInventory != nil && *impExt.Prebid.IsRewardedInventory == 1 { videoCopy := *request.Imp[i].Video videoExt := algorixVideoExt{Rewarded: 1} videoCopy.Ext, err = json.Marshal(&videoExt) diff --git a/adapters/algorix/algorix_test.go b/adapters/algorix/algorix_test.go index 79cf8609c20..762b00dcee4 100644 --- a/adapters/algorix/algorix_test.go +++ b/adapters/algorix/algorix_test.go @@ -1,9 +1,10 @@ package algorix import ( - "github.com/stretchr/testify/assert" "testing" + "github.com/stretchr/testify/assert" + "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" @@ -11,7 +12,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAlgorix, config.Adapter{ - Endpoint: "https://{{.Host}}.test.com?sid={{.SourceId}}&token={{.AccountID}}"}) + Endpoint: "https://{{.Host}}.test.com?sid={{.SourceId}}&token={{.AccountID}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -21,7 +22,7 @@ func TestJsonSamples(t *testing.T) { } func TestEndpointTemplateMalformed(t *testing.T) { - _, buildErr := Builder(openrtb_ext.BidderAlgorix, config.Adapter{Endpoint: "{{Malformed}}"}) + _, buildErr := Builder(openrtb_ext.BidderAlgorix, config.Adapter{Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Error(t, buildErr) } diff --git a/adapters/amx/amx.go b/adapters/amx/amx.go index dc6743a14a9..bad5305e6af 100644 --- a/adapters/amx/amx.go +++ b/adapters/amx/amx.go @@ -6,7 +6,7 @@ import ( "net/http" "net/url" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -22,7 +22,7 @@ type AMXAdapter struct { } // Builder builds a new instance of the AMX adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { endpointURL, err := url.Parse(config.Endpoint) if err != nil { return nil, fmt.Errorf("invalid endpoint: %v", err) diff --git a/adapters/amx/amx_test.go b/adapters/amx/amx_test.go index f4a40c46d13..aaeb631e817 100644 --- a/adapters/amx/amx_test.go +++ b/adapters/amx/amx_test.go @@ -5,7 +5,7 @@ import ( "fmt" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" @@ -22,7 +22,7 @@ const ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAMX, config.Adapter{ - Endpoint: amxTestEndpoint}) + Endpoint: amxTestEndpoint}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -33,14 +33,14 @@ func TestJsonSamples(t *testing.T) { func TestEndpointMalformed(t *testing.T) { _, buildErr := Builder(openrtb_ext.BidderAMX, config.Adapter{ - Endpoint: " http://leading.space.is.invalid"}) + Endpoint: " http://leading.space.is.invalid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Error(t, buildErr) } func TestEndpointQueryStringMalformed(t *testing.T) { _, buildErr := Builder(openrtb_ext.BidderAMX, config.Adapter{ - Endpoint: "http://invalid.query.from.go.docs/page?%gh&%ij"}) + Endpoint: "http://invalid.query.from.go.docs/page?%gh&%ij"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Error(t, buildErr) } @@ -50,7 +50,7 @@ func TestMakeRequestsTagID(t *testing.T) { var width, height int64 = int64(w), int64(h) bidder, buildErr := Builder(openrtb_ext.BidderAMX, config.Adapter{ - Endpoint: amxTestEndpoint}) + Endpoint: amxTestEndpoint}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -113,7 +113,7 @@ func TestMakeRequestsPublisherId(t *testing.T) { var width, height int64 = int64(w), int64(h) bidder, buildErr := Builder(openrtb_ext.BidderAMX, config.Adapter{ - Endpoint: amxTestEndpoint}) + Endpoint: amxTestEndpoint}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -175,7 +175,7 @@ func TestMakeRequestsPublisherId(t *testing.T) { func TestMakeBids(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAMX, config.Adapter{ - Endpoint: amxTestEndpoint}) + Endpoint: amxTestEndpoint}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Failed to build bidder: %v", buildErr) diff --git a/adapters/apacdex/apacdex.go b/adapters/apacdex/apacdex.go index 294b7188940..9e06ee0df7e 100644 --- a/adapters/apacdex/apacdex.go +++ b/adapters/apacdex/apacdex.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -17,7 +17,7 @@ type adapter struct { } // Builder builds a new instance of the Apacdex adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &adapter{ endpoint: config.Endpoint, } diff --git a/adapters/apacdex/apacdex_test.go b/adapters/apacdex/apacdex_test.go index 997302f6ea4..1435850e3eb 100644 --- a/adapters/apacdex/apacdex_test.go +++ b/adapters/apacdex/apacdex_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderApacdex, config.Adapter{ - Endpoint: "//host"}) + Endpoint: "//host"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/apacdex/params_test.go b/adapters/apacdex/params_test.go index 2e5f626e16d..07d846506e5 100644 --- a/adapters/apacdex/params_test.go +++ b/adapters/apacdex/params_test.go @@ -8,7 +8,7 @@ import ( ) // This file actually intends to test static/bidder-params/apacdex.json -// These also validate the format of the external API: request.imp[i].ext.apacdex +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.apacdex // TestValidParams makes sure that the Apacdex schema accepts all imp.ext fields which we intend to support. func TestValidParams(t *testing.T) { diff --git a/adapters/applogy/applogy.go b/adapters/applogy/applogy.go index b144c6b836f..fa77267b958 100644 --- a/adapters/applogy/applogy.go +++ b/adapters/applogy/applogy.go @@ -6,7 +6,7 @@ import ( "net/http" "strconv" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -155,7 +155,7 @@ func (a *ApplogyAdapter) MakeBids(request *openrtb2.BidRequest, _ *adapters.Requ } // Builder builds a new instance of the Applogy adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &ApplogyAdapter{ endpoint: config.Endpoint, } diff --git a/adapters/applogy/applogy_test.go b/adapters/applogy/applogy_test.go index d86c5cacd68..1144f44c83c 100644 --- a/adapters/applogy/applogy_test.go +++ b/adapters/applogy/applogy_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderApplogy, config.Adapter{ - Endpoint: "http://example.com/prebid"}) + Endpoint: "http://example.com/prebid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/appnexus/appnexus.go b/adapters/appnexus/appnexus.go index 3695f541532..7eadd757c02 100644 --- a/adapters/appnexus/appnexus.go +++ b/adapters/appnexus/appnexus.go @@ -4,14 +4,15 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" "math/rand" "net/http" + "os" "strconv" "strings" "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/adcom1" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/adapters" @@ -131,7 +132,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E thisURI = appendMemberId(thisURI, memberId) if len(uniqueIds) > 1 { - errs = append(errs, fmt.Errorf("All request.imp[i].ext.appnexus.member params must match. Request contained: %v", uniqueIds)) + errs = append(errs, fmt.Errorf("All request.imp[i].ext.prebid.bidder.appnexus.member params must match. Request contained: %v", uniqueIds)) } } @@ -308,9 +309,9 @@ func preprocess(imp *openrtb2.Imp, defaultDisplayManagerVer string) (string, boo if imp.Banner != nil { bannerCopy := *imp.Banner if appnexusExt.Position == "above" { - bannerCopy.Pos = openrtb2.AdPositionAboveTheFold.Ptr() + bannerCopy.Pos = adcom1.PositionAboveFold.Ptr() } else if appnexusExt.Position == "below" { - bannerCopy.Pos = openrtb2.AdPositionBelowTheFold.Ptr() + bannerCopy.Pos = adcom1.PositionBelowFold.Ptr() } // Fixes #307 @@ -452,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) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &adapter{ URI: config.Endpoint, iabCategoryMap: loadCategoryMapFromFileSystem(), @@ -473,7 +474,7 @@ func resolvePlatformID(platformID string) int { func loadCategoryMapFromFileSystem() map[string]string { // Load custom options for our adapter (currently just a lookup table to convert appnexus => iab categories) - opts, err := ioutil.ReadFile("./static/adapter/appnexus/opts.json") + opts, err := os.ReadFile("./static/adapter/appnexus/opts.json") if err == nil { var adapterOptions appnexusAdapterOptions diff --git a/adapters/appnexus/appnexus_test.go b/adapters/appnexus/appnexus_test.go index 6399be1a5bb..925a24bb45a 100644 --- a/adapters/appnexus/appnexus_test.go +++ b/adapters/appnexus/appnexus_test.go @@ -10,13 +10,13 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/stretchr/testify/assert" ) func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAppnexus, config.Adapter{ - Endpoint: "http://ib.adnxs.com/openrtb2"}) + Endpoint: "http://ib.adnxs.com/openrtb2"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -28,7 +28,7 @@ func TestJsonSamples(t *testing.T) { func TestVideoSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAppnexus, config.Adapter{ Endpoint: "http://ib.adnxs.com/openrtb2", - PlatformID: "8"}) + PlatformID: "8"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/appnexus/params_test.go b/adapters/appnexus/params_test.go index f84cccc9a4c..4aea1b5a15a 100644 --- a/adapters/appnexus/params_test.go +++ b/adapters/appnexus/params_test.go @@ -9,7 +9,7 @@ import ( // This file actually intends to test static/bidder-params/appnexus.json // -// These also validate the format of the external API: request.imp[i].ext.appnexus +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.appnexus // TestValidParams makes sure that the appnexus schema accepts all imp.ext fields which we intend to support. func TestValidParams(t *testing.T) { diff --git a/adapters/appush/appush.go b/adapters/appush/appush.go new file mode 100644 index 00000000000..fa99fafa81b --- /dev/null +++ b/adapters/appush/appush.go @@ -0,0 +1,154 @@ +package appush + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/prebid/openrtb/v17/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 reqBodyExt struct { + AppushBidderExt reqBodyExtBidder `json:"bidder"` +} + +type reqBodyExtBidder struct { + Type string `json:"type"` + PlacementID string `json:"placementId,omitempty"` + EndpointID string `json:"endpointId,omitempty"` +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var err error + var adapterRequests []*adapters.RequestData + + for _, imp := range request.Imp { + reqCopy := *request + reqCopy.Imp = []openrtb2.Imp{imp} + + var bidderExt adapters.ExtImpBidder + var appushExt openrtb_ext.ImpExtAppush + + if err = json.Unmarshal(reqCopy.Imp[0].Ext, &bidderExt); err != nil { + return nil, []error{err} + } + if err = json.Unmarshal(bidderExt.Bidder, &appushExt); err != nil { + return nil, []error{err} + } + + temp := reqBodyExt{AppushBidderExt: reqBodyExtBidder{}} + + if appushExt.PlacementID != "" { + temp.AppushBidderExt.PlacementID = appushExt.PlacementID + temp.AppushBidderExt.Type = "publisher" + } else if appushExt.EndpointID != "" { + temp.AppushBidderExt.EndpointID = appushExt.EndpointID + temp.AppushBidderExt.Type = "network" + } + + finalyImpExt, err := json.Marshal(temp) + if err != nil { + return nil, []error{err} + } + + reqCopy.Imp[0].Ext = finalyImpExt + + adapterReq, err := a.makeRequest(&reqCopy) + if err != nil { + return nil, []error{err} + } + + if adapterReq != nil { + adapterRequests = append(adapterRequests, adapterReq) + } + } + return adapterRequests, nil +} + +func (a *adapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, error) { + reqJSON, err := json.Marshal(request) + if err != nil { + return nil, err + } + + 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, + }, err +} + +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) { + 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 + } + if imp.Native != nil { + return openrtb_ext.BidTypeNative, nil + } + } + } + + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Failed to find impression \"%s\"", impID), + } +} diff --git a/adapters/appush/appush_test.go b/adapters/appush/appush_test.go new file mode 100644 index 00000000000..65be1c32f0a --- /dev/null +++ b/adapters/appush/appush_test.go @@ -0,0 +1,20 @@ +package appush + +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.BidderAppush, config.Adapter{ + Endpoint: "http://example.com/pserver"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "appushtest", bidder) +} diff --git a/adapters/appush/appushtest/exemplary/endpointId.json b/adapters/appush/appushtest/exemplary/endpointId.json new file mode 100644 index 00000000000..691e1bbcbcd --- /dev/null +++ b/adapters/appush/appushtest/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://example.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": "appush" + } + ], + "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/appush/appushtest/exemplary/simple-banner.json b/adapters/appush/appushtest/exemplary/simple-banner.json new file mode 100644 index 00000000000..5804d6bfa0e --- /dev/null +++ b/adapters/appush/appushtest/exemplary/simple-banner.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": { + "placementId": "test" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.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", + "type": "publisher" + } + } + } + ], + "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": "appush" + } + ], + "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/appush/appushtest/exemplary/simple-native.json b/adapters/appush/appushtest/exemplary/simple-native.json new file mode 100644 index 00000000000..633216b2787 --- /dev/null +++ b/adapters/appush/appushtest/exemplary/simple-native.json @@ -0,0 +1,117 @@ +{ + "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://example.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", + "type": "publisher" + } + } + } + ], + "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": "appush" + } + ], + "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/appush/appushtest/exemplary/simple-video.json b/adapters/appush/appushtest/exemplary/simple-video.json new file mode 100644 index 00000000000..21a0a03d94b --- /dev/null +++ b/adapters/appush/appushtest/exemplary/simple-video.json @@ -0,0 +1,128 @@ +{ + "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://example.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", + "type": "publisher" + } + } + } + ] + } + }, + "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": "appush" + } + ], + "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/appush/appushtest/exemplary/simple-web-banner.json b/adapters/appush/appushtest/exemplary/simple-web-banner.json new file mode 100644 index 00000000000..431170dc4b2 --- /dev/null +++ b/adapters/appush/appushtest/exemplary/simple-web-banner.json @@ -0,0 +1,133 @@ +{ + "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://example.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", + "type": "publisher" + } + } + } + ], + "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": "appush" + } + ], + "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/appush/appushtest/supplemental/bad_media_type.json b/adapters/appush/appushtest/supplemental/bad_media_type.json new file mode 100644 index 00000000000..6489c38aef0 --- /dev/null +++ b/adapters/appush/appushtest/supplemental/bad_media_type.json @@ -0,0 +1,86 @@ +{ + "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://example.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "ext": { + "bidder": { + "placementId": "test", + "type": "publisher" + } + } + } + ], + "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": "appush" + } + ], + "cur": "USD" + } + } + }], + "expectedMakeBidsErrors": [ + { + "value": "Failed to find impression \"test-imp-id\"", + "comparison": "literal" + } + ] +} diff --git a/adapters/appush/appushtest/supplemental/bad_response.json b/adapters/appush/appushtest/supplemental/bad_response.json new file mode 100644 index 00000000000..8c1cc3750db --- /dev/null +++ b/adapters/appush/appushtest/supplemental/bad_response.json @@ -0,0 +1,84 @@ +{ + "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://example.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", + "type": "publisher" + } + } + } + ], + "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/appush/appushtest/supplemental/status-204.json b/adapters/appush/appushtest/supplemental/status-204.json new file mode 100644 index 00000000000..3e536c25a60 --- /dev/null +++ b/adapters/appush/appushtest/supplemental/status-204.json @@ -0,0 +1,79 @@ +{ + "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://example.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", + "type": "publisher" + } + } + } + ], + "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/appush/appushtest/supplemental/status-not-200.json b/adapters/appush/appushtest/supplemental/status-not-200.json new file mode 100644 index 00000000000..b60ca2ccb33 --- /dev/null +++ b/adapters/appush/appushtest/supplemental/status-not-200.json @@ -0,0 +1,84 @@ +{ + "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://example.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", + "type": "publisher" + } + } + } + ], + "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/appush/params_test.go b/adapters/appush/params_test.go new file mode 100644 index 00000000000..a471c3f0300 --- /dev/null +++ b/adapters/appush/params_test.go @@ -0,0 +1,47 @@ +package appush + +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.BidderAppush, 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.BidderAppush, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} + +var validParams = []string{ + `{"placementId": "test"}`, + `{"placementId": "1"}`, + `{"endpointId": "test"}`, + `{"endpointId": "1"}`, +} + +var invalidParams = []string{ + `{"placementId": 42}`, + `{"endpointId": 42}`, + `{"placementId": "1", "endpointId": "1"}`, +} diff --git a/adapters/audienceNetwork/facebook.go b/adapters/audienceNetwork/facebook.go index 63138c42d0f..fab6a916dbd 100644 --- a/adapters/audienceNetwork/facebook.go +++ b/adapters/audienceNetwork/facebook.go @@ -11,7 +11,8 @@ import ( "strings" "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" + "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -261,7 +262,7 @@ func (this *FacebookAdapter) extractPlacementAndPublisher(out *openrtb2.Imp) (st return placementID, publisherID, nil } -// modifyImpCustom modifies the impression after it's marshalled to get around mxmCherry 14.0.0 limitations. +// modifyImpCustom modifies the impression after it's marshalled to add a non-openrtb field. func modifyImpCustom(jsonData []byte, imp *openrtb2.Imp) ([]byte, error) { impType := resolveImpType(imp) @@ -291,7 +292,7 @@ func modifyImpCustom(jsonData []byte, imp *openrtb2.Imp) ([]byte, error) { return jsonData, errors.New("unable to find imp[0].video in json data") } - // mxmCherry omits video.w/h if set to zero, so we need to force set those + // the openrtb library omits video.w/h if set to zero, so we need to force set those // fields to zero post-serialization for the time being videoMap["w"] = json.RawMessage("0") videoMap["h"] = json.RawMessage("0") @@ -413,7 +414,7 @@ func resolveImpType(imp *openrtb2.Imp) openrtb_ext.BidType { } // Builder builds a new instance of Facebook's Audience Network adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { if config.PlatformID == "" { return nil, errors.New("PartnerID is not configured. Did you set adapters.facebook.platform_id in the app config?") } diff --git a/adapters/audienceNetwork/facebook_test.go b/adapters/audienceNetwork/facebook_test.go index a2e17b71ca8..27af5506ab4 100644 --- a/adapters/audienceNetwork/facebook_test.go +++ b/adapters/audienceNetwork/facebook_test.go @@ -46,7 +46,7 @@ func TestJsonSamples(t *testing.T) { Endpoint: "https://an.facebook.com/placementbid.ortb", PlatformID: "test-platform-id", AppSecret: "test-app-secret", - }) + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -63,7 +63,7 @@ func TestMakeTimeoutNoticeApp(t *testing.T) { Endpoint: "https://an.facebook.com/placementbid.ortb", PlatformID: "test-platform-id", AppSecret: "test-app-secret", - }) + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -88,7 +88,7 @@ func TestMakeTimeoutNoticeBadRequest(t *testing.T) { Endpoint: "https://an.facebook.com/placementbid.ortb", PlatformID: "test-platform-id", AppSecret: "test-app-secret", - }) + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -109,7 +109,7 @@ func TestNewFacebookBidderMissingPlatformID(t *testing.T) { bidder, err := Builder(openrtb_ext.BidderAudienceNetwork, config.Adapter{ Endpoint: "https://an.facebook.com/placementbid.ortb", AppSecret: "test-app-secret", - }) + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Empty(t, bidder) assert.EqualError(t, err, "PartnerID is not configured. Did you set adapters.facebook.platform_id in the app config?") @@ -119,7 +119,7 @@ func TestNewFacebookBidderMissingAppSecret(t *testing.T) { bidder, err := Builder(openrtb_ext.BidderAudienceNetwork, config.Adapter{ Endpoint: "https://an.facebook.com/placementbid.ortb", PlatformID: "test-platform-id", - }) + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Empty(t, bidder) assert.EqualError(t, err, "AppSecret is not configured. Did you set adapters.facebook.app_secret in the app config?") diff --git a/adapters/automatad/automatad.go b/adapters/automatad/automatad.go new file mode 100644 index 00000000000..104aa6e5520 --- /dev/null +++ b/adapters/automatad/automatad.go @@ -0,0 +1,77 @@ +package automatad + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/prebid/openrtb/v17/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 Automatad adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (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.StatusBadRequest { + err := &errortypes.BadInput{ + Message: "Unexpected status code: 400. Malformed request syntax received.", + } + return nil, []error{err} + } + + if responseData.StatusCode != http.StatusOK { + err := &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d. Something went wrong on the bidder's side.", 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 { + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &seatBid.Bid[i], + BidType: openrtb_ext.BidTypeBanner, + }) + } + } + return bidResponse, nil +} diff --git a/adapters/automatad/automatad_test.go b/adapters/automatad/automatad_test.go new file mode 100644 index 00000000000..914692741d1 --- /dev/null +++ b/adapters/automatad/automatad_test.go @@ -0,0 +1,20 @@ +package automatad + +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.BidderAutomatad, config.Adapter{ + Endpoint: "http://www.biddertest.com"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "automatadtest", bidder) +} diff --git a/adapters/automatad/automatadtest/exemplary/simple-banner.json b/adapters/automatad/automatadtest/exemplary/simple-banner.json new file mode 100644 index 00000000000..98d4a0a6944 --- /dev/null +++ b/adapters/automatad/automatadtest/exemplary/simple-banner.json @@ -0,0 +1,109 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-banner-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "position": "123", + "placementId": "a34gh6d" + } + } + } + ], + "site": { + "id": "123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://www.biddertest.com", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-banner-imp-id", + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "position": "123", + "placementId": "a34gh6d" + } + } + } + ], + "site": { + "id": "123" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "apacdex", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-banner-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 250, + "w": 300, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-banner-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/automatad/automatadtest/supplemental/bad-request.json b/adapters/automatad/automatadtest/supplemental/bad-request.json new file mode 100644 index 00000000000..df569ea16a8 --- /dev/null +++ b/adapters/automatad/automatadtest/supplemental/bad-request.json @@ -0,0 +1,69 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-banner-imp-id", + "banner": { + "format": [ + { + "w": 200, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "position": "123", + "placementId": "a34gh6d" + } + } + } + ], + "site": { + "id": "123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://www.biddertest.com", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-banner-imp-id", + "banner": { + "format": [ + { + "h": 250, + "w": 200 + } + ] + }, + "ext": { + "bidder": { + "position": "123", + "placementId": "a34gh6d" + } + } + } + ], + "site": { + "id": "123" + } + } + }, + "mockResponse": { + "status": 400, + "body": "bad request" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Malformed request syntax received.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/automatad/automatadtest/supplemental/error-500-request.json b/adapters/automatad/automatadtest/supplemental/error-500-request.json new file mode 100644 index 00000000000..52f87543602 --- /dev/null +++ b/adapters/automatad/automatadtest/supplemental/error-500-request.json @@ -0,0 +1,69 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-banner-imp-id", + "banner": { + "format": [ + { + "w": 200, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "position": "123", + "placementId": "a34gh6d" + } + } + } + ], + "site": { + "id": "123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://www.biddertest.com", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-banner-imp-id", + "banner": { + "format": [ + { + "h": 250, + "w": 200 + } + ] + }, + "ext": { + "bidder": { + "position": "123", + "placementId": "a34gh6d" + } + } + } + ], + "site": { + "id": "123" + } + } + }, + "mockResponse": { + "status": 500, + "body": "Something went wrong on the bidder's side." + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500. Something went wrong on the bidder's side.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/automatad/automatadtest/supplemental/no-content.json b/adapters/automatad/automatadtest/supplemental/no-content.json new file mode 100644 index 00000000000..7e3dec9ed84 --- /dev/null +++ b/adapters/automatad/automatadtest/supplemental/no-content.json @@ -0,0 +1,64 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-banner-imp-id", + "banner": { + "format": [ + { + "w": 200, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "position": "123", + "placementId": "a34gh6d" + } + } + } + ], + "site": { + "id": "123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://www.biddertest.com", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-banner-imp-id", + "banner": { + "format": [ + { + "h": 250, + "w": 200 + } + ] + }, + "ext": { + "bidder": { + "position": "123", + "placementId": "a34gh6d" + } + } + } + ], + "site": { + "id": "123" + } + } + }, + "mockResponse": { + "status": 204, + "body": "" + } + } + ], + "expectedMakeBidsErrors": [] +} \ No newline at end of file diff --git a/adapters/automatad/params_test.go b/adapters/automatad/params_test.go new file mode 100644 index 00000000000..df7f676b70a --- /dev/null +++ b/adapters/automatad/params_test.go @@ -0,0 +1,48 @@ +package automatad + +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 _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderAutomatad, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected valid 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 schema. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderAutomatad, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed invalid params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"position": "123abc"}`, + `{"placementId": "a34gh6d"}`, + `{"position": "123abc", "placementId" : "a34gh6d"}`, +} + +var invalidParams = []string{ + `{"position": 123abc}`, + `"placementId" : 46}`, + `{"position": "123abc", "placementId" : 46}`, + `{"position": 100, "placementId" : "a34gh6d"}`, + `{"position": 100, "placementId" : 200}`, +} diff --git a/adapters/avocet/avocet.go b/adapters/avocet/avocet.go index 7ec4788e858..a8734b2ed3b 100644 --- a/adapters/avocet/avocet.go +++ b/adapters/avocet/avocet.go @@ -5,7 +5,8 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/adcom1" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -110,7 +111,7 @@ func getBidType(bid openrtb2.Bid, ext avocetBidExt) openrtb_ext.BidType { return openrtb_ext.BidTypeVideo } switch bid.API { - case openrtb2.APIFrameworkVPAID10, openrtb2.APIFrameworkVPAID20: + case adcom1.APIVPAID10, adcom1.APIVPAID20: return openrtb_ext.BidTypeVideo default: return openrtb_ext.BidTypeBanner @@ -118,7 +119,7 @@ func getBidType(bid openrtb2.Bid, ext avocetBidExt) openrtb_ext.BidType { } // Builder builds a new instance of the Avocet adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &AvocetAdapter{ Endpoint: config.Endpoint, } diff --git a/adapters/avocet/avocet_test.go b/adapters/avocet/avocet_test.go index 8dc3c333147..7f126fb4d82 100644 --- a/adapters/avocet/avocet_test.go +++ b/adapters/avocet/avocet_test.go @@ -6,7 +6,8 @@ import ( "reflect" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/adcom1" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" @@ -16,7 +17,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAvocet, config.Adapter{ - Endpoint: "https://bid.staging.avct.cloud/ortb/bid/5e722ee9bd6df11d063a8013"}) + Endpoint: "https://bid.staging.avct.cloud/ortb/bid/5e722ee9bd6df11d063a8013"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -205,12 +206,12 @@ func Test_getBidType(t *testing.T) { }{ { name: "VPAID 1.0", - args: args{openrtb2.Bid{API: openrtb2.APIFrameworkVPAID10}, avocetBidExt{}}, + args: args{openrtb2.Bid{API: adcom1.APIVPAID10}, avocetBidExt{}}, want: openrtb_ext.BidTypeVideo, }, { name: "VPAID 2.0", - args: args{openrtb2.Bid{API: openrtb2.APIFrameworkVPAID20}, avocetBidExt{}}, + args: args{openrtb2.Bid{API: adcom1.APIVPAID20}, avocetBidExt{}}, want: openrtb_ext.BidTypeVideo, }, { diff --git a/adapters/axonix/axonix.go b/adapters/axonix/axonix.go index 4a47360fe78..759f9775a22 100644 --- a/adapters/axonix/axonix.go +++ b/adapters/axonix/axonix.go @@ -7,7 +7,7 @@ import ( "net/url" "text/template" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -19,7 +19,7 @@ type adapter struct { EndpointTemplate *template.Template } -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (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) diff --git a/adapters/axonix/axonix_test.go b/adapters/axonix/axonix_test.go index fcac320075f..aa7fdf96b7c 100644 --- a/adapters/axonix/axonix_test.go +++ b/adapters/axonix/axonix_test.go @@ -1,9 +1,10 @@ package axonix import ( - "github.com/stretchr/testify/assert" "testing" + "github.com/stretchr/testify/assert" + "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" @@ -12,7 +13,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAxonix, config.Adapter{ Endpoint: "https://axonix.com/supply/prebid-server/{{.AccountID}}", - }) + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -22,7 +23,7 @@ func TestJsonSamples(t *testing.T) { } func TestEndpointTemplateMalformed(t *testing.T) { - _, buildErr := Builder(openrtb_ext.BidderAxonix, config.Adapter{Endpoint: "{{Malformed}}"}) + _, buildErr := Builder(openrtb_ext.BidderAxonix, config.Adapter{Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Error(t, buildErr) } diff --git a/adapters/axonix/params_test.go b/adapters/axonix/params_test.go index 22b17a862f0..d44fcd15e88 100644 --- a/adapters/axonix/params_test.go +++ b/adapters/axonix/params_test.go @@ -9,7 +9,7 @@ import ( // 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 +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.axonix // TestValidParams makes sure that the Axonix schema accepts all imp.ext fields which we intend to support. func TestValidParams(t *testing.T) { diff --git a/adapters/beachfront/beachfront.go b/adapters/beachfront/beachfront.go index 6eaf5ad9b18..7ff9bc7df6d 100644 --- a/adapters/beachfront/beachfront.go +++ b/adapters/beachfront/beachfront.go @@ -8,7 +8,8 @@ import ( "strconv" "strings" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/adcom1" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -57,27 +58,29 @@ type beachfrontVideoRequest struct { } // --------------------------------------------------- -// Banner +// +// Banner +// // --------------------------------------------------- type beachfrontBannerRequest struct { - Slots []beachfrontSlot `json:"slots"` - Domain string `json:"domain"` - Page string `json:"page"` - Referrer string `json:"referrer"` - Search string `json:"search"` - Secure int8 `json:"secure"` - DeviceOs string `json:"deviceOs"` - DeviceModel string `json:"deviceModel"` - IsMobile int8 `json:"isMobile"` - UA string `json:"ua"` - Dnt int8 `json:"dnt"` - User openrtb2.User `json:"user"` - AdapterName string `json:"adapterName"` - AdapterVersion string `json:"adapterVersion"` - IP string `json:"ip"` - RequestID string `json:"requestId"` - Real204 bool `json:"real204"` - SChain openrtb_ext.ExtRequestPrebidSChainSChain `json:"schain,omitempty"` + Slots []beachfrontSlot `json:"slots"` + Domain string `json:"domain"` + Page string `json:"page"` + Referrer string `json:"referrer"` + Search string `json:"search"` + Secure int8 `json:"secure"` + DeviceOs string `json:"deviceOs"` + DeviceModel string `json:"deviceModel"` + IsMobile int8 `json:"isMobile"` + UA string `json:"ua"` + Dnt int8 `json:"dnt"` + User openrtb2.User `json:"user"` + AdapterName string `json:"adapterName"` + AdapterVersion string `json:"adapterVersion"` + IP string `json:"ip"` + RequestID string `json:"requestId"` + Real204 bool `json:"real204"` + SChain openrtb2.SupplyChain `json:"schain,omitempty"` } type beachfrontSlot struct { @@ -331,7 +334,7 @@ func getBannerRequest(request *openrtb2.BidRequest, reqInfo *adapters.ExtraReque var t = fallBackDeviceType(request) - if t == openrtb2.DeviceTypeMobileTablet { + if t == adcom1.DeviceMobile { bfr.Page = request.App.Bundle if request.App.Domain == "" { bfr.Domain = getDomain(request.App.Domain) @@ -340,7 +343,7 @@ func getBannerRequest(request *openrtb2.BidRequest, reqInfo *adapters.ExtraReque } bfr.IsMobile = 1 - } else if t == openrtb2.DeviceTypePersonalComputer { + } else if t == adcom1.DevicePC { bfr.Page = request.Site.Page if request.Site.Domain == "" { bfr.Domain = getDomain(request.Site.Page) @@ -384,12 +387,12 @@ func getBannerRequest(request *openrtb2.BidRequest, reqInfo *adapters.ExtraReque return bfr, errs } -func fallBackDeviceType(request *openrtb2.BidRequest) openrtb2.DeviceType { +func fallBackDeviceType(request *openrtb2.BidRequest) adcom1.DeviceType { if request.Site != nil { - return openrtb2.DeviceTypePersonalComputer + return adcom1.DevicePC } - return openrtb2.DeviceTypeMobileTablet + return adcom1.DeviceMobile } func getVideoRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]beachfrontVideoRequest, []error) { @@ -746,7 +749,7 @@ func removeVideoElement(slice []beachfrontVideoRequest, s int) []beachfrontVideo } // Builder builds a new instance of the Beachfront adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { extraInfo, err := getExtraInfo(config.ExtraAdapterInfo) if err != nil { return nil, err diff --git a/adapters/beachfront/beachfront_test.go b/adapters/beachfront/beachfront_test.go index 1c1c4ff4469..3e1f75d6c41 100644 --- a/adapters/beachfront/beachfront_test.go +++ b/adapters/beachfront/beachfront_test.go @@ -13,7 +13,7 @@ func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderBeachfront, config.Adapter{ Endpoint: `https://qa.beachrtb.com/prebid_display`, ExtraAdapterInfo: `{"video_endpoint":"https://qa.beachrtb.com/bid.json?exchange_id"}`, - }) + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -26,7 +26,7 @@ func TestExtraInfoDefaultWhenEmpty(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderBeachfront, config.Adapter{ Endpoint: `https://qa.beachrtb.com/prebid_display`, ExtraAdapterInfo: ``, - }) + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -41,7 +41,7 @@ func TestExtraInfoDefaultWhenNotSpecified(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderBeachfront, config.Adapter{ Endpoint: `https://qa.beachrtb.com/prebid_display`, ExtraAdapterInfo: `{"video_endpoint":""}`, - }) + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -56,7 +56,7 @@ func TestExtraInfoMalformed(t *testing.T) { _, buildErr := Builder(openrtb_ext.BidderBeachfront, config.Adapter{ Endpoint: `https://qa.beachrtb.com/prebid_display`, ExtraAdapterInfo: `malformed`, - }) + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Error(t, buildErr) } diff --git a/adapters/beintoo/beintoo.go b/adapters/beintoo/beintoo.go index 60022e73316..82081082a58 100644 --- a/adapters/beintoo/beintoo.go +++ b/adapters/beintoo/beintoo.go @@ -7,7 +7,7 @@ import ( "net/url" "strconv" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -217,7 +217,7 @@ func (a *BeintooAdapter) MakeBids(internalRequest *openrtb2.BidRequest, external } // Builder builds a new instance of the Beintoo adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &BeintooAdapter{ endpoint: config.Endpoint, } diff --git a/adapters/beintoo/beintoo_test.go b/adapters/beintoo/beintoo_test.go index 181b9562577..f95be60169b 100644 --- a/adapters/beintoo/beintoo_test.go +++ b/adapters/beintoo/beintoo_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderBeintoo, config.Adapter{ - Endpoint: "https://ib.beintoo.com"}) + Endpoint: "https://ib.beintoo.com"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/between/between.go b/adapters/between/between.go index f1cdd0ef5af..5a554db0714 100644 --- a/adapters/between/between.go +++ b/adapters/between/between.go @@ -8,7 +8,7 @@ import ( "strconv" "text/template" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -201,7 +201,7 @@ func (a *BetweenAdapter) MakeBids(internalRequest *openrtb2.BidRequest, external } // Builder builds a new instance of the Between adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (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) diff --git a/adapters/between/between_test.go b/adapters/between/between_test.go index 6471b37b187..453a331794f 100644 --- a/adapters/between/between_test.go +++ b/adapters/between/between_test.go @@ -11,7 +11,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderBetween, config.Adapter{ - Endpoint: "http://{{.Host}}/{{.PublisherID}}"}) + Endpoint: "http://{{.Host}}/{{.PublisherID}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -22,7 +22,7 @@ func TestJsonSamples(t *testing.T) { func TestEndpointTemplateMalformed(t *testing.T) { _, buildErr := Builder(openrtb_ext.BidderBetween, config.Adapter{ - Endpoint: "{{Malformed}}"}) + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Error(t, buildErr) } diff --git a/adapters/between/params_test.go b/adapters/between/params_test.go index f0594c87f1d..1907084f9be 100644 --- a/adapters/between/params_test.go +++ b/adapters/between/params_test.go @@ -9,7 +9,7 @@ import ( // This file actually intends to test static/bidder-params/between.json // -// These also validate the format of the external API: request.imp[i].ext.between +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.between // TestValidParams makes sure that the between schema accepts all imp.ext fields which we intend to support. func TestValidParams(t *testing.T) { diff --git a/adapters/beyondmedia/beyondmedia.go b/adapters/beyondmedia/beyondmedia.go new file mode 100644 index 00000000000..dd56d8495a4 --- /dev/null +++ b/adapters/beyondmedia/beyondmedia.go @@ -0,0 +1,147 @@ +package beyondmedia + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/prebid/openrtb/v17/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 reqBodyExt struct { + BeyondMediaBidderExt reqBodyExtBidder `json:"bidder"` +} + +type reqBodyExtBidder struct { + Type string `json:"type"` + PlacementID string `json:"placementId,omitempty"` +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []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 beyondMediaExt openrtb_ext.ImpExtBeyondMedia + + if err = json.Unmarshal(reqCopy.Imp[0].Ext, &bidderExt); err != nil { + return nil, []error{err} + } + if err = json.Unmarshal(bidderExt.Bidder, &beyondMediaExt); err != nil { + return nil, []error{err} + } + + temp := reqBodyExt{BeyondMediaBidderExt: reqBodyExtBidder{}} + temp.BeyondMediaBidderExt.PlacementID = beyondMediaExt.PlacementID + temp.BeyondMediaBidderExt.Type = "publisher" + + finalyImpExt, err := json.Marshal(temp) + if err != nil { + return nil, []error{err} + } + + reqCopy.Imp[0].Ext = finalyImpExt + + adapterReq, err := a.makeRequest(&reqCopy) + if err != nil { + return nil, []error{err} + } + + if adapterReq != nil { + adapterRequests = append(adapterRequests, adapterReq) + } + } + return adapterRequests, nil +} + +func (a *adapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, error) { + reqJSON, err := json.Marshal(request) + if err != nil { + return nil, err + } + + 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, + }, err +} + +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) { + 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 + } + if imp.Native != nil { + return openrtb_ext.BidTypeNative, nil + } + } + } + + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Failed to find impression \"%s\"", impID), + } +} diff --git a/adapters/beyondmedia/beyondmedia_test.go b/adapters/beyondmedia/beyondmedia_test.go new file mode 100644 index 00000000000..9b22d1bb5d1 --- /dev/null +++ b/adapters/beyondmedia/beyondmedia_test.go @@ -0,0 +1,20 @@ +package beyondmedia + +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.BidderBeyondMedia, config.Adapter{ + Endpoint: "http://backend.andbeyond.media/pserver"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "beyondmediatest", bidder) +} diff --git a/adapters/beyondmedia/beyondmediatest/exemplary/simple-banner.json b/adapters/beyondmedia/beyondmediatest/exemplary/simple-banner.json new file mode 100644 index 00000000000..d53a5f1b09d --- /dev/null +++ b/adapters/beyondmedia/beyondmediatest/exemplary/simple-banner.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": { + "placementId": "test" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://backend.andbeyond.media/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", + "type": "publisher" + } + } + } + ], + "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": "beyondmedia" + } + ], + "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/beyondmedia/beyondmediatest/exemplary/simple-native.json b/adapters/beyondmedia/beyondmediatest/exemplary/simple-native.json new file mode 100644 index 00000000000..f0a62312f6e --- /dev/null +++ b/adapters/beyondmedia/beyondmediatest/exemplary/simple-native.json @@ -0,0 +1,117 @@ +{ + "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://backend.andbeyond.media/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", + "type": "publisher" + } + } + } + ], + "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": "beyondmedia" + } + ], + "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/beyondmedia/beyondmediatest/exemplary/simple-video.json b/adapters/beyondmedia/beyondmediatest/exemplary/simple-video.json new file mode 100644 index 00000000000..0b650d62c88 --- /dev/null +++ b/adapters/beyondmedia/beyondmediatest/exemplary/simple-video.json @@ -0,0 +1,128 @@ +{ + "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://backend.andbeyond.media/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", + "type": "publisher" + } + } + } + ] + } + }, + "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": "beyondmedia" + } + ], + "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/beyondmedia/beyondmediatest/exemplary/simple-web-banner.json b/adapters/beyondmedia/beyondmediatest/exemplary/simple-web-banner.json new file mode 100644 index 00000000000..0e04a25d73c --- /dev/null +++ b/adapters/beyondmedia/beyondmediatest/exemplary/simple-web-banner.json @@ -0,0 +1,133 @@ +{ + "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://backend.andbeyond.media/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", + "type": "publisher" + } + } + } + ], + "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": "beyondmedia" + } + ], + "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/beyondmedia/beyondmediatest/supplemental/bad_media_type.json b/adapters/beyondmedia/beyondmediatest/supplemental/bad_media_type.json new file mode 100644 index 00000000000..542529ab490 --- /dev/null +++ b/adapters/beyondmedia/beyondmediatest/supplemental/bad_media_type.json @@ -0,0 +1,86 @@ +{ + "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://backend.andbeyond.media/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "ext": { + "bidder": { + "placementId": "test", + "type": "publisher" + } + } + } + ], + "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": "beyondmedia" + } + ], + "cur": "USD" + } + } + }], + "expectedMakeBidsErrors": [ + { + "value": "Failed to find impression \"test-imp-id\"", + "comparison": "literal" + } + ] +} diff --git a/adapters/beyondmedia/beyondmediatest/supplemental/bad_response.json b/adapters/beyondmedia/beyondmediatest/supplemental/bad_response.json new file mode 100644 index 00000000000..e003ff895e3 --- /dev/null +++ b/adapters/beyondmedia/beyondmediatest/supplemental/bad_response.json @@ -0,0 +1,84 @@ +{ + "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://backend.andbeyond.media/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", + "type": "publisher" + } + } + } + ], + "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/beyondmedia/beyondmediatest/supplemental/status-204.json b/adapters/beyondmedia/beyondmediatest/supplemental/status-204.json new file mode 100644 index 00000000000..1223dd0132c --- /dev/null +++ b/adapters/beyondmedia/beyondmediatest/supplemental/status-204.json @@ -0,0 +1,79 @@ +{ + "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://backend.andbeyond.media/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", + "type": "publisher" + } + } + } + ], + "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/beyondmedia/beyondmediatest/supplemental/status-not-200.json b/adapters/beyondmedia/beyondmediatest/supplemental/status-not-200.json new file mode 100644 index 00000000000..731b33deae8 --- /dev/null +++ b/adapters/beyondmedia/beyondmediatest/supplemental/status-not-200.json @@ -0,0 +1,84 @@ +{ + "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://backend.andbeyond.media/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", + "type": "publisher" + } + } + } + ], + "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/beyondmedia/params_test.go b/adapters/beyondmedia/params_test.go new file mode 100644 index 00000000000..9b0f8e8f6bb --- /dev/null +++ b/adapters/beyondmedia/params_test.go @@ -0,0 +1,45 @@ +package beyondmedia + +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.BidderBeyondMedia, 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.BidderBeyondMedia, 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}`, + `{"endpointId": "1"}`, +} diff --git a/adapters/bidder.go b/adapters/bidder.go index e60227777bf..952ed20fba9 100644 --- a/adapters/bidder.go +++ b/adapters/bidder.go @@ -5,7 +5,7 @@ import ( "encoding/json" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/metrics" @@ -93,12 +93,14 @@ func NewBidderResponse() *BidderResponse { // 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. +// TypedBid.Seat new seat under which the bid should pe placed. Default is adapter name type TypedBid struct { Bid *openrtb2.Bid BidMeta *openrtb_ext.ExtBidPrebidMeta BidType openrtb_ext.BidType BidVideo *openrtb_ext.ExtBidPrebidVideo DealPriority int + Seat openrtb_ext.BidderName } // RequestData and ResponseData exist so that prebid-server core code can implement its "debug" functionality @@ -151,9 +153,9 @@ func NewExtraRequestInfo(c currency.Conversions) ExtraRequestInfo { } // 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. +// - 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 @@ -162,4 +164,4 @@ func (r ExtraRequestInfo) ConvertCurrency(value float64, from, to string) (float } } -type Builder func(openrtb_ext.BidderName, config.Adapter) (Bidder, error) +type Builder func(openrtb_ext.BidderName, config.Adapter, config.Server) (Bidder, error) diff --git a/adapters/bidmachine/bidmachine.go b/adapters/bidmachine/bidmachine.go index eae26c60145..94bf34297f1 100644 --- a/adapters/bidmachine/bidmachine.go +++ b/adapters/bidmachine/bidmachine.go @@ -9,7 +9,9 @@ import ( "strconv" "text/template" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/adcom1" + "github.com/prebid/openrtb/v17/openrtb2" + "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -69,7 +71,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, _ *adapters.ExtraRe errs = append(errs, err) continue } - if bidderExt.Prebid != nil && bidderExt.Prebid.IsRewardedInventory == 1 { + if bidderExt.Prebid != nil && bidderExt.Prebid.IsRewardedInventory != nil && *bidderExt.Prebid.IsRewardedInventory == 1 { if impression.Banner != nil && !hasRewardedBattr(impression.Banner.BAttr) { bannerCopy := *impression.Banner bannerCopy.BAttr = copyBAttrWithRewardedInventory(bannerCopy.BAttr) @@ -100,9 +102,9 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, _ *adapters.ExtraRe return result, errs } -func hasRewardedBattr(attr []openrtb2.CreativeAttribute) bool { +func hasRewardedBattr(attr []adcom1.CreativeAttribute) bool { for i := 0; i < len(attr); i++ { - if attr[i] == openrtb2.CreativeAttribute(16) { + if attr[i] == adcom1.AttrHasSkipButton { return true } } @@ -164,7 +166,7 @@ func (a *adapter) MakeBids(request *openrtb2.BidRequest, _ *adapters.RequestData } // Builder builds a new instance of the Bidmachine adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (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) @@ -198,10 +200,10 @@ func (a *adapter) buildEndpointURL(params openrtb_ext.ExtImpBidmachine) (string, return uri.String(), nil } -func copyBAttrWithRewardedInventory(src []openrtb2.CreativeAttribute) []openrtb2.CreativeAttribute { - dst := make([]openrtb2.CreativeAttribute, len(src)) +func copyBAttrWithRewardedInventory(src []adcom1.CreativeAttribute) []adcom1.CreativeAttribute { + dst := make([]adcom1.CreativeAttribute, len(src)) copy(dst, src) - dst = append(dst, openrtb2.CreativeAttribute(16)) + dst = append(dst, adcom1.AttrHasSkipButton) return dst } diff --git a/adapters/bidmachine/bidmachine_test.go b/adapters/bidmachine/bidmachine_test.go index 9dc82150a82..8f32a2fc60d 100644 --- a/adapters/bidmachine/bidmachine_test.go +++ b/adapters/bidmachine/bidmachine_test.go @@ -11,7 +11,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderBidmachine, config.Adapter{ - Endpoint: "https://{{.Host}}.bidmachine.io"}) + Endpoint: "https://{{.Host}}.bidmachine.io"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -22,7 +22,7 @@ func TestJsonSamples(t *testing.T) { func TestEndpointTemplateMalformed(t *testing.T) { _, buildErr := Builder(openrtb_ext.BidderBidmachine, config.Adapter{ - Endpoint: "{{Malformed}}"}) + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Error(t, buildErr) } diff --git a/adapters/bidmyadz/bidmyadz.go b/adapters/bidmyadz/bidmyadz.go index 829d57e606f..ce903f2b66a 100644 --- a/adapters/bidmyadz/bidmyadz.go +++ b/adapters/bidmyadz/bidmyadz.go @@ -3,12 +3,14 @@ package bidmyadz import ( "encoding/json" "fmt" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "net/http" + + "github.com/prebid/openrtb/v17/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 { @@ -19,7 +21,7 @@ type bidExt struct { MediaType string `json:"mediaType"` } -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &adapter{ endpoint: config.Endpoint, } diff --git a/adapters/bidmyadz/bidmyadz_test.go b/adapters/bidmyadz/bidmyadz_test.go index b0de6a02956..1eb3ef67328 100644 --- a/adapters/bidmyadz/bidmyadz_test.go +++ b/adapters/bidmyadz/bidmyadz_test.go @@ -1,9 +1,10 @@ package bidmyadz import ( - "github.com/stretchr/testify/assert" "testing" + "github.com/stretchr/testify/assert" + "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" @@ -11,7 +12,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderBidmyadz, config.Adapter{ - Endpoint: "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc"}) + Endpoint: "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.NoError(t, buildErr) adapterstest.RunJSONBidderTest(t, "bidmyadztest", bidder) diff --git a/adapters/bidscube/bidscube.go b/adapters/bidscube/bidscube.go index 8b1e5e6601d..ddc00221f75 100644 --- a/adapters/bidscube/bidscube.go +++ b/adapters/bidscube/bidscube.go @@ -8,7 +8,7 @@ import ( "strconv" "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -117,7 +117,7 @@ func getMediaTypeForImp(bidType string) openrtb_ext.BidType { } // Builder builds a new instance of the BidsCube adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &adapter{ endpoint: config.Endpoint, } diff --git a/adapters/bidscube/bidscube_test.go b/adapters/bidscube/bidscube_test.go index 679922727bd..137ba061f65 100644 --- a/adapters/bidscube/bidscube_test.go +++ b/adapters/bidscube/bidscube_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderBidsCube, config.Adapter{ - Endpoint: "http://example.com/?c=o&m=ortb"}) + Endpoint: "http://example.com/?c=o&m=ortb"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) } diff --git a/adapters/bidstack/bidstack.go b/adapters/bidstack/bidstack.go new file mode 100644 index 00000000000..55ce400aebc --- /dev/null +++ b/adapters/bidstack/bidstack.go @@ -0,0 +1,126 @@ +package bidstack + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "strings" + + "github.com/prebid/openrtb/v17/openrtb2" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" +) + +const ( + currencyUSDISO4217 = "USD" + + headerKeyAuthorization = "Authorization" + headerValueAuthorizationBearer = "Bearer " + headerKeyContentType = "Content-Type" + + contentTypeApplicationJSON = "application/json" +) + +type adapter struct { + endpoint string +} + +// Builder builds a new instance of the Bidstack adapter for the given bidder with the given config. +func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + + return bidder, nil +} + +func (a adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + headers, err := prepareHeaders(request) + if err != nil { + return nil, []error{fmt.Errorf("headers prepare: %v", err)} + } + + for i := range request.Imp { + imp := &request.Imp[i] + if imp.BidFloor > 0 && imp.BidFloorCur != "" && strings.ToUpper(imp.BidFloorCur) != currencyUSDISO4217 { + convertedValue, err := reqInfo.ConvertCurrency(imp.BidFloor, imp.BidFloorCur, currencyUSDISO4217) + if err != nil { + return nil, []error{err} + } + imp.BidFloorCur = currencyUSDISO4217 + imp.BidFloor = convertedValue + } + } + + requestJSON, err := json.Marshal(request) + if err != nil { + return nil, []error{fmt.Errorf("bid request marshal: %v", err)} + } + + requestData := &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + Headers: headers, + Body: requestJSON, + } + + return []*adapters.RequestData{requestData}, nil +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, _ *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + switch responseData.StatusCode { + case http.StatusNoContent: + return nil, nil + case http.StatusBadRequest: + return nil, []error{errors.New("bad request from publisher")} + case http.StatusOK: + break + default: + return nil, []error{fmt.Errorf("unexpected response status code: %v", responseData.StatusCode)} + } + + var response openrtb2.BidResponse + if err := json.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{fmt.Errorf("bid response unmarshal: %v", 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.BidTypeVideo, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + + return bidResponse, nil +} + +func prepareHeaders(request *openrtb2.BidRequest) (headers http.Header, err error) { + bidderImpExt, err := getBidderExt(request.Imp[0]) + if err != nil { + return nil, fmt.Errorf("get bidder ext: %v", err) + } + + return http.Header{ + headerKeyContentType: {contentTypeApplicationJSON}, + headerKeyAuthorization: {headerValueAuthorizationBearer + bidderImpExt.PublisherID}, + }, nil +} + +func getBidderExt(imp openrtb2.Imp) (bidderImpExt openrtb_ext.ImpExtBidstack, err error) { + var impExt adapters.ExtImpBidder + if err = json.Unmarshal(imp.Ext, &impExt); err != nil { + return bidderImpExt, fmt.Errorf("imp ext: %v", err) + } + if err = json.Unmarshal(impExt.Bidder, &bidderImpExt); err != nil { + return bidderImpExt, fmt.Errorf("bidder ext: %v", err) + } + return bidderImpExt, nil +} diff --git a/adapters/bidstack/bidstack_test.go b/adapters/bidstack/bidstack_test.go new file mode 100644 index 00000000000..38ed711c39d --- /dev/null +++ b/adapters/bidstack/bidstack_test.go @@ -0,0 +1,50 @@ +package bidstack + +import ( + "net/http" + "testing" + + "github.com/prebid/openrtb/v17/openrtb2" + "github.com/stretchr/testify/assert" + + "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.BidderBidstack, config.Adapter{Endpoint: "http://mock-adserver.url"}, config.Server{}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "bidstacktest", bidder) +} + +func TestPrepareHeaders(t *testing.T) { + publisherID := "12345" + expected := http.Header{ + "Content-Type": {"application/json"}, + "Authorization": {"Bearer " + publisherID}, + } + + actual, err := prepareHeaders(&openrtb2.BidRequest{Imp: []openrtb2.Imp{ + {Ext: []byte(`{"bidder":{"publisherId":"` + publisherID + `"}}`)}}, + }) + + assert.NoError(t, err) + assert.Equal(t, expected, actual) +} + +func TestGetBidderExt(t *testing.T) { + publisherID := "12345" + expected := openrtb_ext.ImpExtBidstack{PublisherID: publisherID} + + actual, err := getBidderExt(openrtb2.Imp{ + Ext: []byte(`{"bidder":{"publisherId":"` + publisherID + `"}}`), + }) + + assert.NoError(t, err) + assert.Equal(t, expected, actual) +} diff --git a/adapters/bidstack/bidstacktest/exemplary/simple-app-video.json b/adapters/bidstack/bidstacktest/exemplary/simple-app-video.json new file mode 100644 index 00000000000..46729f9b7c8 --- /dev/null +++ b/adapters/bidstack/bidstacktest/exemplary/simple-app-video.json @@ -0,0 +1,143 @@ +{ + "mockBidRequest": { + "id": "bid-request-id", + "device": { + "ip": "0.0.0.0", + "ifa": "ce76100d-82cc-4491-a6c9-50e0e539454c" + }, + "app": { + "id": "app-id", + "bundle": "com.app.id" + }, + "imp": [ + { + "id": "imp-id-1", + "bidfloor": 4.20, + "bidfloorcur": "GBP", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "publisherId": "225a4f40-1df1-48b2-b238-8774a638ce6e" + } + } + } + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "GBP": { + "USD": 0.5 + } + }, + "usepbsrates": false + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://mock-adserver.url", + "body": { + "id": "bid-request-id", + "device": { + "ip": "0.0.0.0", + "ifa": "ce76100d-82cc-4491-a6c9-50e0e539454c" + }, + "app": { + "id": "app-id", + "bundle": "com.app.id" + }, + "imp": [ + { + "id": "imp-id-1", + "bidfloor": 2.10, + "bidfloorcur": "USD", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "publisherId": "225a4f40-1df1-48b2-b238-8774a638ce6e" + } + } + } + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "GBP": { + "USD": 0.5 + } + }, + "usepbsrates": false + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "bid-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "bid_id-1", + "impid": "imp-id-1", + "price": 0.27543, + "adm": "", + "cid": "cid", + "crid": "crid", + "dealid": "dealid" + } + ], + "seat": "seat" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "bid_id-1", + "impid": "imp-id-1", + "price": 0.27543, + "adm": "", + "cid": "cid", + "crid": "crid", + "dealid": "dealid" + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/bidstack/bidstacktest/supplemental/currency_rate_not_found.json b/adapters/bidstack/bidstacktest/supplemental/currency_rate_not_found.json new file mode 100644 index 00000000000..b8160c3e46d --- /dev/null +++ b/adapters/bidstack/bidstacktest/supplemental/currency_rate_not_found.json @@ -0,0 +1,54 @@ +{ + "mockBidRequest": { + "id": "bid-request-id", + "device": { + "ip": "0.0.0.0", + "ifa": "ce76100d-82cc-4491-a6c9-50e0e539454c" + }, + "app": { + "id": "app-id", + "bundle": "com.app.id" + }, + "imp": [ + { + "id": "imp-id-1", + "bidfloor": 4.20, + "bidfloorcur": "JPY", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "publisherId": "225a4f40-1df1-48b2-b238-8774a638ce6e" + } + } + } + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "MXN": { + "USD": 0.05 + } + }, + "usepbsrates": false + } + } + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Currency conversion rate not found: 'JPY' => 'USD'", + "comparison": "literal" + } + ] +} diff --git a/adapters/bidstack/bidstacktest/supplemental/status-204.json b/adapters/bidstack/bidstacktest/supplemental/status-204.json new file mode 100644 index 00000000000..2c84a3544d4 --- /dev/null +++ b/adapters/bidstack/bidstacktest/supplemental/status-204.json @@ -0,0 +1,78 @@ +{ + "mockBidRequest": { + "id": "bid-request-id", + "device": { + "ip": "0.0.0.0", + "ifa": "ce76100d-82cc-4491-a6c9-50e0e539454c" + }, + "app": { + "id": "app-id", + "bundle": "com.app.id" + }, + "imp": [ + { + "id": "imp-id-1", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "publisherId": "225a4f40-1df1-48b2-b238-8774a638ce6e" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://mock-adserver.url", + "body": { + "id": "bid-request-id", + "device": { + "ip": "0.0.0.0", + "ifa": "ce76100d-82cc-4491-a6c9-50e0e539454c" + }, + "app": { + "id": "app-id", + "bundle": "com.app.id" + }, + "imp": [ + { + "id": "imp-id-1", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "publisherId": "225a4f40-1df1-48b2-b238-8774a638ce6e" + } + } + } + ] + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/bidstack/bidstacktest/supplemental/status-400.json b/adapters/bidstack/bidstacktest/supplemental/status-400.json new file mode 100644 index 00000000000..eaf5b0e5e04 --- /dev/null +++ b/adapters/bidstack/bidstacktest/supplemental/status-400.json @@ -0,0 +1,83 @@ +{ + "mockBidRequest": { + "id": "bid-request-id", + "device": { + "ip": "0.0.0.0", + "ifa": "ce76100d-82cc-4491-a6c9-50e0e539454c" + }, + "app": { + "id": "app-id", + "bundle": "com.app.id" + }, + "imp": [ + { + "id": "imp-id-1", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "publisherId": "225a4f40-1df1-48b2-b238-8774a638ce6e" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://mock-adserver.url", + "body": { + "id": "bid-request-id", + "device": { + "ip": "0.0.0.0", + "ifa": "ce76100d-82cc-4491-a6c9-50e0e539454c" + }, + "app": { + "id": "app-id", + "bundle": "com.app.id" + }, + "imp": [ + { + "id": "imp-id-1", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "publisherId": "225a4f40-1df1-48b2-b238-8774a638ce6e" + } + } + } + ] + } + }, + "mockResponse": { + "status": 400, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "bad request from publisher", + "comparison": "literal" + } + ] +} diff --git a/adapters/bidstack/bidstacktest/supplemental/status-404.json b/adapters/bidstack/bidstacktest/supplemental/status-404.json new file mode 100644 index 00000000000..7bed7485ebc --- /dev/null +++ b/adapters/bidstack/bidstacktest/supplemental/status-404.json @@ -0,0 +1,83 @@ +{ + "mockBidRequest": { + "id": "bid-request-id", + "device": { + "ip": "0.0.0.0", + "ifa": "ce76100d-82cc-4491-a6c9-50e0e539454c" + }, + "app": { + "id": "app-id", + "bundle": "com.app.id" + }, + "imp": [ + { + "id": "imp-id-1", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "publisherId": "225a4f40-1df1-48b2-b238-8774a638ce6e" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://mock-adserver.url", + "body": { + "id": "bid-request-id", + "device": { + "ip": "0.0.0.0", + "ifa": "ce76100d-82cc-4491-a6c9-50e0e539454c" + }, + "app": { + "id": "app-id", + "bundle": "com.app.id" + }, + "imp": [ + { + "id": "imp-id-1", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "publisherId": "225a4f40-1df1-48b2-b238-8774a638ce6e" + } + } + } + ] + } + }, + "mockResponse": { + "status": 404, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "unexpected response status code: 404", + "comparison": "literal" + } + ] +} diff --git a/adapters/bidstack/params_test.go b/adapters/bidstack/params_test.go new file mode 100644 index 00000000000..832d6000f4c --- /dev/null +++ b/adapters/bidstack/params_test.go @@ -0,0 +1,47 @@ +package bidstack + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +var validParams = []string{ + `{"publisherId": "355cf800-8348-433a-9d95-70345fa70afc"}`, + `{"publisherId": "355cf800-8348-433a-9d95-70345fa70afc","placementId":"Some placement ID"}`, + `{"publisherId": "355cf800-8348-433a-9d95-70345fa70afc","consent":false}`, + `{"publisherId": "355cf800-8348-433a-9d95-70345fa70afc","placementId":"Some placement ID","consent":true}`, +} + +var invalidParams = []string{ + `{"publisherId": ""}`, + `{"publisherId": "non-uuid"}`, + `{"consent": "true"}`, +} + +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.BidderBidstack, 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.BidderBidstack, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} diff --git a/adapters/bizzclick/bizzclick.go b/adapters/bizzclick/bizzclick.go index 70c2bb0cfa8..ce877753163 100644 --- a/adapters/bizzclick/bizzclick.go +++ b/adapters/bizzclick/bizzclick.go @@ -6,7 +6,7 @@ import ( "net/http" "text/template" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -18,7 +18,7 @@ type adapter struct { endpoint *template.Template } -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (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) diff --git a/adapters/bizzclick/bizzclick_test.go b/adapters/bizzclick/bizzclick_test.go index baebb540091..91a9fcd1da4 100644 --- a/adapters/bizzclick/bizzclick_test.go +++ b/adapters/bizzclick/bizzclick_test.go @@ -11,7 +11,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderBizzclick, config.Adapter{ - Endpoint: "http://us.example.com/bid?rtb_seat_id={{.SourceId}}&secret_key={{.AccountID}}"}) + Endpoint: "http://us.example.com/bid?rtb_seat_id={{.SourceId}}&secret_key={{.AccountID}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -22,7 +22,7 @@ func TestJsonSamples(t *testing.T) { func TestEndpointTemplateMalformed(t *testing.T) { _, buildErr := Builder(openrtb_ext.BidderBizzclick, config.Adapter{ - Endpoint: "{{Malformed}}"}) + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Error(t, buildErr) } diff --git a/adapters/bliink/bliink.go b/adapters/bliink/bliink.go index 6e179a3fb34..961d3948e85 100644 --- a/adapters/bliink/bliink.go +++ b/adapters/bliink/bliink.go @@ -3,12 +3,14 @@ package bliink import ( "encoding/json" "fmt" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "net/http" + + "github.com/prebid/openrtb/v17/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 { @@ -16,7 +18,7 @@ type adapter struct { } // Builder builds a new instance of the Bliink adapter for the given bidder with the given config. -func Builder(_ openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(_ openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &adapter{ endpoint: config.Endpoint, } @@ -75,15 +77,16 @@ func (a *adapter) MakeBids(request *openrtb2.BidRequest, _ *adapters.RequestData }} } - bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(response.SeatBid[0].Bid)) + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) bidResponse.Currency = response.Cur - seatBid := response.SeatBid[0] var errs []error - for i, bid := range seatBid.Bid { - mediaType, err := getMediaTypeForImp(bid.ImpID, request.Imp) - if err != nil { - errs = append(errs, err) - } else { + for _, seatBid := range response.SeatBid { + for i, bid := range seatBid.Bid { + mediaType, err := getMediaTypeForImp(bid.ImpID, request.Imp) + if err != nil { + errs = append(errs, err) + continue + } bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ Bid: &seatBid.Bid[i], BidType: mediaType, diff --git a/adapters/bliink/bliink_test.go b/adapters/bliink/bliink_test.go index faa062f89f7..a57a82e1765 100644 --- a/adapters/bliink/bliink_test.go +++ b/adapters/bliink/bliink_test.go @@ -1,15 +1,16 @@ package bliink import ( + "testing" + "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.BidderBliink, config.Adapter{ - Endpoint: "http://biddertest.url/bid"}) + Endpoint: "http://biddertest.url/bid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/bliink/bliinktest/exemplary/banner_native_video.json b/adapters/bliink/bliinktest/exemplary/banner_native_video.json new file mode 100644 index 00000000000..cb652749b2c --- /dev/null +++ b/adapters/bliink/bliinktest/exemplary/banner_native_video.json @@ -0,0 +1,222 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123" + }, + "user": { + "buyeruid": "awesome-user", + "ext": { + "consent": "gdprConsentString" + } + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "test-banner-id", + "tagid": "TAGID", + "banner": { + "w": 320, + "h": 50 + } + }, + { + "id": "test-native-id", + "tagid": "TAGID", + "native": { + "request": "{test json string}", + "ver": "1.2" + } + }, + { + "id": "test-video-id", + "tagid": "TAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 500, + "h": 300, + "minduration": 120, + "maxduration": 150 + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "http://biddertest.url/bid", + "body": { + "id": "test-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123" + }, + "imp": [ + { + "id": "test-banner-id", + "tagid": "TAGID", + "banner": { + "w": 320, + "h": 50 + } + }, + { + "id": "test-native-id", + "tagid": "TAGID", + "native": { + "request": "{test json string}", + "ver": "1.2" + } + }, + { + "id": "test-video-id", + "tagid": "TAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 500, + "h": 300, + "minduration": 120, + "maxduration": 150 + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user", + "ext": { + "consent": "gdprConsentString" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "test-banner-id", + "price": 0.9, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50 + }, + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "test-native-id", + "price": 0.9, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20" + } + ], + "seat": "bliink" + }, + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "test-video-id", + "price": 0.9, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50 + } + ], + "seat": "bliink2" + } + ], + "cur": "EUR" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "test-banner-id", + "price": 0.9, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50 + }, + "type": "banner" + }, + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "test-native-id", + "price": 0.9, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20" + }, + "type": "native" + }, + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "test-video-id", + "price": 0.9, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50 + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/blue/blue.go b/adapters/blue/blue.go new file mode 100644 index 00000000000..cbee5dfc053 --- /dev/null +++ b/adapters/blue/blue.go @@ -0,0 +1,86 @@ +package blue + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/prebid/openrtb/v17/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, server config.Server) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + requestJSON, err := json.Marshal(request) + + if err != nil { + return nil, []error{err} + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + + requestData := &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + 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.StatusOK { + switch responseData.StatusCode { + case http.StatusNoContent: + return nil, nil + case http.StatusBadRequest: + return nil, []error{ + &errortypes.BadInput{ + Message: fmt.Sprintf("Unexpected status code: %d.", responseData.StatusCode), + }, + } + default: + return nil, []error{ + &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d.", responseData.StatusCode), + }, + } + } + } + + 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 +} diff --git a/adapters/blue/blue_test.go b/adapters/blue/blue_test.go new file mode 100644 index 00000000000..d9febec66af --- /dev/null +++ b/adapters/blue/blue_test.go @@ -0,0 +1,20 @@ +package blue + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestBidderBlue(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderInMobi, config.Adapter{ + Endpoint: "https://foo.io/?src=prebid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "bluetest", bidder) +} diff --git a/adapters/blue/bluetest/exemplary/simple-web-banner.json b/adapters/blue/bluetest/exemplary/simple-web-banner.json new file mode 100644 index 00000000000..8f75f53659f --- /dev/null +++ b/adapters/blue/bluetest/exemplary/simple-web-banner.json @@ -0,0 +1,115 @@ +{ + "mockBidRequest": { + "site": { + "page": "https://getblue.io" + }, + "id": "req-id", + "device": { + "ip": "1.1.1.1", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + }, + "imp": [ + { + "ext": { + "bidder": { + "plc": "125566877" + } + }, + "banner": { + "w": 300, + "h": 50 + }, + "id": "blue-id" + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://foo.io/?src=prebid", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ] + }, + "body": { + "site": { + "page": "https://getblue.io" + }, + "id": "req-id", + "device": { + "ip": "1.1.1.1", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + }, + "imp": [ + { + "ext": { + "bidder": { + "plc": "125566877" + } + }, + "banner": { + "w": 300, + "h": 50 + }, + "id": "blue-id" + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "req-id", + "seatbid": [ + { + "bid": [ + { + "ext": { + "prebid": { + "meta": { + "networkName": "blue" + } + } + }, + "nurl": "https://some.event.url/params", + "crid": "123456789", + "adomain": [], + "price": 1.0, + "id": "1234", + "adm": "bannerhtml", + "impid": "blue-id" + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "BRL", + "bids": [ + { + "bid": { + "id": "1234", + "impid": "blue-id", + "price": 1.0, + "adm": "bannerhtml", + "crid": "123456789", + "nurl": "https://some.event.url/params", + "ext": { + "prebid": { + "meta": { + "networkName": "blue" + } + } + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/blue/bluetest/supplemental/204-response-from-target.json b/adapters/blue/bluetest/supplemental/204-response-from-target.json new file mode 100755 index 00000000000..801fdb3e321 --- /dev/null +++ b/adapters/blue/bluetest/supplemental/204-response-from-target.json @@ -0,0 +1,52 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "blue-id", + "banner": { + "format": [ + { + "w": 300, + "h": 50 + } + ] + }, + "ext": { + "bidder": {} + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://foo.io/?src=prebid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "blue-id", + "banner": { + "format": [ + { + "w": 300, + "h": 50 + } + ] + }, + "ext": { + "bidder": {} + } + } + ] + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/blue/bluetest/supplemental/400-response-from-target.json b/adapters/blue/bluetest/supplemental/400-response-from-target.json new file mode 100755 index 00000000000..9daeeec6d23 --- /dev/null +++ b/adapters/blue/bluetest/supplemental/400-response-from-target.json @@ -0,0 +1,57 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "blue-id", + "banner": { + "format": [ + { + "w": 300, + "h": 50 + } + ] + }, + "ext": { + "bidder": {} + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://foo.io/?src=prebid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "blue-id", + "banner": { + "format": [ + { + "w": 300, + "h": 50 + } + ] + }, + "ext": { + "bidder": {} + } + } + ] + } + }, + "mockResponse": { + "status": 400, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/blue/bluetest/supplemental/500-response-from-target.json b/adapters/blue/bluetest/supplemental/500-response-from-target.json new file mode 100755 index 00000000000..5c206fae0bf --- /dev/null +++ b/adapters/blue/bluetest/supplemental/500-response-from-target.json @@ -0,0 +1,57 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "blue-id", + "banner": { + "format": [ + { + "w": 300, + "h": 50 + } + ] + }, + "ext": { + "bidder": {} + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://foo.io/?src=prebid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "blue-id", + "banner": { + "format": [ + { + "w": 300, + "h": 50 + } + ] + }, + "ext": { + "bidder": {} + } + } + ] + } + }, + "mockResponse": { + "status": 500, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/blue/bluetest/supplemental/bad_response.json b/adapters/blue/bluetest/supplemental/bad_response.json new file mode 100644 index 00000000000..2d3e45b2fa9 --- /dev/null +++ b/adapters/blue/bluetest/supplemental/bad_response.json @@ -0,0 +1,57 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "blue-id", + "banner": { + "format": [ + { + "w": 300, + "h": 50 + } + ] + }, + "ext": { + "bidder": {} + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://foo.io/?src=prebid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "blue-id", + "banner": { + "format": [ + { + "w": 300, + "h": 50 + } + ] + }, + "ext": { + "bidder": {} + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": "" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/bmtm/brightmountainmedia.go b/adapters/bmtm/brightmountainmedia.go index ffaacd7c52c..e1824824a3a 100644 --- a/adapters/bmtm/brightmountainmedia.go +++ b/adapters/bmtm/brightmountainmedia.go @@ -6,7 +6,7 @@ import ( "net/http" "strconv" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -18,7 +18,7 @@ type adapter struct { } // Builder builds a new instance of the BrightMountainMedia adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &adapter{ endpoint: config.Endpoint, } diff --git a/adapters/bmtm/brightmountainmedia_test.go b/adapters/bmtm/brightmountainmedia_test.go index 5dbc4820503..0455c8b99b6 100644 --- a/adapters/bmtm/brightmountainmedia_test.go +++ b/adapters/bmtm/brightmountainmedia_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderBmtm, config.Adapter{ - Endpoint: "https://example.com/api/pbs"}) + Endpoint: "https://example.com/api/pbs"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/boldwin/boldwin.go b/adapters/boldwin/boldwin.go new file mode 100644 index 00000000000..88175556832 --- /dev/null +++ b/adapters/boldwin/boldwin.go @@ -0,0 +1,154 @@ +package boldwin + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/prebid/openrtb/v17/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 reqBodyExt struct { + BoldwinBidderExt reqBodyExtBidder `json:"bidder"` +} + +type reqBodyExtBidder struct { + Type string `json:"type"` + PlacementID string `json:"placementId,omitempty"` + EndpointID string `json:"endpointId,omitempty"` +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []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 boldwinExt openrtb_ext.ImpExtBoldwin + + if err = json.Unmarshal(reqCopy.Imp[0].Ext, &bidderExt); err != nil { + return nil, []error{err} + } + if err = json.Unmarshal(bidderExt.Bidder, &boldwinExt); err != nil { + return nil, []error{err} + } + + temp := reqBodyExt{BoldwinBidderExt: reqBodyExtBidder{}} + + if boldwinExt.PlacementID != "" { + temp.BoldwinBidderExt.PlacementID = boldwinExt.PlacementID + temp.BoldwinBidderExt.Type = "publisher" + } else if boldwinExt.EndpointID != "" { + temp.BoldwinBidderExt.EndpointID = boldwinExt.EndpointID + temp.BoldwinBidderExt.Type = "network" + } + + finalyImpExt, err := json.Marshal(temp) + if err != nil { + return nil, []error{err} + } + + reqCopy.Imp[0].Ext = finalyImpExt + + adapterReq, err := a.makeRequest(&reqCopy) + if err != nil { + return nil, []error{err} + } + + if adapterReq != nil { + adapterRequests = append(adapterRequests, adapterReq) + } + } + return adapterRequests, nil +} + +func (a *adapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, error) { + reqJSON, err := json.Marshal(request) + if err != nil { + return nil, err + } + + 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, + }, err +} + +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) { + 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 + } + if imp.Native != nil { + return openrtb_ext.BidTypeNative, nil + } + } + } + + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Failed to find impression \"%s\"", impID), + } +} diff --git a/adapters/boldwin/boldwin_test.go b/adapters/boldwin/boldwin_test.go new file mode 100644 index 00000000000..33d5c68f8de --- /dev/null +++ b/adapters/boldwin/boldwin_test.go @@ -0,0 +1,20 @@ +package boldwin + +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.BidderBoldwin, config.Adapter{ + Endpoint: "http://ssp.videowalldirect.com/pserver"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "boldwintest", bidder) +} diff --git a/adapters/boldwin/boldwintest/exemplary/endpointId.json b/adapters/boldwin/boldwintest/exemplary/endpointId.json new file mode 100644 index 00000000000..442dce7dde9 --- /dev/null +++ b/adapters/boldwin/boldwintest/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://ssp.videowalldirect.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": "boldwin" + } + ], + "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/boldwin/boldwintest/exemplary/simple-banner.json b/adapters/boldwin/boldwintest/exemplary/simple-banner.json new file mode 100644 index 00000000000..ef7d9cc50b0 --- /dev/null +++ b/adapters/boldwin/boldwintest/exemplary/simple-banner.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": { + "placementId": "test" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ssp.videowalldirect.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", + "type": "publisher" + } + } + } + ], + "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": "boldwin" + } + ], + "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/boldwin/boldwintest/exemplary/simple-native.json b/adapters/boldwin/boldwintest/exemplary/simple-native.json new file mode 100644 index 00000000000..f4da8bde006 --- /dev/null +++ b/adapters/boldwin/boldwintest/exemplary/simple-native.json @@ -0,0 +1,117 @@ +{ + "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://ssp.videowalldirect.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", + "type": "publisher" + } + } + } + ], + "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": "boldwin" + } + ], + "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/boldwin/boldwintest/exemplary/simple-video.json b/adapters/boldwin/boldwintest/exemplary/simple-video.json new file mode 100644 index 00000000000..d7ae55b2b7f --- /dev/null +++ b/adapters/boldwin/boldwintest/exemplary/simple-video.json @@ -0,0 +1,128 @@ +{ + "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://ssp.videowalldirect.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", + "type": "publisher" + } + } + } + ] + } + }, + "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": "boldwin" + } + ], + "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/boldwin/boldwintest/exemplary/simple-web-banner.json b/adapters/boldwin/boldwintest/exemplary/simple-web-banner.json new file mode 100644 index 00000000000..3ec17883796 --- /dev/null +++ b/adapters/boldwin/boldwintest/exemplary/simple-web-banner.json @@ -0,0 +1,133 @@ +{ + "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://ssp.videowalldirect.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", + "type": "publisher" + } + } + } + ], + "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": "boldwin" + } + ], + "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/boldwin/boldwintest/supplemental/bad_media_type.json b/adapters/boldwin/boldwintest/supplemental/bad_media_type.json new file mode 100644 index 00000000000..5c6dc24d24e --- /dev/null +++ b/adapters/boldwin/boldwintest/supplemental/bad_media_type.json @@ -0,0 +1,86 @@ +{ + "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://ssp.videowalldirect.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "ext": { + "bidder": { + "placementId": "test", + "type": "publisher" + } + } + } + ], + "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": "boldwin" + } + ], + "cur": "USD" + } + } + }], + "expectedMakeBidsErrors": [ + { + "value": "Failed to find impression \"test-imp-id\"", + "comparison": "literal" + } + ] +} diff --git a/adapters/boldwin/boldwintest/supplemental/bad_response.json b/adapters/boldwin/boldwintest/supplemental/bad_response.json new file mode 100644 index 00000000000..cb874fc349d --- /dev/null +++ b/adapters/boldwin/boldwintest/supplemental/bad_response.json @@ -0,0 +1,84 @@ +{ + "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://ssp.videowalldirect.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", + "type": "publisher" + } + } + } + ], + "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/boldwin/boldwintest/supplemental/status-204.json b/adapters/boldwin/boldwintest/supplemental/status-204.json new file mode 100644 index 00000000000..81aa6401339 --- /dev/null +++ b/adapters/boldwin/boldwintest/supplemental/status-204.json @@ -0,0 +1,79 @@ +{ + "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://ssp.videowalldirect.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", + "type": "publisher" + } + } + } + ], + "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/boldwin/boldwintest/supplemental/status-not-200.json b/adapters/boldwin/boldwintest/supplemental/status-not-200.json new file mode 100644 index 00000000000..9add2335420 --- /dev/null +++ b/adapters/boldwin/boldwintest/supplemental/status-not-200.json @@ -0,0 +1,84 @@ +{ + "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://ssp.videowalldirect.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", + "type": "publisher" + } + } + } + ], + "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/boldwin/params_test.go b/adapters/boldwin/params_test.go new file mode 100644 index 00000000000..f3855746160 --- /dev/null +++ b/adapters/boldwin/params_test.go @@ -0,0 +1,47 @@ +package boldwin + +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.BidderBoldwin, 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.BidderBoldwin, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} + +var validParams = []string{ + `{"placementId": "test"}`, + `{"placementId": "1"}`, + `{"endpointId": "test"}`, + `{"endpointId": "1"}`, +} + +var invalidParams = []string{ + `{"placementId": 42}`, + `{"endpointId": 42}`, + `{"placementId": "1", "endpointId": "1"}`, +} diff --git a/adapters/brightroll/brightroll.go b/adapters/brightroll/brightroll.go index 9a64db45f0b..73f4c4a4a48 100644 --- a/adapters/brightroll/brightroll.go +++ b/adapters/brightroll/brightroll.go @@ -6,7 +6,8 @@ import ( "net/http" "strconv" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/adcom1" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -199,15 +200,15 @@ func (a *BrightrollAdapter) MakeBids(internalRequest *openrtb2.BidRequest, exter return bidResponse, nil } -func getBlockedCreativetypes(attr []int8) []openrtb2.CreativeAttribute { - var creativeAttr []openrtb2.CreativeAttribute +func getBlockedCreativetypes(attr []int8) []adcom1.CreativeAttribute { + var creativeAttr []adcom1.CreativeAttribute for i := 0; i < len(attr); i++ { - creativeAttr = append(creativeAttr, openrtb2.CreativeAttribute(attr[i])) + creativeAttr = append(creativeAttr, adcom1.CreativeAttribute(attr[i])) } return creativeAttr } -//Adding header fields to request header +// Adding header fields to request header func addHeaderIfNonEmpty(headers http.Header, headerName string, headerValue string) { if len(headerValue) > 0 { headers.Add(headerName, headerValue) @@ -229,7 +230,7 @@ func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { } // Builder builds a new instance of the Brightroll adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { extraInfo, err := getExtraInfo(config.ExtraAdapterInfo) if err != nil { return nil, err diff --git a/adapters/brightroll/brightroll_test.go b/adapters/brightroll/brightroll_test.go index a41a9e792c5..4cf0f46fda7 100644 --- a/adapters/brightroll/brightroll_test.go +++ b/adapters/brightroll/brightroll_test.go @@ -14,7 +14,7 @@ func TestEmptyConfig(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderBrightroll, config.Adapter{ Endpoint: `http://test-bid.ybp.yahoo.com/bid/appnexuspbs`, ExtraAdapterInfo: ``, - }) + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -34,7 +34,7 @@ func TestNonEmptyConfig(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderBrightroll, config.Adapter{ Endpoint: `http://test-bid.ybp.yahoo.com/bid/appnexuspbs`, ExtraAdapterInfo: `{"accounts": [{"id": "test","bidfloor":0.1}]}`, - }) + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -54,7 +54,7 @@ func TestMalformedEmpty(t *testing.T) { _, buildErr := Builder(openrtb_ext.BidderBrightroll, config.Adapter{ Endpoint: `http://test-bid.ybp.yahoo.com/bid/appnexuspbs`, ExtraAdapterInfo: `malformed`, - }) + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Error(t, buildErr) } @@ -63,7 +63,7 @@ func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderBrightroll, config.Adapter{ Endpoint: `http://test-bid.ybp.yahoo.com/bid/appnexuspbs`, ExtraAdapterInfo: `{"accounts": [{"id": "adthrive","badv": [], "bcat": ["IAB8-5","IAB8-18"],"battr": [1,2,3], "bidfloor":0.0}]}`, - }) + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/brightroll/params_test.go b/adapters/brightroll/params_test.go index beac822f8f0..c14ee6d73ff 100644 --- a/adapters/brightroll/params_test.go +++ b/adapters/brightroll/params_test.go @@ -9,7 +9,7 @@ import ( // This file actually intends to test static/bidder-params/brightroll.json // -// These also validate the format of the external API: request.imp[i].ext.brightroll +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.brightroll // TestValidParams makes sure that the Brightroll schema accepts all imp.ext fields which we intend to support. func TestValidParams(t *testing.T) { diff --git a/adapters/ccx/ccx.go b/adapters/ccx/ccx.go new file mode 100644 index 00000000000..cc28e60c455 --- /dev/null +++ b/adapters/ccx/ccx.go @@ -0,0 +1,80 @@ +package ccx + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/prebid/openrtb/v17/openrtb2" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" +) + +type adapter struct { + endpoint string +} + +// Builder builds a new instance of the Foo adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (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.StatusBadRequest { + err := &errortypes.BadInput{ + Message: "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.", + } + return nil, []error{err} + } + + if responseData.StatusCode != http.StatusOK { + err := &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info.", responseData.StatusCode), + } + return nil, []error{err} + } + + var response openrtb2.BidResponse + if err := json.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + bidResponse.Currency = response.Cur + for _, seatBid := range response.SeatBid { + for i := range seatBid.Bid { + b := &adapters.TypedBid{ + Bid: &seatBid.Bid[i], + BidType: openrtb_ext.BidTypeBanner, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + return bidResponse, nil +} diff --git a/adapters/ccx/ccx_test.go b/adapters/ccx/ccx_test.go new file mode 100644 index 00000000000..4d02c9848fd --- /dev/null +++ b/adapters/ccx/ccx_test.go @@ -0,0 +1,20 @@ +package ccx + +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.BidderCcx, config.Adapter{ + Endpoint: "https://delivery.clickonometrics.pl/ortb/prebid/bid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "ccxtest", bidder) +} diff --git a/adapters/ccx/ccxtest/exemplary/multi-banner.json b/adapters/ccx/ccxtest/exemplary/multi-banner.json new file mode 100644 index 00000000000..0d6fb2254ae --- /dev/null +++ b/adapters/ccx/ccxtest/exemplary/multi-banner.json @@ -0,0 +1,181 @@ +{ + "mockBidRequest": { + "id": "test-request-multi-id", + "imp": [ + { + "id": "test-imp-id-1", + "banner": { + "format": [{"w": 300, "h": 250}] + }, + "ext": { + "bidder": { + "placementId": 123456789 + } + } + }, + { + "id": "test-imp-id-2", + "banner": { + "format": [{"w": 320, "h": 480}] + }, + "ext": { + "bidder": { + "placementId": 123454321 + } + } + } + ], + "site": { + "page": "somepage.com" + }, + "user": { + "buyeruid": "someuid", + "ext": { + "consent" : "dummy" + } + }, + "regs": { + "ext": { + "gdpr":1 + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://delivery.clickonometrics.pl/ortb/prebid/bid", + "body": { + "id": "test-request-multi-id", + "imp": [ + { + "id": "test-imp-id-1", + "banner": { + "format": [{"w": 300, "h": 250}] + }, + "ext": { + "bidder": { + "placementId": 123456789 + } + } + }, + { + "id": "test-imp-id-2", + "banner": { + "format": [{"w": 320, "h": 480}] + }, + "ext": { + "bidder": { + "placementId": 123454321 + } + } + } + ], + "site": { + "page": "somepage.com" + }, + "user": { + "buyeruid": "someuid", + "ext": { + "consent" : "dummy" + } + }, + "regs": { + "ext": { + "gdpr":1 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-multi-id", + "seatbid": [ + { + "bid": [{ + "id": "11111111-2222-33333333", + "impid": "test-imp-id-1", + "price": 0.500000, + "adm": "some-test-ad-1", + "adomain":[ + "somedomain.com" + ], + "crid": "1234", + "w": 300, + "h": 250, + "ext": { + "type": "standard" + } + }, + { + "id": "11111111-2222-44444444", + "impid": "test-imp-id-2", + "price": 0.500000, + "adm": "some-test-ad-2", + "adomain":[ + "somedomain.com" + ], + "crid": "4321", + "w": 320, + "h": 480, + "ext": { + "type": "standard" + } + }] + } + ], + "cur": "PLN", + "ext": { + "ttl": 10, + "usersync":[] + } + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "PLN", + "bids": [ + { + "bid": { + "id": "11111111-2222-33333333", + "impid": "test-imp-id-1", + "price": 0.5, + "adm": "some-test-ad-1", + "adomain":[ + "somedomain.com" + ], + "crid": "1234", + "w": 300, + "h": 250, + "ext": { + "type": "standard" + } + }, + "type": "banner" + }, + { + "bid": { + "id": "11111111-2222-44444444", + "impid": "test-imp-id-2", + "price": 0.5, + "adm": "some-test-ad-2", + "adomain":[ + "somedomain.com" + ], + "crid": "4321", + "w": 320, + "h": 480, + "ext": { + "type": "standard" + } + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/ccx/ccxtest/exemplary/simple-banner.json b/adapters/ccx/ccxtest/exemplary/simple-banner.json new file mode 100644 index 00000000000..a9ea10c680b --- /dev/null +++ b/adapters/ccx/ccxtest/exemplary/simple-banner.json @@ -0,0 +1,126 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 300, "h": 250}] + }, + "ext": { + "bidder": { + "placementId": 123456789 + } + } + } + ], + "site": { + "page": "somepage.com" + }, + "user": { + "buyeruid": "someuid", + "ext": { + "consent" : "dummy" + } + }, + "regs": { + "ext": { + "gdpr":1 + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://delivery.clickonometrics.pl/ortb/prebid/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 300, "h": 250}] + }, + "ext": { + "bidder": { + "placementId": 123456789 + } + } + } + ], + "site": { + "page": "somepage.com" + }, + "user": { + "buyeruid": "someuid", + "ext": { + "consent" : "dummy" + } + }, + "regs": { + "ext": { + "gdpr":1 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [{ + "id": "11111111-2222-33333333", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "adomain":[ + "somedomain.com" + ], + "crid": "1234", + "w": 300, + "h": 250, + "ext": { + "type": "standard" + } + }] + } + ], + "cur": "PLN", + "ext": { + "ttl": 10, + "usersync":[] + } + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "PLN", + "bids": [ + { + "bid": { + "id": "11111111-2222-33333333", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "adomain":[ + "somedomain.com" + ], + "crid": "1234", + "w": 300, + "h": 250, + "ext": { + "type": "standard" + } + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/ccx/ccxtest/supplemental/204-response-from-target.json b/adapters/ccx/ccxtest/supplemental/204-response-from-target.json new file mode 100644 index 00000000000..75b4dfd19b8 --- /dev/null +++ b/adapters/ccx/ccxtest/supplemental/204-response-from-target.json @@ -0,0 +1,53 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 300, "h": 250}] + }, + "ext": { + "bidder": { + "placementId": 123456789 + } + } + } + ], + "site": { + "page": "somepage.com" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://delivery.clickonometrics.pl/ortb/prebid/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 300, "h": 250}] + }, + "ext": { + "bidder": { + "placementId": 123456789 + } + } + } + ], + "site": { + "page": "somepage.com" + } + } + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedMakeBidsErrors": [] + } + \ No newline at end of file diff --git a/adapters/ccx/ccxtest/supplemental/400-response-from-target.json b/adapters/ccx/ccxtest/supplemental/400-response-from-target.json new file mode 100644 index 00000000000..a57394cd90b --- /dev/null +++ b/adapters/ccx/ccxtest/supplemental/400-response-from-target.json @@ -0,0 +1,58 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 300, "h": 250}] + }, + "ext": { + "bidder": { + "placementId": 123456789 + } + } + } + ], + "site": { + "page": "somepage.com" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://delivery.clickonometrics.pl/ortb/prebid/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 300, "h": 250}] + }, + "ext": { + "bidder": { + "placementId": 123456789 + } + } + } + ], + "site": { + "page": "somepage.com" + } + } + }, + "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/ccx/ccxtest/supplemental/500-response-from-target.json b/adapters/ccx/ccxtest/supplemental/500-response-from-target.json new file mode 100644 index 00000000000..2ddd51f9dd1 --- /dev/null +++ b/adapters/ccx/ccxtest/supplemental/500-response-from-target.json @@ -0,0 +1,58 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 300, "h": 250}] + }, + "ext": { + "bidder": { + "placementId": 123456789 + } + } + } + ], + "site": { + "page": "somepage.com" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://delivery.clickonometrics.pl/ortb/prebid/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 300, "h": 250}] + }, + "ext": { + "bidder": { + "placementId": 123456789 + } + } + } + ], + "site": { + "page": "somepage.com" + } + } + }, + "mockResponse": { + "status": 500 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500. Run with request.debug = 1 for more info.", + "comparison": "literal" + } + ] + } + \ No newline at end of file diff --git a/adapters/ccx/params_test.go b/adapters/ccx/params_test.go new file mode 100644 index 00000000000..ecd9421333b --- /dev/null +++ b/adapters/ccx/params_test.go @@ -0,0 +1,48 @@ +package ccx + +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.BidderCcx, 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.BidderCcx, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} + +var validParams = []string{ + `{"placementId": 123456789}`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `[]`, + `{}`, + `{"placementId": ""}`, + `{"placementId": "123456789"}`, +} diff --git a/adapters/coinzilla/coinzilla.go b/adapters/coinzilla/coinzilla.go index 8535133d152..5f3c0dc9a95 100644 --- a/adapters/coinzilla/coinzilla.go +++ b/adapters/coinzilla/coinzilla.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" @@ -13,7 +13,7 @@ import ( "github.com/prebid/prebid-server/errortypes" ) -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &adapter{ endpoint: config.Endpoint, } diff --git a/adapters/coinzilla/coinzilla_test.go b/adapters/coinzilla/coinzilla_test.go index 7b0763e40fa..6c79a2b12d1 100644 --- a/adapters/coinzilla/coinzilla_test.go +++ b/adapters/coinzilla/coinzilla_test.go @@ -13,7 +13,7 @@ const testsBidderEndpoint = "http://test-request.com/prebid" func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderCoinzilla, config.Adapter{ - Endpoint: testsBidderEndpoint}) + Endpoint: testsBidderEndpoint}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/colossus/colossus.go b/adapters/colossus/colossus.go index 16fe5e0a456..6fb19871fd9 100644 --- a/adapters/colossus/colossus.go +++ b/adapters/colossus/colossus.go @@ -6,7 +6,7 @@ import ( "net/http" "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -22,7 +22,7 @@ type ColossusResponseBidExt struct { } // Builder builds a new instance of the Colossus adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &ColossusAdapter{ URI: config.Endpoint, } diff --git a/adapters/colossus/colossus_test.go b/adapters/colossus/colossus_test.go index 7baa423af70..3b0f0fc5efe 100644 --- a/adapters/colossus/colossus_test.go +++ b/adapters/colossus/colossus_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderColossus, config.Adapter{ - Endpoint: "http://example.com/?c=o&m=rtb"}) + Endpoint: "http://example.com/?c=o&m=rtb"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/compass/compass.go b/adapters/compass/compass.go index eed846576bb..fe6f1b0900b 100644 --- a/adapters/compass/compass.go +++ b/adapters/compass/compass.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -26,7 +26,7 @@ type reqBodyExtBidder struct { EndpointID string `json:"endpointId,omitempty"` } -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &adapter{ endpoint: config.Endpoint, } diff --git a/adapters/compass/compass_test.go b/adapters/compass/compass_test.go index fce791e5997..ad7a4ecf61b 100644 --- a/adapters/compass/compass_test.go +++ b/adapters/compass/compass_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderCompass, config.Adapter{ - Endpoint: "http://sa-lb.deliverimp.com/pserver"}) + Endpoint: "http://sa-lb.deliverimp.com/pserver"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/connectad/connectad.go b/adapters/connectad/connectad.go index 5c30e3a6adc..53133d50944 100644 --- a/adapters/connectad/connectad.go +++ b/adapters/connectad/connectad.go @@ -7,7 +7,7 @@ import ( "net/url" "strconv" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -19,7 +19,7 @@ type ConnectAdAdapter struct { } // 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) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &ConnectAdAdapter{ endpoint: config.Endpoint, } diff --git a/adapters/connectad/connectad_test.go b/adapters/connectad/connectad_test.go index 36005219ce8..037ccbb0a3d 100644 --- a/adapters/connectad/connectad_test.go +++ b/adapters/connectad/connectad_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderConnectAd, config.Adapter{ - Endpoint: "http://bidder.connectad.io/API?src=pbs"}) + Endpoint: "http://bidder.connectad.io/API?src=pbs"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/consumable/adtypes.go b/adapters/consumable/adtypes.go index 5c5bd425bbf..f96a87f71d6 100644 --- a/adapters/consumable/adtypes.go +++ b/adapters/consumable/adtypes.go @@ -3,7 +3,7 @@ package consumable import ( "strconv" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" ) /* Turn array of openrtb formats into consumable's code*/ diff --git a/adapters/consumable/consumable.go b/adapters/consumable/consumable.go index c2e0c201187..dc9759c2fde 100644 --- a/adapters/consumable/consumable.go +++ b/adapters/consumable/consumable.go @@ -8,7 +8,7 @@ import ( "strconv" "strings" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -22,21 +22,24 @@ type ConsumableAdapter struct { } type bidRequest struct { - Placements []placement `json:"placements"` - Time int64 `json:"time"` - NetworkId int `json:"networkId,omitempty"` - SiteId int `json:"siteId"` - UnitId int `json:"unitId"` - UnitName string `json:"unitName,omitempty"` - IncludePricingData bool `json:"includePricingData"` - User user `json:"user,omitempty"` - Referrer string `json:"referrer,omitempty"` - Ip string `json:"ip,omitempty"` - Url string `json:"url,omitempty"` - EnableBotFiltering bool `json:"enableBotFiltering,omitempty"` - Parallel bool `json:"parallel"` - CCPA string `json:"ccpa,omitempty"` - GDPR *bidGdpr `json:"gdpr,omitempty"` + Placements []placement `json:"placements"` + Time int64 `json:"time"` + NetworkId int `json:"networkId,omitempty"` + SiteId int `json:"siteId"` + UnitId int `json:"unitId"` + UnitName string `json:"unitName,omitempty"` + IncludePricingData bool `json:"includePricingData"` + User user `json:"user,omitempty"` + Referrer string `json:"referrer,omitempty"` + Ip string `json:"ip,omitempty"` + Url string `json:"url,omitempty"` + EnableBotFiltering bool `json:"enableBotFiltering,omitempty"` + Parallel bool `json:"parallel"` + CCPA string `json:"ccpa,omitempty"` + GDPR *bidGdpr `json:"gdpr,omitempty"` + Coppa bool `json:"coppa,omitempty"` + SChain openrtb2.SupplyChain `json:"schain"` + Content *openrtb2.Content `json:"content,omitempty"` } type placement struct { @@ -49,7 +52,8 @@ type placement struct { } type user struct { - Key string `json:"key,omitempty"` + Key string `json:"key,omitempty"` + Eids []openrtb2.EID `json:"eids,omitempty"` } type bidGdpr struct { @@ -84,6 +88,8 @@ type pricing struct { } func (a *ConsumableAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var errs []error + headers := http.Header{ "Content-Type": {"application/json"}, "Accept": {"application/json"}, @@ -112,13 +118,14 @@ func (a *ConsumableAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo * headers.Set("Referer", request.Site.Page) pageUrl, err := url.Parse(request.Site.Page) - if err == nil { + if err != nil { + errs = append(errs, err) + } else { origin := url.URL{ Scheme: pageUrl.Scheme, Opaque: pageUrl.Opaque, Host: pageUrl.Host, } - headers.Set("Origin", origin.String()) } } @@ -139,14 +146,17 @@ func (a *ConsumableAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo * gdpr := bidGdpr{} ccpaPolicy, err := ccpa.ReadFromRequest(request) - if err == nil { + if err != nil { + errs = append(errs, err) + } else { body.CCPA = ccpaPolicy.Consent } - // TODO: Replace with gdpr.ReadPolicy when it is available if request.Regs != nil && request.Regs.Ext != nil { var extRegs openrtb_ext.ExtRegs - if err := json.Unmarshal(request.Regs.Ext, &extRegs); err == nil { + if err := json.Unmarshal(request.Regs.Ext, &extRegs); err != nil { + errs = append(errs, err) + } else { if extRegs.GDPR != nil { applies := *extRegs.GDPR != 0 gdpr.Applies = &applies @@ -155,15 +165,37 @@ func (a *ConsumableAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo * } } - // TODO: Replace with gdpr.ReadPolicy when it is available if request.User != nil && request.User.Ext != nil { var extUser openrtb_ext.ExtUser - if err := json.Unmarshal(request.User.Ext, &extUser); err == nil { + if err := json.Unmarshal(request.User.Ext, &extUser); err != nil { + errs = append(errs, err) + } else { gdpr.Consent = extUser.Consent body.GDPR = &gdpr + + if hasEids(extUser.Eids) { + body.User.Eids = extUser.Eids + } + } + } + + if request.Source != nil && request.Source.Ext != nil { + var extSChain openrtb_ext.ExtRequestPrebidSChain + if err := json.Unmarshal(request.Source.Ext, &extSChain); err != nil { + errs = append(errs, err) + } else { + body.SChain = extSChain.SChain } } + body.Coppa = request.Regs != nil && request.Regs.COPPA > 0 + + if request.Site != nil && request.Site.Content != nil { + body.Content = request.Site.Content + } else if request.App != nil && request.App.Content != nil { + body.Content = request.App.Content + } + for i, impression := range request.Imp { _, consumableExt, err := extractExtensions(impression) @@ -204,7 +236,7 @@ func (a *ConsumableAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo * }, } - return requests, nil + return requests, errs } /* @@ -292,10 +324,19 @@ func extractExtensions(impression openrtb2.Imp) (*adapters.ExtImpBidder, *openrt } // Builder builds a new instance of the Consumable adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &ConsumableAdapter{ clock: realInstant{}, endpoint: config.Endpoint, } return bidder, nil } + +func hasEids(eids []openrtb2.EID) bool { + for i := 0; i < len(eids); i++ { + if len(eids[i].UIDs) > 0 && eids[i].UIDs[0].ID != "" { + return true + } + } + return false +} diff --git a/adapters/consumable/consumable/exemplary/simple-banner.json b/adapters/consumable/consumable/exemplary/simple-banner.json index ae57a9f13f6..173104ee9ad 100644 --- a/adapters/consumable/consumable/exemplary/simple-banner.json +++ b/adapters/consumable/consumable/exemplary/simple-banner.json @@ -62,6 +62,11 @@ "unitId": 42 } ], + "schain": { + "complete": 0, + "nodes": null, + "ver": "" + }, "networkId": 11, "siteId": 32, "unitId": 42, diff --git a/adapters/consumable/consumable/supplemental/simple-banner-content-meta.json b/adapters/consumable/consumable/supplemental/simple-banner-content-meta.json new file mode 100644 index 00000000000..56d24166fec --- /dev/null +++ b/adapters/consumable/consumable/supplemental/simple-banner-content-meta.json @@ -0,0 +1,128 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{ "w": 728, "h": 250 }] + }, + "ext": { + "bidder": { + "networkId": 11, + "siteId": 32, + "unitId": 42 + } + } + } + ], + "device": { + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36", + "ip": "123.123.123.123" + }, + "site": { + "domain": "www.some.com", + "page": "http://www.some.com/page-where-ad-will-be-shown", + "content": { + "id": "1009680902", + "title": "What You Know Bout Love", + "context": 3, + "url": "https://www.deezer.com/track/1009680902", + "artist": "Pop Smoke", + "album": "Shoot For The Stars Aim For The Moon" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://e.serverbid.com/api/v2", + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json"], + "User-Agent": [ + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36" + ], + "X-Forwarded-For": ["123.123.123.123"], + "Forwarded": ["for=123.123.123.123"], + "Origin": ["http://www.some.com"], + "Referer": ["http://www.some.com/page-where-ad-will-be-shown"] + }, + "body": { + "content": { + "id": "1009680902", + "title": "What You Know Bout Love", + "context": 3, + "url": "https://www.deezer.com/track/1009680902", + "artist": "Pop Smoke", + "album": "Shoot For The Stars Aim For The Moon" + }, + "placements": [ + { + "adTypes": [2730], + "divName": "test-imp-id", + "networkId": 11, + "siteId": 32, + "unitId": 42 + } + ], + "schain": { + "complete": 0, + "nodes": null, + "ver": "" + }, + "networkId": 11, + "siteId": 32, + "unitId": 42, + "time": 1451651415, + "url": "http://www.some.com/page-where-ad-will-be-shown", + "includePricingData": true, + "user": {}, + "enableBotFiltering": true, + "parallel": true + } + }, + "mockResponse": { + "status": 200, + "body": { + "decisions": { + "test-imp-id": { + "adId": 1234567890, + "pricing": { + "clearPrice": 0.5 + }, + "width": 728, + "height": 250, + "impressionUrl": "http://localhost:8080/shown", + "contents": [ + { + "body": "Remember this: https://www.google.com/search?q=blink+tag ?" + } + ] + } + } + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-request-id", + "impid": "test-imp-id", + "price": 0.5, + "adm": "Remember this: https://www.google.com/search?q=blink+tag ?", + "crid": "1234567890", + "exp": 30, + "w": 728, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/consumable/consumable/supplemental/simple-banner-coppa.json b/adapters/consumable/consumable/supplemental/simple-banner-coppa.json new file mode 100644 index 00000000000..d1551c92e7c --- /dev/null +++ b/adapters/consumable/consumable/supplemental/simple-banner-coppa.json @@ -0,0 +1,128 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 728, "h": 250}] + }, + "ext": { + "bidder": { + "networkId": 11, + "siteId": 32, + "unitId": 42 + } + } + } + ], + "device": { + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36", + "ip": "123.123.123.123" + }, + "site": { + "domain": "www.some.com", + "page": "http://www.some.com/page-where-ad-will-be-shown" + }, + "regs": { + "coppa": 1 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://e.serverbid.com/api/v2", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json" + ], + "User-Agent": [ + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ], + "Forwarded": [ + "for=123.123.123.123" + ], + "Origin": [ + "http://www.some.com" + ], + "Referer": [ + "http://www.some.com/page-where-ad-will-be-shown" + ] + }, + "body": { + "placements": [ + { + "adTypes": [2730], + "divName": "test-imp-id", + "networkId": 11, + "siteId": 32, + "unitId": 42 + } + ], + "schain": { + "complete": 0, + "nodes": null, + "ver": "" + }, + "networkId": 11, + "siteId": 32, + "unitId": 42, + "time": 1451651415, + "url": "http://www.some.com/page-where-ad-will-be-shown", + "includePricingData": true, + "user":{}, + "enableBotFiltering": true, + "parallel": true, + "coppa": true + } + }, + "mockResponse": { + "status": 200, + "body": { + "decisions": { + "test-imp-id": { + "adId": 1234567890, + "pricing": { + "clearPrice": 0.5 + }, + "width": 728, + "height": 250, + "impressionUrl": "http://localhost:8080/shown", + "contents" : [ + { + "body": "Remember this: https://www.google.com/search?q=blink+tag ?" + } + ] + } + } + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-request-id", + "impid": "test-imp-id", + "price": 0.5, + "adm": "Remember this: https://www.google.com/search?q=blink+tag ?", + "crid": "1234567890", + "exp": 30, + "w": 728, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/consumable/consumable/supplemental/simple-banner-eids.json b/adapters/consumable/consumable/supplemental/simple-banner-eids.json new file mode 100644 index 00000000000..106796e21c0 --- /dev/null +++ b/adapters/consumable/consumable/supplemental/simple-banner-eids.json @@ -0,0 +1,150 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 728, "h": 250}] + }, + "ext": { + "bidder": { + "networkId": 11, + "siteId": 32, + "unitId": 42 + } + } + } + ], + "device": { + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36", + "ip": "123.123.123.123" + }, + "site": { + "domain": "www.some.com", + "page": "http://www.some.com/page-where-ad-will-be-shown" + }, + "user": { + "ext": { + "eids": [{ + "source": "adserver.org", + "uids": [{ + "id": "TTD_ID", + "atype": 1, + "ext": { + "rtiPartner": "TDID" + } + }] + }] + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://e.serverbid.com/api/v2", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json" + ], + "User-Agent": [ + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ], + "Forwarded": [ + "for=123.123.123.123" + ], + "Origin": [ + "http://www.some.com" + ], + "Referer": [ + "http://www.some.com/page-where-ad-will-be-shown" + ] + }, + "body": { + "placements": [ + { + "adTypes": [2730], + "divName": "test-imp-id", + "networkId": 11, + "siteId": 32, + "unitId": 42 + } + ], + "schain": { + "complete": 0, + "nodes": null, + "ver": "" + }, + "networkId": 11, + "siteId": 32, + "unitId": 42, + "time": 1451651415, + "url": "http://www.some.com/page-where-ad-will-be-shown", + "includePricingData": true, + "user": { + "eids": [{ + "source": "adserver.org", + "uids": [{ + "id": "TTD_ID", + "atype": 1, + "ext": { + "rtiPartner": "TDID" + } + }] + }] + }, + "enableBotFiltering": true, + "gdpr": {}, + "parallel": true + } + }, + "mockResponse": { + "status": 200, + "body": { + "decisions": { + "test-imp-id": { + "adId": 1234567890, + "pricing": { + "clearPrice": 0.5 + }, + "width": 728, + "height": 250, + "impressionUrl": "http://localhost:8080/shown", + "contents" : [ + { + "body": "Remember this: https://www.google.com/search?q=blink+tag ?" + } + ] + } + } + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-request-id", + "impid": "test-imp-id", + "price": 0.5, + "adm": "Remember this: https://www.google.com/search?q=blink+tag ?", + "crid": "1234567890", + "exp": 30, + "w": 728, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/consumable/consumable/supplemental/simple-banner-gdpr-2.json b/adapters/consumable/consumable/supplemental/simple-banner-gdpr-2.json index 32c3fd6e635..2e2d8588326 100644 --- a/adapters/consumable/consumable/supplemental/simple-banner-gdpr-2.json +++ b/adapters/consumable/consumable/supplemental/simple-banner-gdpr-2.json @@ -67,6 +67,11 @@ "unitId": 42 } ], + "schain": { + "complete": 0, + "nodes": null, + "ver": "" + }, "networkId": 11, "siteId": 32, "unitId": 42, diff --git a/adapters/consumable/consumable/supplemental/simple-banner-gdpr-3.json b/adapters/consumable/consumable/supplemental/simple-banner-gdpr-3.json index afbb7e2b037..ea7be2342b2 100644 --- a/adapters/consumable/consumable/supplemental/simple-banner-gdpr-3.json +++ b/adapters/consumable/consumable/supplemental/simple-banner-gdpr-3.json @@ -67,6 +67,11 @@ "unitId": 42 } ], + "schain": { + "complete": 0, + "nodes": null, + "ver": "" + }, "networkId": 11, "siteId": 32, "unitId": 42, diff --git a/adapters/consumable/consumable/supplemental/simple-banner-gdpr.json b/adapters/consumable/consumable/supplemental/simple-banner-gdpr.json index 1d422e255f2..3f6a953efa8 100644 --- a/adapters/consumable/consumable/supplemental/simple-banner-gdpr.json +++ b/adapters/consumable/consumable/supplemental/simple-banner-gdpr.json @@ -72,6 +72,11 @@ "unitId": 42 } ], + "schain": { + "complete": 0, + "nodes": null, + "ver": "" + }, "networkId": 11, "siteId": 32, "unitId": 42, diff --git a/adapters/consumable/consumable/supplemental/simple-banner-no-impressionUrl.json b/adapters/consumable/consumable/supplemental/simple-banner-no-impressionUrl.json index 66f18d548b5..6e9787ee163 100644 --- a/adapters/consumable/consumable/supplemental/simple-banner-no-impressionUrl.json +++ b/adapters/consumable/consumable/supplemental/simple-banner-no-impressionUrl.json @@ -72,6 +72,11 @@ "unitName": "the-answer" } ], + "schain": { + "complete": 0, + "nodes": null, + "ver": "" + }, "networkId": 11, "siteId": 32, "unitId": 42, diff --git a/adapters/consumable/consumable/supplemental/simple-banner-schain.json b/adapters/consumable/consumable/supplemental/simple-banner-schain.json new file mode 100644 index 00000000000..0b963a507e7 --- /dev/null +++ b/adapters/consumable/consumable/supplemental/simple-banner-schain.json @@ -0,0 +1,145 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 728, "h": 250}] + }, + "ext": { + "bidder": { + "networkId": 11, + "siteId": 32, + "unitId": 42 + } + } + } + ], + "device": { + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36", + "ip": "123.123.123.123" + }, + "site": { + "domain": "www.some.com", + "page": "http://www.some.com/page-where-ad-will-be-shown" + }, + "source": { + "ext": { + "schain": { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "indirectseller.com", + "sid": "00001", + "hp": 1 + } + ] + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://e.serverbid.com/api/v2", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json" + ], + "User-Agent": [ + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ], + "Forwarded": [ + "for=123.123.123.123" + ], + "Origin": [ + "http://www.some.com" + ], + "Referer": [ + "http://www.some.com/page-where-ad-will-be-shown" + ] + }, + "body": { + "placements": [ + { + "adTypes": [2730], + "divName": "test-imp-id", + "networkId": 11, + "siteId": 32, + "unitId": 42 + } + ], + "networkId": 11, + "siteId": 32, + "unitId": 42, + "time": 1451651415, + "url": "http://www.some.com/page-where-ad-will-be-shown", + "includePricingData": true, + "user":{}, + "enableBotFiltering": true, + "parallel": true, + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "indirectseller.com", + "hp": 1, + "sid": "00001" + } + ], + "ver": "1.0" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "decisions": { + "test-imp-id": { + "adId": 1234567890, + "pricing": { + "clearPrice": 0.5 + }, + "width": 728, + "height": 250, + "impressionUrl": "http://localhost:8080/shown", + "contents" : [ + { + "body": "Remember this: https://www.google.com/search?q=blink+tag ?" + } + ] + } + } + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-request-id", + "impid": "test-imp-id", + "price": 0.5, + "adm": "Remember this: https://www.google.com/search?q=blink+tag ?", + "crid": "1234567890", + "exp": 30, + "w": 728, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/consumable/consumable/supplemental/simple-banner-us-privacy.json b/adapters/consumable/consumable/supplemental/simple-banner-us-privacy.json index 6313606e337..c09200c4bd3 100644 --- a/adapters/consumable/consumable/supplemental/simple-banner-us-privacy.json +++ b/adapters/consumable/consumable/supplemental/simple-banner-us-privacy.json @@ -67,6 +67,11 @@ "unitId": 42 } ], + "schain": { + "complete": 0, + "nodes": null, + "ver": "" + }, "networkId": 11, "siteId": 32, "unitId": 42, diff --git a/adapters/consumable/consumable_test.go b/adapters/consumable/consumable_test.go index 56e1d626c7b..5cfe3fe2824 100644 --- a/adapters/consumable/consumable_test.go +++ b/adapters/consumable/consumable_test.go @@ -13,7 +13,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderConsumable, config.Adapter{ - Endpoint: "http://ib.adnxs.com/openrtb2"}) + Endpoint: "http://ib.adnxs.com/openrtb2"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/consumable/params_test.go b/adapters/consumable/params_test.go index 42de5cb9ca8..570b4128339 100644 --- a/adapters/consumable/params_test.go +++ b/adapters/consumable/params_test.go @@ -9,7 +9,7 @@ import ( // This file actually intends to test static/bidder-params/consumable.json // -// These also validate the format of the external API: request.imp[i].ext.consumable +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.consumable // TestValidParams makes sure that the 33across schema accepts all imp.ext fields which we intend to support. func TestValidParams(t *testing.T) { diff --git a/adapters/conversant/conversant.go b/adapters/conversant/conversant.go index 7cf4ab5d04c..3664f62e4ee 100644 --- a/adapters/conversant/conversant.go +++ b/adapters/conversant/conversant.go @@ -5,7 +5,8 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/adcom1" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -56,7 +57,7 @@ func (c ConversantAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *a data, err := json.Marshal(request) if err != nil { return nil, []error{&errortypes.BadInput{ - Message: fmt.Sprintf("Error in packaging request to JSON"), + Message: "Error in packaging request to JSON", }} } headers := http.Header{} @@ -88,9 +89,9 @@ func parseCnvrParams(imp *openrtb2.Imp, cnvrExt openrtb_ext.ExtImpConversant) { imp.Secure = cnvrExt.Secure } - var position *openrtb2.AdPosition + var position *adcom1.PlacementPosition if cnvrExt.Position != nil { - position = openrtb2.AdPosition(*cnvrExt.Position).Ptr() + position = adcom1.PlacementPosition(*cnvrExt.Position).Ptr() } if imp.Banner != nil { tmpBanner := *imp.Banner @@ -103,9 +104,9 @@ func parseCnvrParams(imp *openrtb2.Imp, cnvrExt openrtb_ext.ExtImpConversant) { imp.Video.Pos = position if len(cnvrExt.API) > 0 { - imp.Video.API = make([]openrtb2.APIFramework, 0, len(cnvrExt.API)) + imp.Video.API = make([]adcom1.APIFramework, 0, len(cnvrExt.API)) for _, api := range cnvrExt.API { - imp.Video.API = append(imp.Video.API, openrtb2.APIFramework(api)) + imp.Video.API = append(imp.Video.API, adcom1.APIFramework(api)) } } @@ -114,9 +115,9 @@ func parseCnvrParams(imp *openrtb2.Imp, cnvrExt openrtb_ext.ExtImpConversant) { // but are overridden if the custom params object also contains them. if len(cnvrExt.Protocols) > 0 { - imp.Video.Protocols = make([]openrtb2.Protocol, 0, len(cnvrExt.Protocols)) + imp.Video.Protocols = make([]adcom1.MediaCreativeSubtype, 0, len(cnvrExt.Protocols)) for _, protocol := range cnvrExt.Protocols { - imp.Video.Protocols = append(imp.Video.Protocols, openrtb2.Protocol(protocol)) + imp.Video.Protocols = append(imp.Video.Protocols, adcom1.MediaCreativeSubtype(protocol)) } } @@ -151,7 +152,7 @@ func (c ConversantAdapter) MakeBids(internalRequest *openrtb2.BidRequest, extern if len(resp.SeatBid) == 0 { return nil, []error{&errortypes.BadServerResponse{ - Message: fmt.Sprintf("Empty bid request"), + Message: "Empty bid request", }} } @@ -180,7 +181,7 @@ func getBidType(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { } // Builder builds a new instance of the Conversant adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &ConversantAdapter{ URI: config.Endpoint, } diff --git a/adapters/conversant/conversant_test.go b/adapters/conversant/conversant_test.go index 55b316b473f..ac3f5a2b633 100644 --- a/adapters/conversant/conversant_test.go +++ b/adapters/conversant/conversant_test.go @@ -9,7 +9,7 @@ import ( ) func TestJsonSamples(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderConversant, config.Adapter{}) + bidder, buildErr := Builder(openrtb_ext.BidderConversant, config.Adapter{}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/cpmstar/cpmstar.go b/adapters/cpmstar/cpmstar.go index 86c87c4ccb6..eec267cc00f 100644 --- a/adapters/cpmstar/cpmstar.go +++ b/adapters/cpmstar/cpmstar.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -156,7 +156,7 @@ func (a *Adapter) MakeBids(bidRequest *openrtb2.BidRequest, unused *adapters.Req } // Builder builds a new instance of the Cpmstar adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &Adapter{ endpoint: config.Endpoint, } diff --git a/adapters/cpmstar/cpmstar_test.go b/adapters/cpmstar/cpmstar_test.go index c10dfbe1a59..9218ef4ce2c 100644 --- a/adapters/cpmstar/cpmstar_test.go +++ b/adapters/cpmstar/cpmstar_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderCpmstar, config.Adapter{ - Endpoint: "//host"}) + Endpoint: "//host"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/cpmstar/params_test.go b/adapters/cpmstar/params_test.go index cee471a8322..45b1fbefd96 100644 --- a/adapters/cpmstar/params_test.go +++ b/adapters/cpmstar/params_test.go @@ -8,7 +8,7 @@ import ( ) // This file actually intends to test static/bidder-params/cpmstar.json -// These also validate the format of the external API: request.imp[i].ext.cpmstar +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.cpmstar // TestValidParams makes sure that the Cpmstar schema accepts all imp.ext fields which we intend to support. func TestValidParams(t *testing.T) { diff --git a/adapters/criteo/criteo.go b/adapters/criteo/criteo.go index de5568b9b9b..584ef40c973 100644 --- a/adapters/criteo/criteo.go +++ b/adapters/criteo/criteo.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -102,11 +102,11 @@ func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest } // Builder builds a new instance of the Criteo adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { - return builderWithGuidGenerator(bidderName, config, newRandomSlotIDGenerator()) +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + return builderWithGuidGenerator(bidderName, config, server, newRandomSlotIDGenerator()) } -func builderWithGuidGenerator(bidderName openrtb_ext.BidderName, config config.Adapter, slotIDGenerator slotIDGenerator) (adapters.Bidder, error) { +func builderWithGuidGenerator(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server, slotIDGenerator slotIDGenerator) (adapters.Bidder, error) { return &adapter{ uri: config.Endpoint, slotIDGenerator: slotIDGenerator, diff --git a/adapters/criteo/criteo_test.go b/adapters/criteo/criteo_test.go index 0a700734538..23e9633b017 100644 --- a/adapters/criteo/criteo_test.go +++ b/adapters/criteo/criteo_test.go @@ -16,6 +16,7 @@ func TestJsonSamples(t *testing.T) { config.Adapter{ Endpoint: "https://bidder.criteo.com/cdb?profileId=230", }, + config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}, newFakeGuidGenerator("00000000-0000-0000-00000000"), ) diff --git a/adapters/criteo/models.go b/adapters/criteo/models.go index 0365c0e62a6..893a5a11bc9 100644 --- a/adapters/criteo/models.go +++ b/adapters/criteo/models.go @@ -5,19 +5,19 @@ import ( "fmt" "strings" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" ) type criteoRequest struct { - ID string `json:"id,omitempty"` - Publisher criteoPublisher `json:"publisher,omitempty"` - User criteoUser `json:"user,omitempty"` - GdprConsent criteoGdprConsent `json:"gdprconsent,omitempty"` - Slots []criteoRequestSlot `json:"slots,omitempty"` - Eids []openrtb_ext.ExtUserEid `json:"eids,omitempty"` + ID string `json:"id,omitempty"` + Publisher criteoPublisher `json:"publisher,omitempty"` + User criteoUser `json:"user,omitempty"` + GdprConsent criteoGdprConsent `json:"gdprconsent,omitempty"` + Slots []criteoRequestSlot `json:"slots,omitempty"` + Eids []openrtb2.EID `json:"eids,omitempty"` } func newCriteoRequest(slotIDGenerator slotIDGenerator, request *openrtb2.BidRequest) (criteoRequest, []error) { diff --git a/adapters/criteo/models_test.go b/adapters/criteo/models_test.go index e85fa4c1a5c..60b8c67eb11 100644 --- a/adapters/criteo/models_test.go +++ b/adapters/criteo/models_test.go @@ -5,7 +5,7 @@ import ( "reflect" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/openrtb_ext" ) diff --git a/adapters/criteo/params_test.go b/adapters/criteo/params_test.go index 9c836769aca..c373de0a83f 100644 --- a/adapters/criteo/params_test.go +++ b/adapters/criteo/params_test.go @@ -9,7 +9,7 @@ import ( // This file actually intends to test static/bidder-params/criteo.json // -// These also validate the format of the external API: request.imp[i].ext.criteo +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.criteo // TestValidParams makes sure that the criteo schema accepts all imp.ext fields which we intend to support. func TestValidParams(t *testing.T) { diff --git a/adapters/datablocks/datablocks.go b/adapters/datablocks/datablocks.go index e9d727d70d4..564af2166e7 100644 --- a/adapters/datablocks/datablocks.go +++ b/adapters/datablocks/datablocks.go @@ -7,7 +7,7 @@ import ( "strconv" "text/template" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -179,7 +179,7 @@ func getMediaType(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { } // Builder builds a new instance of the Datablocks adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (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) diff --git a/adapters/datablocks/datablocks_test.go b/adapters/datablocks/datablocks_test.go index 553c8edf8da..9a5fb9a23f4 100644 --- a/adapters/datablocks/datablocks_test.go +++ b/adapters/datablocks/datablocks_test.go @@ -11,7 +11,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderDatablocks, config.Adapter{ - Endpoint: "http://{{.Host}}/openrtb2?sid={{.SourceId}}"}) + Endpoint: "http://{{.Host}}/openrtb2?sid={{.SourceId}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -22,7 +22,7 @@ func TestJsonSamples(t *testing.T) { func TestEndpointTemplateMalformed(t *testing.T) { _, buildErr := Builder(openrtb_ext.BidderDatablocks, config.Adapter{ - Endpoint: "{{Malformed}}"}) + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Error(t, buildErr) } diff --git a/adapters/decenterads/decenterads.go b/adapters/decenterads/decenterads.go index c94c461e367..7b3b6521c37 100644 --- a/adapters/decenterads/decenterads.go +++ b/adapters/decenterads/decenterads.go @@ -7,7 +7,7 @@ import ( "net/http" "strconv" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -116,7 +116,7 @@ func getMediaTypeForImp(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { } // Builder builds a new instance of the DecenterAds adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &adapter{ endpoint: config.Endpoint, } diff --git a/adapters/decenterads/decenterads_test.go b/adapters/decenterads/decenterads_test.go index ca86e89187c..3c1ed971833 100644 --- a/adapters/decenterads/decenterads_test.go +++ b/adapters/decenterads/decenterads_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderDecenterAds, config.Adapter{ - Endpoint: "http://example.com/?c=o&m=ortb"}) + Endpoint: "http://example.com/?c=o&m=ortb"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) } diff --git a/adapters/deepintent/deepintent.go b/adapters/deepintent/deepintent.go index 0853bb8b405..cd04ec66057 100644 --- a/adapters/deepintent/deepintent.go +++ b/adapters/deepintent/deepintent.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -21,14 +21,14 @@ type DeepintentAdapter struct { } // 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) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &DeepintentAdapter{ URI: config.Endpoint, } return bidder, nil } -//MakeRequests which creates request object for Deepintent DSP +// MakeRequests which creates request object for Deepintent DSP func (d *DeepintentAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error var deepintentExt openrtb_ext.ExtImpDeepintent diff --git a/adapters/deepintent/deepintent_test.go b/adapters/deepintent/deepintent_test.go index cdf158e6d28..97f685d2f7e 100644 --- a/adapters/deepintent/deepintent_test.go +++ b/adapters/deepintent/deepintent_test.go @@ -11,7 +11,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderDeepintent, config.Adapter{ - Endpoint: "https://prebid.deepintent.com/prebid"}) + Endpoint: "https://prebid.deepintent.com/prebid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/dianomi/dianomi.go b/adapters/dianomi/dianomi.go new file mode 100644 index 00000000000..bc4881639ab --- /dev/null +++ b/adapters/dianomi/dianomi.go @@ -0,0 +1,159 @@ +package dianomi + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/prebid/openrtb/v17/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 dianomiRequestExt struct { + openrtb_ext.ExtRequest + PriceType string `json:"pt"` +} + +// Builder builds a new instance of the Dianomi adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var errors []error + var validImps = make([]openrtb2.Imp, 0, len(request.Imp)) + priceType := "" + + for _, imp := range request.Imp { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + errors = append(errors, &errortypes.BadInput{ + Message: err.Error(), + }) + continue + } + + var dianomiImpExt openrtb_ext.ExtImpDianomi + if err := json.Unmarshal(bidderExt.Bidder, &dianomiImpExt); err != nil { + errors = append(errors, &errortypes.BadInput{ + Message: err.Error(), + }) + continue + } + + imp.TagID = dianomiImpExt.SmartadId.String() + validImps = append(validImps, imp) + + // If imps specify priceType they should all be the same. If they differ, only the first one will be used + if dianomiImpExt.PriceType != "" && priceType == "" { + priceType = dianomiImpExt.PriceType + } + } + + if priceType != "" { + requestExt := dianomiRequestExt{} + var err error + + if len(request.Ext) > 0 { + if err = json.Unmarshal(request.Ext, &requestExt); err != nil { + errors = append(errors, err) + } + } + + if err == nil { + requestExt.PriceType = priceType + + if request.Ext, err = json.Marshal(&requestExt); err != nil { + errors = append(errors, err) + } + } + } + + request.Imp = validImps + + requestJSON, err := json.Marshal(request) + if err != nil { + errors = append(errors, err) + return nil, errors + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + requestData := &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + Body: requestJSON, + Headers: headers, + } + + return []*adapters.RequestData{requestData}, errors +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if responseData.StatusCode == http.StatusNoContent { + return nil, nil + } + + if responseData.StatusCode == http.StatusBadRequest { + err := &errortypes.BadInput{ + Message: "Unexpected status code: 400. Bad request from publisher.", + } + return nil, []error{err} + } + + if responseData.StatusCode != http.StatusOK { + err := &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d.", responseData.StatusCode), + } + return nil, []error{err} + } + + var response openrtb2.BidResponse + if err := json.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + bidResponse.Currency = response.Cur + var errors []error + for _, seatBid := range response.SeatBid { + for i, bid := range seatBid.Bid { + bidType, err := getMediaTypeForBid(bid) + if err != nil { + errors = append(errors, err) + continue + } + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &seatBid.Bid[i], + BidType: bidType, + }) + } + } + + return bidResponse, errors +} + +func 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.BadServerResponse{ + Message: fmt.Sprintf("Failed to parse impression \"%s\" mediatype", bid.ImpID), + } +} diff --git a/adapters/dianomi/dianomi_test.go b/adapters/dianomi/dianomi_test.go new file mode 100644 index 00000000000..95c94a02f14 --- /dev/null +++ b/adapters/dianomi/dianomi_test.go @@ -0,0 +1,20 @@ +package dianomi + +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.BidderDianomi, config.Adapter{ + Endpoint: "https://prebid-server-aws.dianomi.com/openrtb2/auction"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "dianomitest", bidder) +} diff --git a/adapters/dianomi/dianomitest/exemplary/multi-format.json b/adapters/dianomi/dianomitest/exemplary/multi-format.json new file mode 100644 index 00000000000..69497d897e0 --- /dev/null +++ b/adapters/dianomi/dianomitest/exemplary/multi-format.json @@ -0,0 +1,170 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id-1", + "ext": { + "bidder": { + "smartadId": "9526" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "placement": 1 + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + } + }, { + "id": "test-imp-id-2", + "ext": { + "bidder": { + "smartadId": "9526" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "placement": 1 + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://prebid-server-aws.dianomi.com/openrtb2/auction", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "body": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id-1", + "ext": { + "bidder": { + "smartadId": "9526" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "placement": 1 + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "tagid": "9526" + }, { + "id": "test-imp-id-2", + "ext": { + "bidder": { + "smartadId": "9526" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "placement": 1 + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "tagid": "9526" + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "bid": [{ + "id": "test-bid-id-1", + "impid": "test-imp-id-1", + "price": 10, + "adm": "{video xml}", + "adomain": [], + "crid": "test-creative-id-1", + "ext": { + "prebid": { + "type": "video" + } + } + }] + }, { + "bid": [{ + "id": "test-bid-id-2", + "impid": "test-imp-id-2", + "price": 2, + "adm": "{banner html}", + "adomain": [ "ad-domain" ], + "crid": "test-creative-id-2", + "ext": { + "prebid": { + "type": "banner" + } + } + }] + }], + "cur": "EUR" + } + } + }], + "expectedBidResponses": [{ + "currency": "EUR", + "bids": [{ + "bid": { + "id": "test-bid-id-1", + "impid": "test-imp-id-1", + "price": 10, + "adm": "{video xml}", + "crid": "test-creative-id-1", + "ext": { + "prebid": { + "type": "video" + } + } + }, + "type": "video" + }, { + "bid": { + "id": "test-bid-id-2", + "impid": "test-imp-id-2", + "price": 2, + "adm": "{banner html}", + "adomain": [ "ad-domain" ], + "crid": "test-creative-id-2", + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + }] + }] +} diff --git a/adapters/dianomi/dianomitest/exemplary/multi-native.json b/adapters/dianomi/dianomitest/exemplary/multi-native.json new file mode 100644 index 00000000000..ccaeb33a152 --- /dev/null +++ b/adapters/dianomi/dianomitest/exemplary/multi-native.json @@ -0,0 +1,136 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id-1", + "ext": { + "bidder": { + "smartadId": "9607" + } + }, + "native": { + "request": "{json string 1}", + "ver": "1.2" + } + }, { + "id": "test-imp-id-2", + "ext": { + "bidder": { + "smartadId": "9607" + } + }, + "native": { + "request": "{json string 2}", + "ver": "1.2" + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://prebid-server-aws.dianomi.com/openrtb2/auction", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "body": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id-1", + "ext": { + "bidder": { + "smartadId": "9607" + } + }, + "native": { + "request": "{json string 1}", + "ver": "1.2" + }, + "tagid": "9607" + }, { + "id": "test-imp-id-2", + "ext": { + "bidder": { + "smartadId": "9607" + } + }, + "native": { + "request": "{json string 2}", + "ver": "1.2" + }, + "tagid": "9607" + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "bid": [{ + "id": "test-bid-id-1", + "impid": "test-imp-id-1", + "price": 10, + "adm": "{json response string 1}", + "adomain": [], + "crid": "test-creative-id-1", + "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", + "ext": { + "prebid": { + "type": "native" + } + } + }] + }], + "cur": "EUR" + } + } + }], + "expectedBidResponses": [{ + "currency": "EUR", + "bids": [{ + "bid": { + "id": "test-bid-id-1", + "impid": "test-imp-id-1", + "price": 10, + "adm": "{json response string 1}", + "crid": "test-creative-id-1", + "ext": { + "prebid": { + "type": "native" + } + } + }, + "type": "native" + }, { + "bid": { + "id": "test-bid-id-2", + "impid": "test-imp-id-2", + "price": 2, + "adm": "{json response string 2}", + "adomain": [ "ad-domain" ], + "crid": "test-creative-id-2", + "ext": { + "prebid": { + "type": "native" + } + } + }, + "type": "native" + }] + }] +} diff --git a/adapters/dianomi/dianomitest/exemplary/single-banner-pricetype-gross-extend-ext.json b/adapters/dianomi/dianomitest/exemplary/single-banner-pricetype-gross-extend-ext.json new file mode 100644 index 00000000000..836da52eef0 --- /dev/null +++ b/adapters/dianomi/dianomitest/exemplary/single-banner-pricetype-gross-extend-ext.json @@ -0,0 +1,130 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "ext":{ + "prebid":{ + "aliases":{ + "dianomialias": "dia" + } + } + }, + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "smartadId": "9526", + "priceType": "gross" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + } + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://prebid-server-aws.dianomi.com/openrtb2/auction", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "body": { + "id": "test-request-id", + "ext": { + "prebid":{ + "aliases":{ + "dianomialias": "dia" + } + }, + "pt": "gross" + }, + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "smartadId": "9526", + "priceType": "gross" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "tagid": "9526" + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "bid": [{ + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{banner html}", + "adomain": [ "test.com" ], + "crid": "test-creative-id", + "ext": { + "prebid": { + "type": "banner" + } + } + }] + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{banner html}", + "crid": "test-creative-id", + "adomain": [ "test.com" ], + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + }] +} diff --git a/adapters/dianomi/dianomitest/exemplary/single-banner-pricetype-gross.json b/adapters/dianomi/dianomitest/exemplary/single-banner-pricetype-gross.json new file mode 100644 index 00000000000..0b3683f08eb --- /dev/null +++ b/adapters/dianomi/dianomitest/exemplary/single-banner-pricetype-gross.json @@ -0,0 +1,120 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "smartadId": "9526", + "priceType": "gross" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + } + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://prebid-server-aws.dianomi.com/openrtb2/auction", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "body": { + "id": "test-request-id", + "ext": { + "prebid": { + }, + "pt": "gross" + }, + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "smartadId": "9526", + "priceType": "gross" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "tagid": "9526" + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "bid": [{ + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{banner html}", + "adomain": [ "test.com" ], + "crid": "test-creative-id", + "ext": { + "prebid": { + "type": "banner" + } + } + }] + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{banner html}", + "crid": "test-creative-id", + "adomain": [ "test.com" ], + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + }] +} diff --git a/adapters/dianomi/dianomitest/exemplary/single-banner-pricetype-net.json b/adapters/dianomi/dianomitest/exemplary/single-banner-pricetype-net.json new file mode 100644 index 00000000000..a45b0118565 --- /dev/null +++ b/adapters/dianomi/dianomitest/exemplary/single-banner-pricetype-net.json @@ -0,0 +1,120 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "smartadId": "9526", + "priceType": "net" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + } + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://prebid-server-aws.dianomi.com/openrtb2/auction", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "body": { + "id": "test-request-id", + "ext": { + "prebid": { + }, + "pt": "net" + }, + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "smartadId": "9526", + "priceType": "net" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "tagid": "9526" + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "bid": [{ + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{banner html}", + "adomain": [ "test.com" ], + "crid": "test-creative-id", + "ext": { + "prebid": { + "type": "banner" + } + } + }] + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{banner html}", + "crid": "test-creative-id", + "adomain": [ "test.com" ], + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + }] +} diff --git a/adapters/dianomi/dianomitest/exemplary/single-banner.json b/adapters/dianomi/dianomitest/exemplary/single-banner.json new file mode 100644 index 00000000000..9ddcab7cd7b --- /dev/null +++ b/adapters/dianomi/dianomitest/exemplary/single-banner.json @@ -0,0 +1,113 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "smartadId": "9526" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + } + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://prebid-server-aws.dianomi.com/openrtb2/auction", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "body": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "smartadId": "9526" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "tagid": "9526" + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "bid": [{ + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{banner html}", + "adomain": [ "test.com" ], + "crid": "test-creative-id", + "ext": { + "prebid": { + "type": "banner" + } + } + }] + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{banner html}", + "crid": "test-creative-id", + "adomain": [ "test.com" ], + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + }] +} diff --git a/adapters/dianomi/dianomitest/exemplary/single-native.json b/adapters/dianomi/dianomitest/exemplary/single-native.json new file mode 100644 index 00000000000..d532f7d9168 --- /dev/null +++ b/adapters/dianomi/dianomitest/exemplary/single-native.json @@ -0,0 +1,108 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "smartadId": "9607" + } + }, + "native": { + "request": "{json string}", + "ver": "1.2" + } + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://prebid-server-aws.dianomi.com/openrtb2/auction", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "body": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "smartadId": "9607" + } + }, + "native": { + "request": "{json string}", + "ver": "1.2" + }, + "tagid": "9607" + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "bid": [{ + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{json response string}", + "adomain": [], + "crid": "test-creative-id", + "ext": { + "prebid": { + "type": "native" + } + } + }] + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{json response string}", + "crid": "test-creative-id", + "ext": { + "prebid": { + "type": "native" + } + } + }, + "type": "native" + } + ] + }] +} diff --git a/adapters/dianomi/dianomitest/exemplary/single-video.json b/adapters/dianomi/dianomitest/exemplary/single-video.json new file mode 100644 index 00000000000..4c13602c887 --- /dev/null +++ b/adapters/dianomi/dianomitest/exemplary/single-video.json @@ -0,0 +1,111 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "smartadId": "9525" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "placement": 1 + } + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://prebid-server-aws.dianomi.com/openrtb2/auction", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "body": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "smartadId": "9525" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "placement": 1 + }, + "tagid": "9525" + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "bid": [{ + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{vast xml}", + "crid": "test-creative-id", + "ext": { + "prebid": { + "type": "video" + } + } + }] + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{vast xml}", + "crid": "test-creative-id", + "ext": { + "prebid": { + "type": "video" + } + } + }, + "type": "video" + } + ] + }] +} diff --git a/adapters/dianomi/dianomitest/exemplary/two-banners-different-pricetypes-extend-ext.json b/adapters/dianomi/dianomitest/exemplary/two-banners-different-pricetypes-extend-ext.json new file mode 100644 index 00000000000..f9f0a95bafc --- /dev/null +++ b/adapters/dianomi/dianomitest/exemplary/two-banners-different-pricetypes-extend-ext.json @@ -0,0 +1,159 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "ext":{ + "prebid":{ + "aliases":{ + "dianomialias": "dia" + } + } + }, + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "smartadId": "9526", + "priceType": "gross" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + } + },{ + "id": "test-imp-id2", + "ext": { + "bidder": { + "smartadId": "9526", + "priceType": "net" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + } + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://prebid-server-aws.dianomi.com/openrtb2/auction", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "body": { + "id": "test-request-id", + "ext": { + "prebid":{ + "aliases":{ + "dianomialias": "dia" + } + }, + "pt": "gross" + }, + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "smartadId": "9526", + "priceType": "gross" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "tagid": "9526" + },{ + "id": "test-imp-id2", + "ext": { + "bidder": { + "smartadId": "9526", + "priceType": "net" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "tagid": "9526" + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "bid": [{ + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{banner html}", + "adomain": [ "test.com" ], + "crid": "test-creative-id", + "ext": { + "prebid": { + "type": "banner" + } + } + }] + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{banner html}", + "crid": "test-creative-id", + "adomain": [ "test.com" ], + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + }] +} diff --git a/adapters/dianomi/dianomitest/exemplary/two-banners-different-pricetypes.json b/adapters/dianomi/dianomitest/exemplary/two-banners-different-pricetypes.json new file mode 100644 index 00000000000..0d4f77fc5e3 --- /dev/null +++ b/adapters/dianomi/dianomitest/exemplary/two-banners-different-pricetypes.json @@ -0,0 +1,149 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "smartadId": "9526", + "priceType": "gross" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + } + },{ + "id": "test-imp-id2", + "ext": { + "bidder": { + "smartadId": "9526", + "priceType": "net" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + } + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://prebid-server-aws.dianomi.com/openrtb2/auction", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "body": { + "id": "test-request-id", + "ext": { + "prebid": { + }, + "pt": "gross" + }, + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "smartadId": "9526", + "priceType": "gross" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "tagid": "9526" + },{ + "id": "test-imp-id2", + "ext": { + "bidder": { + "smartadId": "9526", + "priceType": "net" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "tagid": "9526" + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "bid": [{ + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{banner html}", + "adomain": [ "test.com" ], + "crid": "test-creative-id", + "ext": { + "prebid": { + "type": "banner" + } + } + }] + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{banner html}", + "crid": "test-creative-id", + "adomain": [ "test.com" ], + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + }] +} diff --git a/adapters/dianomi/dianomitest/supplemental/bad-request.json b/adapters/dianomi/dianomitest/supplemental/bad-request.json new file mode 100644 index 00000000000..3c8628c8608 --- /dev/null +++ b/adapters/dianomi/dianomitest/supplemental/bad-request.json @@ -0,0 +1,48 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "native": { + "request": "" + }, + "ext": { + "bidder": { + "smartadId": 9607 + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://prebid-server-aws.dianomi.com/openrtb2/auction", + "body": { + "id": "test-request-id", + "imp": [{ + "ext": { + "bidder": { + "smartadId": 9607 + } + }, + "id": "test-imp-id", + "native": { + "request": "" + }, + "tagid": "9607" + }] + } + }, + "mockResponse": { + "status": 400 + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Bad request from publisher.", + "comparison": "literal" + } + ] +} diff --git a/adapters/dianomi/dianomitest/supplemental/empty-response.json b/adapters/dianomi/dianomitest/supplemental/empty-response.json new file mode 100644 index 00000000000..ea6f657b59d --- /dev/null +++ b/adapters/dianomi/dianomitest/supplemental/empty-response.json @@ -0,0 +1,42 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "native": { + "request": "" + }, + "ext": { + "bidder": { + "smartadId": 9607 + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://prebid-server-aws.dianomi.com/openrtb2/auction", + "body": { + "id": "test-request-id", + "imp": [{ + "ext": { + "bidder": { + "smartadId": 9607 + } + }, + "id": "test-imp-id", + "native": { + "request": "" + }, + "tagid": "9607" + }] + } + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/dianomi/dianomitest/supplemental/invalid-imp-mediatype.json b/adapters/dianomi/dianomitest/supplemental/invalid-imp-mediatype.json new file mode 100644 index 00000000000..f12b62b03dc --- /dev/null +++ b/adapters/dianomi/dianomitest/supplemental/invalid-imp-mediatype.json @@ -0,0 +1,93 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "audio": {}, + "ext": { + "bidder": { + "smartadId": 12345 + } + } + },{ + "id": "test-imp-id-2", + "audio": {}, + "ext": { + "bidder": { + "smartadId": 12345 + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://prebid-server-aws.dianomi.com/openrtb2/auction", + "body": { + "id": "test-request-id", + "imp": [{ + "ext": { + "bidder": { + "smartadId": 12345 + } + }, + "id": "test-imp-id", + "audio": { + "mimes": null + }, + "tagid": "12345" + }, { + "ext": { + "bidder": { + "smartadId": 12345 + } + }, + "id": "test-imp-id-2", + "audio": { + "mimes": null + }, + "tagid": "12345" + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "bid": [{ + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{vast xml}", + "crid": "test-creative-id" + }, { + "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" + } + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Failed to parse impression \"test-imp-id\" mediatype", + "comparison": "literal" + }, + { + "value": "invalid BidType: not-a-banner", + "comparison": "literal" + } + ] +} diff --git a/adapters/dianomi/dianomitest/supplemental/nobid-response.json b/adapters/dianomi/dianomitest/supplemental/nobid-response.json new file mode 100644 index 00000000000..7654e803b26 --- /dev/null +++ b/adapters/dianomi/dianomitest/supplemental/nobid-response.json @@ -0,0 +1,49 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "native": { + "request": "" + }, + "ext": { + "bidder": { + "smartadId": 9607 + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://prebid-server-aws.dianomi.com/openrtb2/auction", + "body": { + "id": "test-request-id", + "imp": [{ + "ext": { + "bidder": { + "smartadId": 9607 + } + }, + "id": "test-imp-id", + "native": { + "request": "" + }, + "tagid": "9607" + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": null, + "bidid": null, + "cur": null + } + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [] +} diff --git a/adapters/dianomi/dianomitest/supplemental/server-error.json b/adapters/dianomi/dianomitest/supplemental/server-error.json new file mode 100644 index 00000000000..3aa5be915ae --- /dev/null +++ b/adapters/dianomi/dianomitest/supplemental/server-error.json @@ -0,0 +1,49 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "native": { + "request": "" + }, + "ext": { + "bidder": { + "smartadId": 9607 + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://prebid-server-aws.dianomi.com/openrtb2/auction", + "body": { + "id": "test-request-id", + "imp": [{ + "ext": { + "bidder": { + "smartadId": 9607 + } + }, + "id": "test-imp-id", + "native": { + "request": "" + }, + "tagid": "9607" + }] + } + }, + "mockResponse": { + "status": 500, + "body": "Server error" + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500.", + "comparison": "literal" + } + ] +} diff --git a/adapters/dianomi/dianomitest/supplemental/unparsable-response.json b/adapters/dianomi/dianomitest/supplemental/unparsable-response.json new file mode 100644 index 00000000000..7ab8ef2fdeb --- /dev/null +++ b/adapters/dianomi/dianomitest/supplemental/unparsable-response.json @@ -0,0 +1,49 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "native": { + "request": "" + }, + "ext": { + "bidder": { + "smartadId": 9607 + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://prebid-server-aws.dianomi.com/openrtb2/auction", + "body": { + "id": "test-request-id", + "imp": [{ + "ext": { + "bidder": { + "smartadId": 9607 + } + }, + "id": "test-imp-id", + "native": { + "request": "" + }, + "tagid": "9607" + }] + } + }, + "mockResponse": { + "status": 200, + "body": "" + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "comparison": "literal" + } + ] +} diff --git a/adapters/dianomi/params_test.go b/adapters/dianomi/params_test.go new file mode 100644 index 00000000000..462d6d75edd --- /dev/null +++ b/adapters/dianomi/params_test.go @@ -0,0 +1,60 @@ +package dianomi + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +// This file actually intends to test static/bidder-params/dianomi.json +// +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.dianomi + +// TestValidParams makes sure that the dianomi 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.BidderDianomi, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected dianomi params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the dianomi 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.BidderDianomi, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"smartadId":123}`, + `{"smartadId":"123"}`, + `{"smartadId":"123","priceType":"gross"}`, + `{"smartadId":"123","priceType":"net"}`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{"notsmartadId":"123"}`, + `{"smartadID":"smartadId"}`, + `{"SmartadId":"123","priceType":"GROSS"}`, +} diff --git a/adapters/dmx/dmx.go b/adapters/dmx/dmx.go index adcec4a33c5..968ed66ea98 100644 --- a/adapters/dmx/dmx.go +++ b/adapters/dmx/dmx.go @@ -8,7 +8,8 @@ import ( "net/url" "strings" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/adcom1" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -20,7 +21,7 @@ type DmxAdapter struct { } // Builder builds a new instance of the DistrictM DMX adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &DmxAdapter{ endpoint: config.Endpoint, } @@ -48,7 +49,7 @@ type dmxParams struct { Bidfloor float64 `json:"bidfloor,omitempty"` } -var protocols = []openrtb2.Protocol{2, 3, 5, 6, 7, 8} +var protocols = []adcom1.MediaCreativeSubtype{2, 3, 5, 6, 7, 8} func UserSellerOrPubId(str1, str2 string) string { if str1 != "" { @@ -351,7 +352,7 @@ func getIdfa(request *openrtb2.BidRequest) (string, bool) { } return "", false } -func checkProtocols(imp *openrtb2.Video) []openrtb2.Protocol { +func checkProtocols(imp *openrtb2.Video) []adcom1.MediaCreativeSubtype { if len(imp.Protocols) > 0 { return imp.Protocols } diff --git a/adapters/dmx/dmx_test.go b/adapters/dmx/dmx_test.go index aa4a6f79053..e5f97c841f7 100644 --- a/adapters/dmx/dmx_test.go +++ b/adapters/dmx/dmx_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" @@ -54,7 +54,7 @@ func TestFetchParams(t *testing.T) { } func TestJsonSamples(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderDmx, config.Adapter{}) + bidder, buildErr := Builder(openrtb_ext.BidderDmx, config.Adapter{}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -69,7 +69,7 @@ func TestMakeRequestsOtherPlacement(t *testing.T) { var width, height int64 = int64(w), int64(h) bidder, buildErr := Builder(openrtb_ext.BidderDmx, config.Adapter{ - Endpoint: "https://dmx.districtm.io/b/v2"}) + Endpoint: "https://dmx.districtm.io/b/v2"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -114,7 +114,7 @@ func TestMakeRequestsInvalid(t *testing.T) { var width, height int64 = int64(w), int64(h) bidder, buildErr := Builder(openrtb_ext.BidderDmx, config.Adapter{ - Endpoint: "https://dmx.districtm.io/b/v2"}) + Endpoint: "https://dmx.districtm.io/b/v2"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -158,7 +158,7 @@ func TestMakeRequestNoSite(t *testing.T) { var width, height int64 = int64(w), int64(h) bidder, buildErr := Builder(openrtb_ext.BidderDmx, config.Adapter{ - Endpoint: "https://dmx.districtm.io/b/v2"}) + Endpoint: "https://dmx.districtm.io/b/v2"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -206,7 +206,7 @@ func TestMakeRequestsApp(t *testing.T) { var width, height int64 = int64(w), int64(h) bidder, buildErr := Builder(openrtb_ext.BidderDmx, config.Adapter{ - Endpoint: "https://dmx.districtm.io/b/v2"}) + Endpoint: "https://dmx.districtm.io/b/v2"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -256,7 +256,7 @@ func TestMakeRequestsNoUser(t *testing.T) { var width, height int64 = int64(w), int64(h) bidder, buildErr := Builder(openrtb_ext.BidderDmx, config.Adapter{ - Endpoint: "https://dmx.districtm.io/b/v2"}) + Endpoint: "https://dmx.districtm.io/b/v2"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -297,7 +297,7 @@ func TestMakeRequests(t *testing.T) { var width, height int64 = int64(w), int64(h) bidder, buildErr := Builder(openrtb_ext.BidderDmx, config.Adapter{ - Endpoint: "https://dmx.districtm.io/b/v2"}) + Endpoint: "https://dmx.districtm.io/b/v2"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -367,7 +367,7 @@ func TestMakeBidVideo(t *testing.T) { var width, height int64 = int64(w), int64(h) bidder, buildErr := Builder(openrtb_ext.BidderDmx, config.Adapter{ - Endpoint: "https://dmx.districtm.io/b/v2"}) + Endpoint: "https://dmx.districtm.io/b/v2"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -414,7 +414,7 @@ func TestMakeBidsNoContent(t *testing.T) { var width, height int64 = int64(w), int64(h) bidder, buildErr := Builder(openrtb_ext.BidderDmx, config.Adapter{ - Endpoint: "https://dmx.districtm.io/b/v2"}) + Endpoint: "https://dmx.districtm.io/b/v2"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -552,7 +552,7 @@ func TestUserExtEmptyObject(t *testing.T) { var width, height int64 = int64(w), int64(h) bidder, buildErr := Builder(openrtb_ext.BidderDmx, config.Adapter{ - Endpoint: "https://dmx.districtm.io/b/v2"}) + Endpoint: "https://dmx.districtm.io/b/v2"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -591,7 +591,7 @@ func TestUserEidsOnly(t *testing.T) { var width, height int64 = int64(w), int64(h) bidder, buildErr := Builder(openrtb_ext.BidderDmx, config.Adapter{ - Endpoint: "https://dmx.districtm.io/b/v2"}) + Endpoint: "https://dmx.districtm.io/b/v2"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -645,7 +645,7 @@ func TestUsersEids(t *testing.T) { var width, height int64 = int64(w), int64(h) bidder, buildErr := Builder(openrtb_ext.BidderDmx, config.Adapter{ - Endpoint: "https://dmx.districtm.io/b/v2"}) + Endpoint: "https://dmx.districtm.io/b/v2"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/e_volution/evolution.go b/adapters/e_volution/evolution.go index 26df301cdb7..ace79ed8327 100644 --- a/adapters/e_volution/evolution.go +++ b/adapters/e_volution/evolution.go @@ -3,12 +3,14 @@ package evolution import ( "encoding/json" "fmt" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "net/http" + + "github.com/prebid/openrtb/v17/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 { @@ -19,7 +21,7 @@ type bidExt struct { MediaType openrtb_ext.BidType `json:"mediaType"` } -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &adapter{ URI: config.Endpoint, } diff --git a/adapters/e_volution/evolution_test.go b/adapters/e_volution/evolution_test.go index 1d2ee7ef9a6..9752a4c7587 100644 --- a/adapters/e_volution/evolution_test.go +++ b/adapters/e_volution/evolution_test.go @@ -11,7 +11,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderEVolution, config.Adapter{ - Endpoint: "http://service.e-volution.ai/pbserver"}) + Endpoint: "http://service.e-volution.ai/pbserver"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.NoError(t, buildErr) adapterstest.RunJSONBidderTest(t, "evolutiontest", bidder) diff --git a/adapters/emx_digital/emx_digital.go b/adapters/emx_digital/emx_digital.go index 75d74cf6cd3..c3d407769cf 100644 --- a/adapters/emx_digital/emx_digital.go +++ b/adapters/emx_digital/emx_digital.go @@ -9,7 +9,8 @@ import ( "strings" "time" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/adcom1" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -162,11 +163,11 @@ func buildImpVideo(imp *openrtb2.Imp) error { } // not supporting VAST protocol 7 (VAST 4.0); -func cleanProtocol(protocols []openrtb2.Protocol) []openrtb2.Protocol { - newitems := make([]openrtb2.Protocol, 0, len(protocols)) +func cleanProtocol(protocols []adcom1.MediaCreativeSubtype) []adcom1.MediaCreativeSubtype { + newitems := make([]adcom1.MediaCreativeSubtype, 0, len(protocols)) for _, i := range protocols { - if i != openrtb2.ProtocolVAST40 { + if i != adcom1.CreativeVAST40 { newitems = append(newitems, i) } } @@ -309,7 +310,7 @@ func ContainsAny(raw string, keys []string) bool { } // Builder builds a new instance of the EmxDigital adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &EmxDigitalAdapter{ endpoint: config.Endpoint, testing: false, diff --git a/adapters/emx_digital/emx_digital_test.go b/adapters/emx_digital/emx_digital_test.go index bb7567e8b17..6538ff70efa 100644 --- a/adapters/emx_digital/emx_digital_test.go +++ b/adapters/emx_digital/emx_digital_test.go @@ -11,7 +11,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderEmxDigital, config.Adapter{ - Endpoint: "https://hb.emxdgt.com"}) + Endpoint: "https://hb.emxdgt.com"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/engagebdr/engagebdr.go b/adapters/engagebdr/engagebdr.go index 645773fe735..b2734326558 100644 --- a/adapters/engagebdr/engagebdr.go +++ b/adapters/engagebdr/engagebdr.go @@ -4,7 +4,7 @@ import ( "encoding/json" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" @@ -143,7 +143,7 @@ func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { } // Builder builds a new instance of the EngageBDR adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &EngageBDRAdapter{ URI: config.Endpoint, } diff --git a/adapters/engagebdr/engagebdr_test.go b/adapters/engagebdr/engagebdr_test.go index 97e6a8e974f..0877750cb19 100644 --- a/adapters/engagebdr/engagebdr_test.go +++ b/adapters/engagebdr/engagebdr_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderEngageBDR, config.Adapter{ - Endpoint: "http://dsp.bnmla.com/hb"}) + Endpoint: "http://dsp.bnmla.com/hb"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/eplanning/eplanning.go b/adapters/eplanning/eplanning.go index 1e593fb7d93..061627a8e9d 100644 --- a/adapters/eplanning/eplanning.go +++ b/adapters/eplanning/eplanning.go @@ -11,7 +11,8 @@ import ( "fmt" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/adcom1" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -192,7 +193,7 @@ func (adapter *EPlanningAdapter) MakeRequests(request *openrtb2.BidRequest, reqI } func isMobileDevice(request *openrtb2.BidRequest) bool { - return request.Device != nil && (request.Device.DeviceType == openrtb2.DeviceTypeMobileTablet || request.Device.DeviceType == openrtb2.DeviceTypePhone || request.Device.DeviceType == openrtb2.DeviceTypeTablet) + return request.Device != nil && (request.Device.DeviceType == adcom1.DeviceMobile || request.Device.DeviceType == adcom1.DevicePhone || request.Device.DeviceType == adcom1.DeviceTablet) } func cleanName(name string) string { @@ -345,7 +346,7 @@ func (adapter *EPlanningAdapter) MakeBids(internalRequest *openrtb2.BidRequest, } // Builder builds a new instance of the EPlanning adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &EPlanningAdapter{ URI: config.Endpoint, testing: false, diff --git a/adapters/eplanning/eplanning_test.go b/adapters/eplanning/eplanning_test.go index 70e108d5e0a..c4a33e54c3d 100644 --- a/adapters/eplanning/eplanning_test.go +++ b/adapters/eplanning/eplanning_test.go @@ -11,7 +11,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderEPlanning, config.Adapter{ - Endpoint: "http://rtb.e-planning.net/pbs/1"}) + Endpoint: "http://rtb.e-planning.net/pbs/1"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/epom/epom.go b/adapters/epom/epom.go index a8336b64da6..3f0ebc002c8 100644 --- a/adapters/epom/epom.go +++ b/adapters/epom/epom.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -17,7 +17,7 @@ type adapter struct { } // Builder builds a new instance of the Epom adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &adapter{ endpoint: config.Endpoint, } diff --git a/adapters/epom/epom_test.go b/adapters/epom/epom_test.go index 7991f4eabc7..6769ff6beb1 100644 --- a/adapters/epom/epom_test.go +++ b/adapters/epom/epom_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderEpom, config.Adapter{ - Endpoint: "https://an.epom.com/ortb"}) + Endpoint: "https://an.epom.com/ortb"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/freewheelssp/freewheelssp.go b/adapters/freewheelssp/freewheelssp.go new file mode 100644 index 00000000000..0f82952d3e1 --- /dev/null +++ b/adapters/freewheelssp/freewheelssp.go @@ -0,0 +1,103 @@ +package freewheelssp + +import ( + "encoding/json" + "fmt" + "github.com/prebid/openrtb/v17/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 +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + for i := 0; i < len(request.Imp); i++ { + imp := &request.Imp[i] + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Invalid imp.ext for impression index %d. Error Infomation: %s", i, err.Error()), + }} + } + + var impExt openrtb_ext.ImpExtFreewheelSSP + if err := json.Unmarshal(bidderExt.Bidder, &impExt); err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Invalid imp.ext for impression index %d. Error Infomation: %s", i, err.Error()), + }} + } + + var err error + if imp.Ext, err = json.Marshal(impExt); err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Unable to transfer requestImpExt to Json fomat, %s", err.Error()), + }} + } + } + + requestJSON, err := json.Marshal(request) + if err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Unable to transfer request to Json fomat, %s", err.Error()), + }} + } + + headers := http.Header{} + headers.Add("Componentid", "prebid-go") + + requestData := &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + Body: requestJSON, + Headers: headers, + } + return []*adapters.RequestData{requestData}, 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.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} + } + + cur := bidResp.Cur + bidResponse := &adapters.BidderResponse{ + Currency: cur, + Bids: []*adapters.TypedBid{}, + } + + bidType := openrtb_ext.BidTypeVideo + + for _, seatBid := range bidResp.SeatBid { + for i := range seatBid.Bid { + b := &adapters.TypedBid{ + Bid: &seatBid.Bid[i], + BidType: bidType, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + return bidResponse, nil +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + bidder := &adapter{ + config.Endpoint, + } + return bidder, nil +} diff --git a/adapters/freewheelssp/freewheelssp_test.go b/adapters/freewheelssp/freewheelssp_test.go new file mode 100644 index 00000000000..5f06a29c2fd --- /dev/null +++ b/adapters/freewheelssp/freewheelssp_test.go @@ -0,0 +1,19 @@ +package freewheelssp + +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.BidderFreewheelSSP, config.Adapter{ + Endpoint: "https://testjsonsample.com"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "freewheelssptest", bidder) +} diff --git a/adapters/freewheelssp/freewheelssptest/exemplary/multi-imp.json b/adapters/freewheelssp/freewheelssptest/exemplary/multi-imp.json new file mode 100644 index 00000000000..5ec36993375 --- /dev/null +++ b/adapters/freewheelssp/freewheelssptest/exemplary/multi-imp.json @@ -0,0 +1,144 @@ +{ + "mockBidRequest": { + "id": "freewheelssp-test", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "imp-1", + "video": { + "mimes": ["video/mp4"], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "zoneId": 12345 + } + } + }, + { + "id": "imp-2", + "video": { + "mimes": ["video/mp4"], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "zoneId": 12346 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://testjsonsample.com", + "body":{ + "id": "freewheelssp-test", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "imp-1", + "video": { + "mimes": ["video/mp4"], + "w": 300, + "h": 250 + }, + "ext": { + "zoneId": 12345 + } + }, + { + "id": "imp-2", + "video": { + "mimes": ["video/mp4"], + "w": 300, + "h": 250 + }, + "ext": { + "zoneId": 12346 + } + } + ] + }, + "headers": { + "Componentid": [ + "prebid-go" + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "freewheelssp-test", + "seatbid": [ + { + "bid": [ + { + "id": "12345_freewheelssp-test_1", + "impid": "imp-1", + "price": 1.0, + "adid": "7857", + "adm": "", + "cid": "4001", + "crid": "7857" + }, + { + "id": "12346_freewheelssp-test_2", + "impid": "imp-2", + "price": 1.0, + "adid": "7933", + "adm": "", + "cid": "3476", + "crid": "7933" + } + ], + "seat": "freewheelsspTv" + } + ], + "bidid": "freewheelssp-test", + "cur": "EUR" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "CUR", + "bids": [ + { + "bid": { + "id": "12345_freewheelssp-test_1", + "impid": "imp-1", + "price": 1.0, + "adid": "7857", + "adm": "", + "cid": "4001", + "crid": "7857" + }, + "type": "video" + }, + { + "bid": { + "id": "12346_freewheelssp-test_2", + "impid": "imp-2", + "price": 1.0, + "adid": "7933", + "adm": "", + "cid": "3476", + "crid": "7933" + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/freewheelssp/freewheelssptest/exemplary/single-imp.json b/adapters/freewheelssp/freewheelssptest/exemplary/single-imp.json new file mode 100644 index 00000000000..1d55dfecaf0 --- /dev/null +++ b/adapters/freewheelssp/freewheelssptest/exemplary/single-imp.json @@ -0,0 +1,97 @@ +{ + "mockBidRequest": { + "id": "freewheelssp-test", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "imp-1", + "video": { + "mimes": ["video/mp4"], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "zoneId": 12345 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://testjsonsample.com", + "body":{ + "id": "freewheelssp-test", + "site": { + "page": "prebid.org" + }, + "imp": [{ + "id": "imp-1", + "video": { + "mimes": ["video/mp4"], + "w": 300, + "h": 250 + }, + "ext": { + "zoneId": 12345 + } + }] + }, + "headers": { + "Componentid": [ + "prebid-go" + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "freewheelssp-test", + "seatbid": [ + { + "bid": [ + { + "id": "12345_freewheelssp-test_1", + "impid": "imp-1", + "price": 1.0, + "adid": "7857", + "adm": "", + "cid": "4001", + "crid": "7857" + } + ], + "seat": "freewheelsspTv" + } + ], + "bidid": "freewheelssp-test", + "cur": "EUR" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "CUR", + "bids": [ + { + "bid": { + "id": "12345_freewheelssp-test_1", + "impid": "imp-1", + "price": 1.0, + "adid": "7857", + "adm": "", + "cid": "4001", + "crid": "7857" + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/freewheelssp/freewheelssptest/supplemental/204-bid-response.json b/adapters/freewheelssp/freewheelssptest/supplemental/204-bid-response.json new file mode 100644 index 00000000000..a84fa4f1268 --- /dev/null +++ b/adapters/freewheelssp/freewheelssptest/supplemental/204-bid-response.json @@ -0,0 +1,60 @@ +{ + "mockBidRequest": { + "id": "freewheelssp-test", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "imp-1", + "video": { + "mimes": ["video/mp4"], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "zoneId": 12345 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://testjsonsample.com", + "body":{ + "id": "freewheelssp-test", + "site": { + "page": "prebid.org" + }, + "imp": [{ + "id": "imp-1", + "video": { + "mimes": ["video/mp4"], + "w": 300, + "h": 250 + }, + "ext": { + "zoneId": 12345 + } + }] + }, + "headers": { + "Componentid": [ + "prebid-go" + ] + } + }, + "mockResponse": { + "status": 204, + "body" : {} + } + } + ], + + "expectedBidResponses": [] +} + diff --git a/adapters/freewheelssp/freewheelssptest/supplemental/503-bid-response.json b/adapters/freewheelssp/freewheelssptest/supplemental/503-bid-response.json new file mode 100644 index 00000000000..475114d0230 --- /dev/null +++ b/adapters/freewheelssp/freewheelssptest/supplemental/503-bid-response.json @@ -0,0 +1,64 @@ +{ + "mockBidRequest": { + "id": "freewheelssp-test", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "imp-1", + "video": { + "mimes": ["video/mp4"], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "zoneId": 12345 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://testjsonsample.com", + "body":{ + "id": "freewheelssp-test", + "site": { + "page": "prebid.org" + }, + "imp": [{ + "id": "imp-1", + "video": { + "mimes": ["video/mp4"], + "w": 300, + "h": 250 + }, + "ext": { + "zoneId": 12345 + } + }] + }, + "headers": { + "Componentid": [ + "prebid-go" + ] + } + }, + "mockResponse": { + "status": 503, + "body" : {} + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 503. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/gamma/gamma.go b/adapters/gamma/gamma.go index a99a4643632..d20b200a623 100644 --- a/adapters/gamma/gamma.go +++ b/adapters/gamma/gamma.go @@ -7,7 +7,8 @@ import ( "net/url" "strconv" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" + "github.com/prebid/openrtb/v17/openrtb3" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -30,13 +31,13 @@ type gammaSeatBid struct { Ext json.RawMessage `json:"ext,omitempty"` } type gammaBidResponse struct { - ID string `json:"id"` - SeatBid []gammaSeatBid `json:"seatbid,omitempty"` - BidID string `json:"bidid,omitempty"` - Cur string `json:"cur,omitempty"` - CustomData string `json:"customdata,omitempty"` - NBR *openrtb2.NoBidReasonCode `json:"nbr,omitempty"` - Ext json.RawMessage `json:"ext,omitempty"` + ID string `json:"id"` + SeatBid []gammaSeatBid `json:"seatbid,omitempty"` + BidID string `json:"bidid,omitempty"` + Cur string `json:"cur,omitempty"` + CustomData string `json:"customdata,omitempty"` + NBR *openrtb3.NoBidReason `json:"nbr,omitempty"` + Ext json.RawMessage `json:"ext,omitempty"` } func checkParams(gammaExt openrtb_ext.ExtImpGamma) error { @@ -275,7 +276,7 @@ func (a *GammaAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRe return bidResponse, errs } -//Adding header fields to request header +// Adding header fields to request header func addHeaderIfNonEmpty(headers http.Header, headerName string, headerValue string) { if len(headerValue) > 0 { headers.Add(headerName, headerValue) @@ -297,7 +298,7 @@ func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { } // Builder builds a new instance of the Gamma adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &GammaAdapter{ URI: config.Endpoint, } diff --git a/adapters/gamma/gamma_test.go b/adapters/gamma/gamma_test.go index 2741e425fae..08709364ce9 100644 --- a/adapters/gamma/gamma_test.go +++ b/adapters/gamma/gamma_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderGamma, config.Adapter{ - Endpoint: "https://hb.gammaplatform.com/adx/request/"}) + Endpoint: "https://hb.gammaplatform.com/adx/request/"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/gamma/params_test.go b/adapters/gamma/params_test.go index 3329545a264..56f1b591190 100644 --- a/adapters/gamma/params_test.go +++ b/adapters/gamma/params_test.go @@ -9,7 +9,7 @@ import ( // This file actually intends to test static/bidder-params/gamma.json // -// These also validate the format of the external API: request.imp[i].ext.brightroll +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.brightroll // TestValidParams makes sure that the Gamma schema accepts all imp.ext fields which we intend to support. func TestValidParams(t *testing.T) { diff --git a/adapters/gamoshi/gamoshi.go b/adapters/gamoshi/gamoshi.go index c3219c1b59d..3e9ca0551a1 100644 --- a/adapters/gamoshi/gamoshi.go +++ b/adapters/gamoshi/gamoshi.go @@ -7,7 +7,7 @@ import ( "strconv" "github.com/golang/glog" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -182,7 +182,7 @@ func getMediaType(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { } // Builder builds a new instance of the Gamoshi adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &GamoshiAdapter{ URI: config.Endpoint, } diff --git a/adapters/gamoshi/gamoshi_test.go b/adapters/gamoshi/gamoshi_test.go index d8834c45123..979f54cddab 100644 --- a/adapters/gamoshi/gamoshi_test.go +++ b/adapters/gamoshi/gamoshi_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamplesWithConfiguredURI(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderGamoshi, config.Adapter{ - Endpoint: "https://rtb.gamoshi.io"}) + Endpoint: "https://rtb.gamoshi.io"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -20,7 +20,7 @@ func TestJsonSamplesWithConfiguredURI(t *testing.T) { } func TestJsonSamplesWithHardcodedURI(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderGamoshi, config.Adapter{}) + bidder, buildErr := Builder(openrtb_ext.BidderGamoshi, config.Adapter{}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/gamoshi/params_test.go b/adapters/gamoshi/params_test.go index 29a1864b9ae..b9659aaa68a 100644 --- a/adapters/gamoshi/params_test.go +++ b/adapters/gamoshi/params_test.go @@ -9,7 +9,7 @@ import ( // This file actually intends to test static/bidder-params/gamoshi.json // -// These also validate the format of the external API: request.imp[i].ext.gamoshi +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.gamoshi // TestValidParams makes sure that the Gamoshi schema accepts all imp.ext fields which we intend to support. func TestValidParams(t *testing.T) { diff --git a/adapters/grid/grid.go b/adapters/grid/grid.go index c46fcbc2a10..f6daed7bb78 100644 --- a/adapters/grid/grid.go +++ b/adapters/grid/grid.go @@ -7,7 +7,7 @@ import ( "sort" "strings" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -21,6 +21,7 @@ type GridAdapter struct { type GridBid struct { *openrtb2.Bid + AdmNative json.RawMessage `json:"adm_native,omitempty"` ContentType openrtb_ext.BidType `json:"content_type"` } @@ -282,6 +283,35 @@ func setImpExtData(imp openrtb2.Imp) openrtb2.Imp { return imp } +func fixNative(req json.RawMessage) (json.RawMessage, error) { + var gridReq map[string]interface{} + var parsedRequest map[string]interface{} + + if err := json.Unmarshal(req, &gridReq); err != nil { + return req, nil + } + if imps, exists := maputil.ReadEmbeddedSlice(gridReq, "imp"); exists { + for _, imp := range imps { + if gridImp, ok := imp.(map[string]interface{}); ok { + native, hasNative := maputil.ReadEmbeddedMap(gridImp, "native") + if hasNative { + request, hasRequest := maputil.ReadEmbeddedString(native, "request") + if hasRequest { + delete(native, "request") + if err := json.Unmarshal([]byte(request), &parsedRequest); err == nil { + native["request_native"] = parsedRequest + } else { + native["request_native"] = request + } + } + } + } + } + } + + return json.Marshal(gridReq) +} + // MakeRequests makes the HTTP requests which should be made to fetch bids. func (a *GridAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errors = make([]error, 0) @@ -312,6 +342,14 @@ func (a *GridAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapte request.Imp = validImps reqJSON, err := json.Marshal(request) + + if err != nil { + errors = append(errors, err) + return nil, errors + } + + fixedReqJSON, err := fixNative(reqJSON) + if err != nil { errors = append(errors, err) return nil, errors @@ -323,7 +361,7 @@ func (a *GridAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapte return []*adapters.RequestData{{ Method: "POST", Uri: a.endpoint, - Body: reqJSON, + Body: fixedReqJSON, Headers: headers, }}, errors } @@ -357,6 +395,11 @@ func (a *GridAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalReq for i := range sb.Bid { bidMeta, err := getBidMeta(sb.Bid[i].Ext) bidType, err := getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp, sb.Bid[i]) + if sb.Bid[i].AdmNative != nil && sb.Bid[i].AdM == "" { + if bytes, err := json.Marshal(sb.Bid[i].AdmNative); err == nil { + sb.Bid[i].AdM = string(bytes) + } + } if err != nil { return nil, []error{err} } @@ -375,7 +418,7 @@ func (a *GridAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalReq } // Builder builds a new instance of the Grid adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &GridAdapter{ endpoint: config.Endpoint, } @@ -411,6 +454,10 @@ func getMediaTypeForImp(impID string, imps []openrtb2.Imp, bidWithType GridBid) return openrtb_ext.BidTypeVideo, nil } + if imp.Native != nil { + return openrtb_ext.BidTypeNative, nil + } + return "", &errortypes.BadServerResponse{ Message: fmt.Sprintf("Unknown impression type for ID: \"%s\"", impID), } diff --git a/adapters/grid/grid_test.go b/adapters/grid/grid_test.go index 1f9baaba640..38e9341cdaf 100644 --- a/adapters/grid/grid_test.go +++ b/adapters/grid/grid_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderGrid, config.Adapter{ - Endpoint: "http://localhost/prebid"}) + Endpoint: "http://localhost/prebid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/grid/gridtest/exemplary/multitype-native.json b/adapters/grid/gridtest/exemplary/multitype-native.json new file mode 100644 index 00000000000..2a142938ac6 --- /dev/null +++ b/adapters/grid/gridtest/exemplary/multitype-native.json @@ -0,0 +1,110 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 300, + "h": 250 + }, + "native": { + "ver": "1.1", + "request": "{\"ver\":\"1.0\",\"layout\":1,\"adunit\":1,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":60,\"hmin\":60,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":3,\"required\":0,\"data\":{\"type\":2,\"len\":75}},{\"id\":4,\"required\":0,\"data\":{\"type\":6,\"len\":1000}},{\"id\":5,\"required\":0,\"data\":{\"type\":7,\"len\":1000}},{\"id\":6,\"required\":0,\"data\":{\"type\":11,\"len\":1000}}]}" + }, + "ext": { + "bidder": { + "uid": 1 + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 300, + "h": 250 + }, + "native": { + "ver": "1.1", + "request_native": {"ver":"1.0","layout":1,"adunit":1,"plcmttype":1,"plcmtcnt":1,"seq":0,"assets":[{"id":1,"required":1,"title":{"len":75}},{"id":2,"required":1,"img":{"type":3,"wmin":60,"hmin":60,"mimes":["image/jpeg","image/jpg","image/png"]}},{"id":3,"required":0,"data":{"type":2,"len":75}},{"id":4,"required":0,"data":{"type":6,"len":1000}},{"id":5,"required":0,"data":{"type":7,"len":1000}},{"id":6,"required":0,"data":{"type":11,"len":1000}}]} + }, + "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_native": { + "some_native_key": "native_value" + }, + "cid": "987", + "crid": "12345678", + "content_type": "native", + "h": 250, + "w": 300 + }] + }], + "cur": "USD" + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "randomid", + "impid": "test-imp-id", + "price": 0.5, + "adm": "{\"some_native_key\":\"native_value\"}", + "adid": "12345678", + "cid": "987", + "crid": "12345678", + "w": 300, + "h": 250 + }, + "type": "native" + }] + }] +} diff --git a/adapters/grid/gridtest/exemplary/native-as-string.json b/adapters/grid/gridtest/exemplary/native-as-string.json new file mode 100644 index 00000000000..3ad66b98ea9 --- /dev/null +++ b/adapters/grid/gridtest/exemplary/native-as-string.json @@ -0,0 +1,85 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "native": { + "ver": "1.1", + "request": "request string" + }, + "ext": { + "bidder": { + "uid": 1 + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "native": { + "ver": "1.1", + "request_native": "request string" + }, + "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_native": { + "some_native_key": "native_value" + }, + "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_native_key\":\"native_value\"}", + "adid": "12345678", + "cid": "987", + "crid": "12345678", + "w": 300, + "h": 250 + }, + "type": "native" + }] + }] +} diff --git a/adapters/grid/gridtest/exemplary/simple-native.json b/adapters/grid/gridtest/exemplary/simple-native.json new file mode 100644 index 00000000000..be7c17f77ae --- /dev/null +++ b/adapters/grid/gridtest/exemplary/simple-native.json @@ -0,0 +1,85 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "native": { + "ver": "1.1", + "request": "{\"ver\":\"1.0\",\"layout\":1,\"adunit\":1,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":60,\"hmin\":60,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":3,\"required\":0,\"data\":{\"type\":2,\"len\":75}},{\"id\":4,\"required\":0,\"data\":{\"type\":6,\"len\":1000}},{\"id\":5,\"required\":0,\"data\":{\"type\":7,\"len\":1000}},{\"id\":6,\"required\":0,\"data\":{\"type\":11,\"len\":1000}}]}" + }, + "ext": { + "bidder": { + "uid": 1 + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "native": { + "ver": "1.1", + "request_native": {"ver":"1.0","layout":1,"adunit":1,"plcmttype":1,"plcmtcnt":1,"seq":0,"assets":[{"id":1,"required":1,"title":{"len":75}},{"id":2,"required":1,"img":{"type":3,"wmin":60,"hmin":60,"mimes":["image/jpeg","image/jpg","image/png"]}},{"id":3,"required":0,"data":{"type":2,"len":75}},{"id":4,"required":0,"data":{"type":6,"len":1000}},{"id":5,"required":0,"data":{"type":7,"len":1000}},{"id":6,"required":0,"data":{"type":11,"len":1000}}]} + }, + "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_native": { + "some_native_key": "native_value" + }, + "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_native_key\":\"native_value\"}", + "adid": "12345678", + "cid": "987", + "crid": "12345678", + "w": 300, + "h": 250 + }, + "type": "native" + }] + }] +} diff --git a/adapters/gumgum/gumgum.go b/adapters/gumgum/gumgum.go index 27533127134..d7426527b2e 100644 --- a/adapters/gumgum/gumgum.go +++ b/adapters/gumgum/gumgum.go @@ -7,7 +7,7 @@ import ( "strconv" "strings" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -222,7 +222,7 @@ func validateVideoParams(video *openrtb2.Video) (err error) { } // Builder builds a new instance of the GumGum adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &GumGumAdapter{ URI: config.Endpoint, } diff --git a/adapters/gumgum/gumgum_test.go b/adapters/gumgum/gumgum_test.go index b097042b6d3..fa78eb10e11 100644 --- a/adapters/gumgum/gumgum_test.go +++ b/adapters/gumgum/gumgum_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderGumGum, config.Adapter{ - Endpoint: "https://g2.gumgum.com/providers/prbds2s/bid"}) + Endpoint: "https://g2.gumgum.com/providers/prbds2s/bid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/huaweiads/huaweiads.go b/adapters/huaweiads/huaweiads.go index f8acb2b99bd..1294b147e87 100644 --- a/adapters/huaweiads/huaweiads.go +++ b/adapters/huaweiads/huaweiads.go @@ -8,20 +8,21 @@ import ( "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" + + "github.com/prebid/openrtb/v17/native1" + nativeRequests "github.com/prebid/openrtb/v17/native1/request" + nativeResponse "github.com/prebid/openrtb/v17/native1/response" + "github.com/prebid/openrtb/v17/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" ) const huaweiAdxApiVersion = "3.4" @@ -49,24 +50,33 @@ const ( videoWithPicturesText int32 = 11 ) +// interaction type +const ( + appPromotion int32 = 3 +) + // ads type const ( banner int32 = 8 native int32 = 3 roll int32 = 60 + interstitial int32 = 12 rewarded int32 = 7 splash int32 = 1 - interstitial int32 = 12 + magazinelock int32 = 2 + audio int32 = 17 ) 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"` + 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"` + Consent string `json:"consent,omitempty"` + ClientAdRequestId string `json:"clientAdRequestId,omitempty"` } type adslot30 struct { @@ -269,10 +279,10 @@ func (a *adapter) MakeRequests(openRTBRequest *openrtb2.BidRequest, } if huaweiAdsImpExt == nil { - return nil, []error{errors.New("UnmarshalExtImpHuaweiAds: huaweiAdsImpExt is nil.")} + return nil, []error{errors.New("Unmarshal ExtImpHuaweiAds failed: huaweiAdsImpExt is nil.")} } - adslot30, err := getHuaweiAdsReqAdslot30(huaweiAdsImpExt, &openRTBRequest.Imp[index], openRTBRequest) + adslot30, err := getReqAdslot30(huaweiAdsImpExt, &openRTBRequest.Imp[index]) if err != nil { return nil, []error{err} } @@ -280,8 +290,9 @@ func (a *adapter) MakeRequests(openRTBRequest *openrtb2.BidRequest, multislot = append(multislot, adslot30) } request.Multislot = multislot + request.ClientAdRequestId = openRTBRequest.ID - countryCode, err := getHuaweiAdsReqJson(&request, openRTBRequest, huaweiAdsImpExt, a.extraInfo) + countryCode, err := getReqJson(&request, openRTBRequest, a.extraInfo) if err != nil { return nil, []error{err} } @@ -357,7 +368,7 @@ func (a *adapter) MakeBids(openRTBRequest *openrtb2.BidRequest, requestToBidder return nil, []error{err} } - bidderResponse, err := a.convertHuaweiAdsResp2BidderResp(&huaweiAdsResponse, openRTBRequest) + bidderResponse, err := a.convertHuaweiAdsRespToBidderResp(&huaweiAdsResponse, openRTBRequest) if err != nil { return nil, []error{err} } @@ -366,7 +377,7 @@ func (a *adapter) MakeBids(openRTBRequest *openrtb2.BidRequest, requestToBidder } // 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) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { extraInfo, err := getExtraInfo(config.ExtraAdapterInfo) if err != nil { return nil, err @@ -429,65 +440,91 @@ func getHeaders(huaweiAdsImpExt *openrtb_ext.ExtImpHuaweiAds, request *openrtb2. return headers } -// getHuaweiAdsReqJson: get body json for HuaweiAds request -func getHuaweiAdsReqJson(request *huaweiAdsRequest, openRTBRequest *openrtb2.BidRequest, - huaweiAdsImpExt *openrtb_ext.ExtImpHuaweiAds, extraInfo ExtraInfo) (countryCode string, err error) { +// getReqJson: get body json for HuaweiAds request +func getReqJson(request *huaweiAdsRequest, openRTBRequest *openrtb2.BidRequest, extraInfo ExtraInfo) (countryCode string, err error) { request.Version = huaweiAdxApiVersion - if countryCode, err = getHuaweiAdsReqAppInfo(request, openRTBRequest, extraInfo); err != nil { + if countryCode, err = getReqAppInfo(request, openRTBRequest, extraInfo); err != nil { return "", err } - if err = getHuaweiAdsReqDeviceInfo(request, openRTBRequest, huaweiAdsImpExt); err != nil { + if err = getReqDeviceInfo(request, openRTBRequest); err != nil { return "", err } - getHuaweiAdsReqNetWorkInfo(request, openRTBRequest) - getHuaweiAdsReqRegsInfo(request, openRTBRequest) - getHuaweiAdsReqGeoInfo(request, openRTBRequest) + getReqNetWorkInfo(request, openRTBRequest) + getReqRegsInfo(request, openRTBRequest) + getReqGeoInfo(request, openRTBRequest) + getReqConsentInfo(request, openRTBRequest) return countryCode, nil } -func getHuaweiAdsReqAdslot30(huaweiAdsImpExt *openrtb_ext.ExtImpHuaweiAds, - openRTBImp *openrtb2.Imp, openRTBRequest *openrtb2.BidRequest) (adslot30, error) { - adtypeLower := strings.ToLower(huaweiAdsImpExt.Adtype) +func getReqAdslot30(huaweiAdsImpExt *openrtb_ext.ExtImpHuaweiAds, + openRTBImp *openrtb2.Imp) (adslot30, error) { + adtype := convertAdtypeStringToInteger(strings.ToLower(huaweiAdsImpExt.Adtype)) + testStatus := 0 + if huaweiAdsImpExt.IsTestAuthorization == "true" { + testStatus = 1 + } var adslot30 = adslot30{ Slotid: huaweiAdsImpExt.SlotId, - Adtype: convertAdtypeString2Integer(adtypeLower), - Test: int32(openRTBRequest.Test), + Adtype: adtype, + Test: int32(testStatus), } + if err := checkAndExtractOpenrtbFormat(&adslot30, adtype, huaweiAdsImpExt.Adtype, openRTBImp); err != nil { + return adslot30, err + } + return adslot30, nil +} +// opentrb : huawei adtype +// banner <-> banner, interstitial +// native <-> native +// video <-> banner, roll, interstitial, rewarded +func checkAndExtractOpenrtbFormat(adslot30 *adslot30, adtype int32, yourAdtype string, openRTBImp *openrtb2.Imp) error { 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 + if adtype != banner && adtype != interstitial { + return errors.New("check openrtb format: request has banner, doesn't correspond to huawei adtype " + yourAdtype) } + getBannerFormat(adslot30, openRTBImp) } else if openRTBImp.Native != nil { - if err := getNativeFormat(&adslot30, openRTBImp); err != nil { - return adslot30, err + if adtype != native { + return errors.New("check openrtb format: request has native, doesn't correspond to huawei adtype " + yourAdtype) + } + if err := getNativeFormat(adslot30, openRTBImp); err != nil { + return err + } + } else if openRTBImp.Video != nil { + if adtype != banner && adtype != interstitial && adtype != rewarded && adtype != roll { + return errors.New("check openrtb format: request has video, doesn't correspond to huawei adtype " + yourAdtype) } + if err := getVideoFormat(adslot30, adtype, openRTBImp); err != nil { + return err + } + } else if openRTBImp.Audio != nil { + return errors.New("check openrtb format: request has audio, not currently supported") + } else { + return errors.New("check openrtb format: please choose one of our supported type banner, native, or video") } + return nil +} - // 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.") +func getBannerFormat(adslot30 *adslot30, openRTBImp *openrtb2.Imp) { + 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 } - return adslot30, nil } func getNativeFormat(adslot30 *adslot30, openRTBImp *openrtb2.Imp) error { if openRTBImp.Native.Request == "" { - return errors.New("extractAdmNative: imp.Native.Request is empty") + return errors.New("extract openrtb native failed: imp.Native.Request is empty") } var nativePayload nativeRequests.Request @@ -495,24 +532,30 @@ func getNativeFormat(adslot30 *adslot30, openRTBImp *openrtb2.Imp) error { return err } - var numImage = 0 + // only compute the main image number, type = native1.ImageAssetTypeMain + var numMainImage = 0 var numVideo = 0 var width int64 var height int64 for _, asset := range nativePayload.Assets { + // Only one of the {title,img,video,data} objects should be present in each object. if asset.Video != nil { numVideo++ + continue } // 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 + if asset.Img.Type == native1.ImageAssetTypeMain { + numMainImage++ + 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 + } } + continue } } adslot30.W = width @@ -521,10 +564,10 @@ func getNativeFormat(adslot30 *adslot30, openRTBImp *openrtb2.Imp) error { var detailedCreativeTypeList = make([]string, 0, 2) if numVideo >= 1 { detailedCreativeTypeList = append(detailedCreativeTypeList, "903") - } else if numImage > 1 { + } else if numMainImage > 1 { detailedCreativeTypeList = append(detailedCreativeTypeList, "904") - } else if numImage == 1 { - detailedCreativeTypeList = append(detailedCreativeTypeList, "909") + } else if numMainImage == 1 { + detailedCreativeTypeList = append(detailedCreativeTypeList, "901") } else { detailedCreativeTypeList = append(detailedCreativeTypeList, "913", "914") } @@ -532,7 +575,21 @@ func getNativeFormat(adslot30 *adslot30, openRTBImp *openrtb2.Imp) error { return nil } -func convertAdtypeString2Integer(adtypeLower string) int32 { +// roll ad need TotalDuration +func getVideoFormat(adslot30 *adslot30, adtype int32, openRTBImp *openrtb2.Imp) error { + adslot30.W = openRTBImp.Video.W + adslot30.H = openRTBImp.Video.H + + if adtype == roll { + if openRTBImp.Video.MaxDuration == 0 { + return errors.New("extract openrtb video failed: MaxDuration is empty when huaweiads adtype is roll.") + } + adslot30.TotalDuration = int32(openRTBImp.Video.MaxDuration) + } + return nil +} + +func convertAdtypeStringToInteger(adtypeLower string) int32 { switch adtypeLower { case "banner": return banner @@ -540,19 +597,23 @@ func convertAdtypeString2Integer(adtypeLower string) int32 { return native case "rewarded": return rewarded - case "splash": - return splash case "interstitial": return interstitial case "roll": return roll + case "splash": + return splash + case "magazinelock": + return magazinelock + case "audio": + return audio default: return banner } } -// getHuaweiAdsReqAppInfo: get app information for HuaweiAds request -func getHuaweiAdsReqAppInfo(request *huaweiAdsRequest, openRTBRequest *openrtb2.BidRequest, extraInfo ExtraInfo) (countryCode string, err error) { +// getReqAppInfo: get app information for HuaweiAds request +func getReqAppInfo(request *huaweiAdsRequest, openRTBRequest *openrtb2.BidRequest, extraInfo ExtraInfo) (countryCode string, err error) { var app app if openRTBRequest.App != nil { if openRTBRequest.App.Ver != "" { @@ -566,7 +627,7 @@ func getHuaweiAdsReqAppInfo(request *huaweiAdsRequest, openRTBRequest *openrtb2. if openRTBRequest.App.Bundle != "" { app.Pkgname = getFinalPkgName(openRTBRequest.App.Bundle, extraInfo) } else { - return "", errors.New("HuaweiAdsReqApp: Pkgname is empty.") + return "", errors.New("generate HuaweiAds AppInfo failed: openrtb BidRequest.App.Bundle is empty.") } if openRTBRequest.App.Content != nil && openRTBRequest.App.Content.Language != "" { @@ -620,6 +681,7 @@ func getFinalPkgName(bundleName string, extraInfo ExtraInfo) string { } // getClientTime: get field clientTime, format: 2006-01-02 15:04:05.000+0200 +// If this parameter is not passed, the server time is used func getClientTime(clientTime string) (newClientTime string) { var zone = defaultTimeZone t := time.Now().Local().Format(time.RFC822Z) @@ -639,8 +701,8 @@ func getClientTime(clientTime string) (newClientTime string) { 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) { +// getReqDeviceInfo: get device information for HuaweiAds request +func getReqDeviceInfo(request *huaweiAdsRequest, openRTBRequest *openrtb2.BidRequest) (err error) { var device device if openRTBRequest.Device != nil { device.Type = int32(openRTBRequest.Device.DeviceType) @@ -660,11 +722,24 @@ func getHuaweiAdsReqDeviceInfo(request *huaweiAdsRequest, openRTBRequest *openrt device.BelongCountry = country device.LocaleCountry = country device.Ip = openRTBRequest.Device.IP + device.Gaid = openRTBRequest.Device.IFA } + // get oaid gaid imei in openRTBRequest.User.Ext.Data - if err = getDeviceID(&device, openRTBRequest); err != nil { + if err = getDeviceIDFromUserExt(&device, openRTBRequest); err != nil { return err } + + // 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)) + } + } + request.Device = device return nil } @@ -674,6 +749,8 @@ func getCountryCode(openRTBRequest *openrtb2.BidRequest) string { 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 if openRTBRequest.Device != nil && openRTBRequest.Device.MCCMNC != "" { + return getCountryCodeFromMCC(openRTBRequest.Device.MCCMNC) } else { return defaultCountryName } @@ -701,48 +778,66 @@ func convertCountryCode(country string) (out string) { 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]) +func getCountryCodeFromMCC(MCC string) (out string) { + var countryMCC = strings.Split(MCC, "-")[0] + intVar, err := strconv.Atoi(countryMCC) + + if err != nil { + return defaultCountryName + } else { + if result, found := MccList[intVar]; found { + return strings.ToUpper(result) + } else { + return defaultCountryName + } } - // IsTrackingEnabled = 1 - DNT - if openRTBRequest.Device != nil && openRTBRequest.Device.DNT != nil { - if device.Oaid != "" { - device.IsTrackingEnabled = strconv.Itoa(1 - int(*openRTBRequest.Device.DNT)) +} + +// getDeviceID include oaid gaid imei. In prebid mobile, use TargetingParams.addUserData("imei", "imei-test"); +// When ifa: gaid exists, other device id can be passed by TargetingParams.addUserData("oaid", "oaid-test"); +func getDeviceIDFromUserExt(device *device, openRTBRequest *openrtb2.BidRequest) (err error) { + var userObjExist = true + if openRTBRequest.User == nil || openRTBRequest.User.Ext == nil { + userObjExist = false + } + if userObjExist { + var extUserDataHuaweiAds openrtb_ext.ExtUserDataHuaweiAds + if err := json.Unmarshal(openRTBRequest.User.Ext, &extUserDataHuaweiAds); err != nil { + return errors.New("get gaid from openrtb Device.IFA failed, and get device id failed: Unmarshal openRTBRequest.User.Ext -> extUserDataHuaweiAds. Error: " + err.Error()) } - if device.Gaid != "" { - device.GaidTrackingEnabled = strconv.Itoa(1 - int(*openRTBRequest.Device.DNT)) + + var deviceId = extUserDataHuaweiAds.Data + isValidDeviceId := false + + if len(deviceId.Oaid) > 0 { + device.Oaid = deviceId.Oaid[0] + isValidDeviceId = true + } + if len(deviceId.Gaid) > 0 { + device.Gaid = deviceId.Gaid[0] + isValidDeviceId = true + } + if len(deviceId.Imei) > 0 { + device.Imei = deviceId.Imei[0] + isValidDeviceId = true + } + + if !isValidDeviceId { + return errors.New("getDeviceID: Imei ,Oaid, Gaid are all empty.") + } + if len(deviceId.ClientTime) > 0 { + device.ClientTime = getClientTime(deviceId.ClientTime[0]) + } + } else { + if len(device.Gaid) == 0 { + return errors.New("getDeviceID: openRTBRequest.User.Ext is nil and device.Gaid is not specified.") } } return nil } -// getHuaweiAdsReqNetWorkInfo: for HuaweiAds request, include Carrier, Mcc, Mnc -func getHuaweiAdsReqNetWorkInfo(request *huaweiAdsRequest, openRTBRequest *openrtb2.BidRequest) { +// getReqNetWorkInfo: for HuaweiAds request, include Carrier, Mcc, Mnc +func getReqNetWorkInfo(request *huaweiAdsRequest, openRTBRequest *openrtb2.BidRequest) { if openRTBRequest.Device != nil { var network network if openRTBRequest.Device.ConnectionType != nil { @@ -777,8 +872,8 @@ func getHuaweiAdsReqNetWorkInfo(request *huaweiAdsRequest, openRTBRequest *openr } } -// getHuaweiAdsReqRegsInfo: get regs information for HuaweiAds request, include Coppa -func getHuaweiAdsReqRegsInfo(request *huaweiAdsRequest, openRTBRequest *openrtb2.BidRequest) { +// getReqRegsInfo: get regs information for HuaweiAds request, include Coppa +func getReqRegsInfo(request *huaweiAdsRequest, openRTBRequest *openrtb2.BidRequest) { if openRTBRequest.Regs != nil && openRTBRequest.Regs.COPPA >= 0 { var regs regs regs.Coppa = int32(openRTBRequest.Regs.COPPA) @@ -786,8 +881,8 @@ func getHuaweiAdsReqRegsInfo(request *huaweiAdsRequest, openRTBRequest *openrtb2 } } -// getHuaweiAdsReqGeoInfo: get geo information for HuaweiAds request, include Lon, Lat, Accuracy, Lastfix -func getHuaweiAdsReqGeoInfo(request *huaweiAdsRequest, openRTBRequest *openrtb2.BidRequest) { +// getReqGeoInfo: get geo information for HuaweiAds request, include Lon, Lat, Accuracy, Lastfix +func getReqGeoInfo(request *huaweiAdsRequest, openRTBRequest *openrtb2.BidRequest) { if openRTBRequest.Device != nil && openRTBRequest.Device.Geo != nil { var geo geo geo.Lon = float32(openRTBRequest.Device.Geo.Lon) @@ -798,6 +893,18 @@ func getHuaweiAdsReqGeoInfo(request *huaweiAdsRequest, openRTBRequest *openrtb2. } } +// getReqGeoInfo: get GDPR consent +func getReqConsentInfo(request *huaweiAdsRequest, openRTBRequest *openrtb2.BidRequest) { + if openRTBRequest.User != nil && openRTBRequest.User.Ext != nil { + var extUser openrtb_ext.ExtUser + if err := json.Unmarshal(openRTBRequest.User.Ext, &extUser); err != nil { + fmt.Errorf("failed to parse ExtUser in HuaweiAds GDPR check: %v", err) + return + } + request.Consent = extUser.Consent + } +} + func unmarshalExtImpHuaweiAds(openRTBImp *openrtb2.Imp) (*openrtb_ext.ExtImpHuaweiAds, error) { var bidderExt adapters.ExtImpBidder var huaweiAdsImpExt openrtb_ext.ExtImpHuaweiAds @@ -861,10 +968,10 @@ func checkHuaweiAdsResponseRetcode(response huaweiAdsResponse) error { return nil } -// convertHuaweiAdsResp2BidderResp: convert HuaweiAds' response into bidder's response -func (a *adapter) convertHuaweiAdsResp2BidderResp(huaweiAdsResponse *huaweiAdsResponse, openRTBRequest *openrtb2.BidRequest) (bidderResponse *adapters.BidderResponse, err error) { +// convertHuaweiAdsRespToBidderResp: convert HuaweiAds' response into bidder's response +func (a *adapter) convertHuaweiAdsRespToBidderResp(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.") + return nil, errors.New("convert huaweiads response to bidder response failed: multiad length is 0, get no ads from huawei side.") } bidderResponse = adapters.NewBidderResponseWithBidsCapacity(len(huaweiAdsResponse.Multiad)) // Default Currency: CNY @@ -892,7 +999,7 @@ func (a *adapter) convertHuaweiAdsResp2BidderResp(huaweiAdsResponse *huaweiAdsRe } if len(mapSlotid2MediaType) < 1 || len(mapSlotid2Imp) < 1 { - return nil, errors.New("convertHuaweiAdsResp2BidderResp: openRTBRequest.imp is nil") + return nil, errors.New("convert huaweiads response to bidder response failed: openRTBRequest.imp is nil") } for _, ad30 := range huaweiAdsResponse.Multiad { @@ -946,7 +1053,6 @@ func getNurl(content content) string { // 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) @@ -959,7 +1065,7 @@ func (a *adapter) handleHuaweiAdsContent(adType int32, content *content, bidType } if err != nil { - return "", 0, 0, fmt.Errorf("getAdmFromHuaweiAdsContent failed: %s", err) + return "", 0, 0, fmt.Errorf("generate Adm field from HuaweiAds response failed: %s", err) } return adm, adWidth, adHeight, nil } @@ -967,8 +1073,9 @@ func (a *adapter) handleHuaweiAdsContent(adType int32, content *content, bidType // 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") + // support openrtb: banner <=> huawei adtype: banner, interstitial + if adType != banner && adType != interstitial { + return "", 0, 0, errors.New("openrtb banner should correspond to huaweiads adtype: banner or interstitial") } var creativeType = content.Creativetype if content.Creativetype > 100 { @@ -989,13 +1096,13 @@ func (a *adapter) extractAdmBanner(adType int32, content *content, bidType openr 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") + return "", 0, 0, errors.New("extract Adm for Native ad: huaweiads response is not a native ad") } if openrtb2Imp.Native == nil { - return "", 0, 0, errors.New("extractAdmNative: imp.Native is nil") + return "", 0, 0, errors.New("extract Adm for Native ad: imp.Native is nil") } if openrtb2Imp.Native.Request == "" { - return "", 0, 0, errors.New("extractAdmNative: imp.Native.Request is empty") + return "", 0, 0, errors.New("extract Adm for Native ad: imp.Native.Request is empty") } var nativePayload nativeRequests.Request @@ -1005,10 +1112,9 @@ func (a *adapter) extractAdmNative(adType int32, content *content, bidType openr 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) + linkObject.URL, err = a.getClickUrl(content) + if err != nil { + return "", 0, 0, err } nativeResult.Assets = make([]nativeResponse.Asset, 0, len(nativePayload.Assets)) @@ -1116,14 +1222,13 @@ func jsonEncode(nativeResult nativeResponse.Response) ([]byte, error) { // 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") + return "", 0, 0, errors.New("extract Adm failed: content is empty") } var clickUrl = "" - if content.MetaData.ClickUrl != "" { - clickUrl = content.MetaData.ClickUrl - } else if content.MetaData.Intent != "" { - clickUrl = getDecodeValue(content.MetaData.Intent) + clickUrl, err = a.getClickUrl(content) + if err != nil { + return "", 0, 0, err } var imageInfoUrl string @@ -1177,6 +1282,24 @@ func (a *adapter) extractAdmPicture(content *content) (adm string, adWidth int64 return adm, adWidth, adHeight, nil } +// for Interactiontype == appPromotion, clickUrl is intent +func (a *adapter) getClickUrl(content *content) (clickUrl string, err error) { + if content.Interactiontype == appPromotion { + if content.MetaData.Intent != "" { + clickUrl = getDecodeValue(content.MetaData.Intent) + } else { + return "", errors.New("content.MetaData.Intent in huaweiads resopnse is empty when interactiontype is appPromotion") + } + } else { + if content.MetaData.ClickUrl != "" { + clickUrl = content.MetaData.ClickUrl + } else if content.MetaData.Intent != "" { + clickUrl = getDecodeValue(content.MetaData.Intent) + } + } + return clickUrl, nil +} + func getDspImpClickTrackings(content *content) (dspImpTrackings []string, dspClickTrackings string) { for _, monitor := range content.Monitor { if len(monitor.Url) != 0 { @@ -1216,14 +1339,13 @@ func getDuration(duration int64) string { 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") + return "", 0, 0, errors.New("extract Adm for video failed: content is empty") } var clickUrl = "" - if content.MetaData.ClickUrl != "" { - clickUrl = content.MetaData.ClickUrl - } else if content.MetaData.Intent != "" { - clickUrl = getDecodeValue(content.MetaData.Intent) + clickUrl, err = a.getClickUrl(content) + if err != nil { + return "", 0, 0, err } var mime = "video/mp4" @@ -1239,14 +1361,14 @@ func (a *adapter) extractAdmVideo(adType int32, content *content, bidType openrt if content.MetaData.MediaFile.Url != "" { resourceUrl = content.MetaData.MediaFile.Url } else { - return "", 0, 0, errors.New("extractAdmVideo: Content.MetaData.MediaFile.Url is empty") + return "", 0, 0, errors.New("extract Adm for video failed: 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") + return "", 0, 0, errors.New("extract Adm for video failed: content.MetaData.VideoInfo.VideoDownloadUrl is empty") } if content.MetaData.VideoInfo.Width != 0 && content.MetaData.VideoInfo.Height != 0 { adWidth = int64(content.MetaData.VideoInfo.Width) @@ -1257,7 +1379,7 @@ func (a *adapter) extractAdmVideo(adType int32, content *content, bidType openrt adHeight = opentrb2Imp.Video.H } } else { - return "", 0, 0, errors.New("extractAdmVideo: cannot get width, height") + return "", 0, 0, errors.New("extract Adm for video failed: cannot get video width, height") } duration = getDuration(int64(content.MetaData.VideoInfo.VideoDuration)) } @@ -1306,6 +1428,47 @@ func (a *adapter) extractAdmVideo(adType int32, content *content, bidType openrt } } + // Only for rewarded video + var rewardedVideoPart = "" + var isAddRewardedVideoPart = true + if adType == rewarded { + var staticImageUrl = "" + var staticImageHeight = "" + var staticImageWidth = "" + var staticImageType = "image/png" + if len(content.MetaData.Icon) > 0 && content.MetaData.Icon[0].Url != "" { + staticImageUrl = content.MetaData.Icon[0].Url + if content.MetaData.Icon[0].Height > 0 && content.MetaData.Icon[0].Width > 0 { + staticImageHeight = strconv.Itoa(int(content.MetaData.Icon[0].Height)) + staticImageWidth = strconv.Itoa(int(content.MetaData.Icon[0].Width)) + } else { + staticImageHeight = strconv.Itoa(int(adHeight)) + staticImageWidth = strconv.Itoa(int(adWidth)) + } + } else if len(content.MetaData.ImageInfo) > 0 && content.MetaData.ImageInfo[0].Url != "" { + staticImageUrl = content.MetaData.ImageInfo[0].Url + if content.MetaData.ImageInfo[0].Height > 0 && content.MetaData.ImageInfo[0].Width > 0 { + staticImageHeight = strconv.Itoa(int(content.MetaData.ImageInfo[0].Height)) + staticImageWidth = strconv.Itoa(int(content.MetaData.ImageInfo[0].Width)) + } else { + staticImageHeight = strconv.Itoa(int(adHeight)) + staticImageWidth = strconv.Itoa(int(adWidth)) + } + } else { + isAddRewardedVideoPart = false + } + if isAddRewardedVideoPart { + rewardedVideoPart = `` + + "" + + `` + + `` + + "" + + "" + + "" + + "" + } + } + adm = `` + `` + `` + @@ -1328,7 +1491,7 @@ func (a *adapter) extractAdmVideo(adType int32, content *content, bidType openrt "" + "" + "" + - "" + + "" + rewardedVideoPart + "" + "" + "" + diff --git a/adapters/huaweiads/huaweiads_test.go b/adapters/huaweiads/huaweiads_test.go index d3eeedff588..f523f0b9d94 100644 --- a/adapters/huaweiads/huaweiads_test.go +++ b/adapters/huaweiads/huaweiads_test.go @@ -1,11 +1,12 @@ package huaweiads 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" - "testing" ) func TestJsonSamples(t *testing.T) { @@ -21,7 +22,7 @@ func TestJsonSamples(t *testing.T) { "\"unconvertedPkgNameKeyWords\":[\"p11\",\"p12\"]," + "\"unconvertedPkgNamePrefixs\":[\"com.example3\",\"com.example4\"]," + "\"exceptionPkgNames\":[\"com.example.p15\",\"com.example3.unchanged\"]}]}", - }) + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -33,7 +34,7 @@ func TestExtraInfoDefaultWhenEmpty(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderHuaweiAds, config.Adapter{ Endpoint: `https://huaweiads.com/adxtest/`, ExtraAdapterInfo: ``, - }) + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -52,7 +53,7 @@ func TestExtraInfo1(t *testing.T) { "\"unconvertedPkgNameKeyWords\":[\"com.example.p3\",\"com.example.p4\"]," + "\"unconvertedPkgNamePrefixs\":[\"com.example.p5\",\"com.example.p6\"]," + "\"exceptionPkgNames\":[\"com.example.p7\",\"com.example.p8\"]}]}", - }) + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -80,7 +81,7 @@ func TestExtraInfo2(t *testing.T) { "\"unconvertedPkgNameKeyWords\":[\"com.example.p11\",\"com.example.p12\"]," + "\"unconvertedPkgNamePrefixs\":[\"com.example.p13\",\"com.example.p14\"]," + "\"exceptionPkgNames\":[\"com.example.p15\",\"com.example.p16\"]}]}", - }) + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -104,7 +105,7 @@ func TestExtraInfo3(t *testing.T) { "\"unconvertedPkgNamePrefixs\":[\"com.example.p5\",\"com.example.p6\"]," + "\"exceptionPkgNames\":[\"com.example.p7\",\"com.example.p8\"]}]," + "\"closeSiteSelectionByCountry\":\"1\"}", - }) + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Equal(t, buildErr.Error(), "invalid extra info: ConvertedPkgName is empty, pls check") } @@ -117,7 +118,7 @@ func TestExtraInfo4(t *testing.T) { "\"unconvertedPkgNamePrefixs\":[\"com.example.p5\",\"\"]," + "\"exceptionPkgNames\":[\"com.example.p7\",\"com.example.p8\"]}]," + "\"closeSiteSelectionByCountry\":\"1\"}", - }) + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Equal(t, buildErr.Error(), "invalid extra info: UnconvertedPkgNameKeyWords has a empty keyword, pls check") } @@ -130,7 +131,7 @@ func TestExtraInfo5(t *testing.T) { "\"unconvertedPkgNamePrefixs\":[\"com.example.p5\",\"\"]," + "\"exceptionPkgNames\":[\"com.example.p7\",\"com.example.p8\"]}]," + "\"closeSiteSelectionByCountry\":\"1\"}", - }) + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Equal(t, buildErr.Error(), "invalid extra info: UnconvertedPkgNamePrefixs has a empty value, pls check") } @@ -147,7 +148,7 @@ func TestExtraInfo6(t *testing.T) { "\"unconvertedPkgNameKeyWords\":[\"com.example.p11\",\"com.example.p12\"]," + "\"unconvertedPkgNamePrefixs\":[\"com.example.p13\",\"com.example.p14\"]," + "\"exceptionPkgNames\":[\"com.example.p15\",\"com.example.p16\"]}],\"closeSiteSelectionByCountry\":\"1\"}", - }) + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/huaweiads/huaweiadstest/exemplary/banner1.json b/adapters/huaweiads/huaweiadstest/exemplary/banner1.json index df1b40eba0a..ffcc704f617 100644 --- a/adapters/huaweiads/huaweiadstest/exemplary/banner1.json +++ b/adapters/huaweiads/huaweiadstest/exemplary/banner1.json @@ -45,7 +45,9 @@ "make": "huawei", "w": 1080, "ip": "ip", + "ifa": "e4fe9bde-caa0-47b6-908d-ffba3fa184f2", "pxratio": 23.01, + "mccmnc": "460", "geo": { "country": "CHN" } @@ -62,7 +64,8 @@ "clientTime": [ "2018-11-02 16:34:07.981+1300" ] - } + }, + "consent": "CPaYLJBPaYLJBIPAAAENCSCgAPAAAAAAAAAAGsQAQGsAAAAA.YAAAAAAAAAA" } }, "regs": { @@ -101,7 +104,7 @@ { "adtype": 8, "slotid": "m8x9x3rzff", - "test": 0, + "test": 1, "format": [ { "w": 300, @@ -138,7 +141,9 @@ "regs": { "coppa": 1 }, - "version": "3.4" + "consent": "CPaYLJBPaYLJBIPAAAENCSCgAPAAAAAAAAAAGsQAQGsAAAAA.YAAAAAAAAAA", + "version": "3.4", + "clientAdRequestId": "test-req-id" } }, "mockResponse": { diff --git a/adapters/huaweiads/huaweiadstest/exemplary/banner2.json b/adapters/huaweiads/huaweiadstest/exemplary/banner2.json index 65580187f3e..4f1cc6b498a 100644 --- a/adapters/huaweiads/huaweiadstest/exemplary/banner2.json +++ b/adapters/huaweiads/huaweiadstest/exemplary/banner2.json @@ -126,7 +126,7 @@ { "adtype": 8, "slotid": "m8x9x3rzff", - "test": 0, + "test": 1, "format": [ { "w": 300, @@ -165,7 +165,8 @@ }, "regs": { }, - "version": "3.4" + "version": "3.4", + "clientAdRequestId": "a3b18c5b-6708-4bb7-b41b-40ed37d89561" } }, "mockResponse": { diff --git a/adapters/huaweiads/huaweiadstest/exemplary/banner3.json b/adapters/huaweiads/huaweiadstest/exemplary/banner3.json new file mode 100644 index 00000000000..d012dd8e912 --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/exemplary/banner3.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": "za", + "w": 360, + "h": 755, + "pxratio": 3, + "mccmnc": "424-3", + "carrier": "carrier", + "connectiontype": 1, + "geo": { + } + }, + "app": { + "bundle": "com.example.p10", + "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://adx-dra.op.hicloud.com/ppsadx/getResult", + "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": "AE", + "name": "API1.0Demo", + "pkgname": "com.example.pkgname2", + "version": "1.11" + }, + "multislot": [ + { + "adtype": 8, + "slotid": "m8x9x3rzff", + "test": 1, + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + ], + "device": { + "height": 755, + "language": "za", + "oaid": "oaid", + "os": "android", + "localeCountry": "AE", + "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": "AE" + }, + "geo": { + }, + "network": { + "carrier": 99, + "cellInfo": [ + { + "mcc": "424", + "mnc": "3" + } + ], + "type": 1 + }, + "regs": { + }, + "version": "3.4", + "clientAdRequestId": "a3b18c5b-6708-4bb7-b41b-40ed37d89561" + } + }, + "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/banner4_mccmnc.json b/adapters/huaweiads/huaweiadstest/exemplary/banner4_mccmnc.json new file mode 100644 index 00000000000..d72cdb19e40 --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/exemplary/banner4_mccmnc.json @@ -0,0 +1,269 @@ +{ + "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": "655-1", + "carrier": "carrier", + "connectiontype": 1 + }, + "app": { + "bundle": "com.example.p10", + "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://adx-dra.op.hicloud.com/ppsadx/getResult", + "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": "com.example.pkgname2", + "version": "1.11" + }, + "multislot": [ + { + "adtype": 8, + "slotid": "m8x9x3rzff", + "test": 1, + "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": "655", + "mnc": "1" + } + ], + "type": 1 + }, + "regs": { + }, + "version": "3.4", + "clientAdRequestId": "a3b18c5b-6708-4bb7-b41b-40ed37d89561" + } + }, + "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/banner5_user_geo.json b/adapters/huaweiads/huaweiadstest/exemplary/banner5_user_geo.json new file mode 100644 index 00000000000..987d157e0eb --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/exemplary/banner5_user_geo.json @@ -0,0 +1,272 @@ +{ + "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": "655-1", + "carrier": "carrier", + "connectiontype": 1 + }, + "app": { + "bundle": "com.example.p10", + "ver": "1.11", + "name": "API1.0Demo", + "publisher": { + "id": "1001" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.11" + } + } + }, + "user": { + "gender": "O", + "geo": { + "country": "ZAF" + }, + "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://adx-dra.op.hicloud.com/ppsadx/getResult", + "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": "com.example.pkgname2", + "version": "1.11" + }, + "multislot": [ + { + "adtype": 8, + "slotid": "m8x9x3rzff", + "test": 1, + "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": "655", + "mnc": "1" + } + ], + "type": 1 + }, + "regs": { + }, + "version": "3.4", + "clientAdRequestId": "a3b18c5b-6708-4bb7-b41b-40ed37d89561" + } + }, + "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/banner6_imei.json b/adapters/huaweiads/huaweiadstest/exemplary/banner6_imei.json new file mode 100644 index 00000000000..4de8fa7ef94 --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/exemplary/banner6_imei.json @@ -0,0 +1,262 @@ +{ + "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.example.p1", + "name": "Huawei Browser", + "ver": "9.1.0.301", + "content": { + "language": "zh" + } + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "zh", + "model": "COL-TEST", + "os": "android", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "ip", + "ifa": "e4fe9bde-caa0-47b6-908d-ffba3fa184f2", + "pxratio": 23.01, + "mccmnc": "460", + "geo": { + "country": "CHN" + } + }, + "user": { + "ext": { + "data": { + "gaid": [ + "gaid" + ], + "oaid": [ + "oaid" + ], + "imei": [ + "354347048908641" + ], + "clientTime": [ + "2018-11-02 16:34:07.981+1300" + ] + }, + "consent": "CPaYLJBPaYLJBIPAAAENCSCgAPAAAAAAAAAAGsQAQGsAAAAA.YAAAAAAAAAA" + } + }, + "regs": { + "coppa": 1 + }, + "ext": { + } + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://acd.op.hicloud.com/ppsadx/getResult", + "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.example.pkgname1", + "version": "9.1.0.301" + }, + "multislot": [ + { + "adtype": 8, + "slotid": "m8x9x3rzff", + "test": 1, + "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", + "imei": "354347048908641", + "useragent": "useragent", + "version": "10.0.0", + "maker": "huawei", + "model": "COL-TEST", + "belongCountry": "CN" + }, + "geo": { + }, + "network": { + "type": 0 + }, + "regs": { + "coppa": 1 + }, + "consent": "CPaYLJBPaYLJBIPAAAENCSCgAPAAAAAAAAAAGsQAQGsAAAAA.YAAAAAAAAAA", + "version": "3.4", + "clientAdRequestId": "test-req-id" + } + }, + "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/bannerAppPromotionType.json b/adapters/huaweiads/huaweiadstest/exemplary/bannerAppPromotionType.json new file mode 100644 index 00000000000..685a5235492 --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/exemplary/bannerAppPromotionType.json @@ -0,0 +1,227 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "slotid": "testslotid", + "adtype": "banner", + "publisherid": "123", + "signkey": "signkey", + "keyid": "41", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.example.p1", + "name": "Huawei Browser", + "ver": "9.1.0.301", + "content": { + "language": "zh" + } + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "zh", + "model": "COL-TEST", + "os": "android", + "ifa": "gaid", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "ip", + "pxratio": 23.01, + "geo": { + "country": "CHN" + } + }, + "user": { + "ext": { + "data": { + "gaid": [ + ], + "oaid": [ + "oaid" + ], + "clientTime": [ + "2018-11-02 16:34:07.981+1300" + ] + } + } + }, + "regs": { + "coppa": 1 + }, + "ext": { + } + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://acd.op.hicloud.com/ppsadx/getResult", + "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.example.pkgname1", + "version": "9.1.0.301" + }, + "multislot": [ + { + "adtype": 8, + "slotid": "testslotid", + "test": 1, + "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", + "clientAdRequestId": "test-req-id" + } + }, + "mockResponse": { + "status": 200, + "body": { + "multiad": [ + { + "adtype": 8, + "content": [ + { + "contentid": "58025103", + "creativetype": 2, + "ctrlSwitchs": "001011001001010112", + "endtime": 1621428898335, + "interactiontype": 3, + "landingTitle": 1, + "metaData": { + "adSign": "2", + "appPromotionChannel": "401721412", + "clickUrl": "https://ads.huawei.com/usermgtportal/home/index.html#/", + "intent": "https%3A%2F%2Fhibobi.app.link%2FK1sog7A40hb", + "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" + ] + }, + { + "eventType": "imp", + "url": [ + "http://test/imp1" + ] + } + ], + "price": 2.8, + "starttime": 1621267200000, + "taskid": "48017658" + } + ], + "retcode30": 200, + "slotid": "testslotid" + } + ], + "retcode": 200 + } + } + } + ], + "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/bannerNonIntegerMccmnc.json b/adapters/huaweiads/huaweiadstest/exemplary/bannerNonIntegerMccmnc.json new file mode 100644 index 00000000000..8cb6ca920b1 --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/exemplary/bannerNonIntegerMccmnc.json @@ -0,0 +1,269 @@ +{ + "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": "test-1", + "carrier": "carrier", + "connectiontype": 1 + }, + "app": { + "bundle": "com.example.p10", + "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://adx-dra.op.hicloud.com/ppsadx/getResult", + "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": "com.example.pkgname2", + "version": "1.11" + }, + "multislot": [ + { + "adtype": 8, + "slotid": "m8x9x3rzff", + "test": 1, + "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": "test", + "mnc": "1" + } + ], + "type": 1 + }, + "regs": { + }, + "version": "3.4", + "clientAdRequestId": "a3b18c5b-6708-4bb7-b41b-40ed37d89561" + } + }, + "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/bannerNotAppPromotionType.json b/adapters/huaweiads/huaweiadstest/exemplary/bannerNotAppPromotionType.json new file mode 100644 index 00000000000..ec1ace1dee2 --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/exemplary/bannerNotAppPromotionType.json @@ -0,0 +1,227 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "slotid": "testslotid", + "adtype": "banner", + "publisherid": "123", + "signkey": "signkey", + "keyid": "41", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.example.p1", + "name": "Huawei Browser", + "ver": "9.1.0.301", + "content": { + "language": "zh" + } + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "zh", + "model": "COL-TEST", + "os": "android", + "ifa": "gaid", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "ip", + "pxratio": 23.01, + "geo": { + "country": "CHN" + } + }, + "user": { + "ext": { + "data": { + "gaid": [ + ], + "oaid": [ + "oaid" + ], + "clientTime": [ + "2018-11-02 16:34:07.981+1300" + ] + } + } + }, + "regs": { + "coppa": 1 + }, + "ext": { + } + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://acd.op.hicloud.com/ppsadx/getResult", + "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.example.pkgname1", + "version": "9.1.0.301" + }, + "multislot": [ + { + "adtype": 8, + "slotid": "testslotid", + "test": 1, + "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", + "clientAdRequestId": "test-req-id" + } + }, + "mockResponse": { + "status": 200, + "body": { + "multiad": [ + { + "adtype": 8, + "content": [ + { + "contentid": "58025103", + "creativetype": 2, + "ctrlSwitchs": "001011001001010112", + "endtime": 1621428898335, + "interactiontype": 2, + "landingTitle": 1, + "metaData": { + "adSign": "2", + "appPromotionChannel": "401721412", + "clickUrl": "", + "intent": "https%3A%2F%2Fads.huawei.com%2Fusermgtportal%2Fhome%2Findex.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" + ] + }, + { + "eventType": "imp", + "url": [ + "http://test/imp1" + ] + } + ], + "price": 2.8, + "starttime": 1621267200000, + "taskid": "48017658" + } + ], + "retcode30": 200, + "slotid": "testslotid" + } + ], + "retcode": 200 + } + } + } + ], + "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/bannerTestExtraInfo1.json b/adapters/huaweiads/huaweiadstest/exemplary/bannerTestExtraInfo1.json index 10fceb5c1ac..6010fdcf7c7 100644 --- a/adapters/huaweiads/huaweiadstest/exemplary/bannerTestExtraInfo1.json +++ b/adapters/huaweiads/huaweiadstest/exemplary/bannerTestExtraInfo1.json @@ -101,7 +101,7 @@ { "adtype": 8, "slotid": "m8x9x3rzff", - "test": 0, + "test": 1, "format": [ { "w": 300, @@ -138,7 +138,8 @@ "regs": { "coppa": 1 }, - "version": "3.4" + "version": "3.4", + "clientAdRequestId": "test-req-id" } }, "mockResponse": { diff --git a/adapters/huaweiads/huaweiadstest/exemplary/bannerTestExtraInfo2.json b/adapters/huaweiads/huaweiadstest/exemplary/bannerTestExtraInfo2.json index a05ca8d9820..43e540f30bc 100644 --- a/adapters/huaweiads/huaweiadstest/exemplary/bannerTestExtraInfo2.json +++ b/adapters/huaweiads/huaweiadstest/exemplary/bannerTestExtraInfo2.json @@ -126,7 +126,7 @@ { "adtype": 8, "slotid": "m8x9x3rzff", - "test": 0, + "test": 1, "format": [ { "w": 300, @@ -165,7 +165,8 @@ }, "regs": { }, - "version": "3.4" + "version": "3.4", + "clientAdRequestId": "a3b18c5b-6708-4bb7-b41b-40ed37d89561" } }, "mockResponse": { diff --git a/adapters/huaweiads/huaweiadstest/exemplary/bannerTestExtraInfo3.json b/adapters/huaweiads/huaweiadstest/exemplary/bannerTestExtraInfo3.json index 6ab59e5cb80..65f47015975 100644 --- a/adapters/huaweiads/huaweiadstest/exemplary/bannerTestExtraInfo3.json +++ b/adapters/huaweiads/huaweiadstest/exemplary/bannerTestExtraInfo3.json @@ -126,7 +126,7 @@ { "adtype": 8, "slotid": "m8x9x3rzff", - "test": 0, + "test": 1, "format": [ { "w": 300, @@ -165,7 +165,8 @@ }, "regs": { }, - "version": "3.4" + "version": "3.4", + "clientAdRequestId": "a3b18c5b-6708-4bb7-b41b-40ed37d89561" } }, "mockResponse": { diff --git a/adapters/huaweiads/huaweiadstest/exemplary/bannerWrongMccmnc.json b/adapters/huaweiads/huaweiadstest/exemplary/bannerWrongMccmnc.json new file mode 100644 index 00000000000..52962e459ff --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/exemplary/bannerWrongMccmnc.json @@ -0,0 +1,269 @@ +{ + "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": "203-1", + "carrier": "carrier", + "connectiontype": 1 + }, + "app": { + "bundle": "com.example.p10", + "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://adx-dra.op.hicloud.com/ppsadx/getResult", + "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": "com.example.pkgname2", + "version": "1.11" + }, + "multislot": [ + { + "adtype": 8, + "slotid": "m8x9x3rzff", + "test": 1, + "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": "203", + "mnc": "1" + } + ], + "type": 1 + }, + "regs": { + }, + "version": "3.4", + "clientAdRequestId": "a3b18c5b-6708-4bb7-b41b-40ed37d89561" + } + }, + "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/interstitialBannerType.json b/adapters/huaweiads/huaweiadstest/exemplary/interstitialBannerType.json new file mode 100644 index 00000000000..be7366a48cb --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/exemplary/interstitialBannerType.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": "testslotid", + "adtype": "interstitial", + "publisherid": "123", + "signkey": "signkey", + "keyid": "41", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.example.p1", + "name": "Huawei Browser", + "ver": "9.1.0.301", + "content": { + "language": "fr" + } + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "fr", + "model": "COL-TEST", + "os": "android", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "ip", + "pxratio": 23.01, + "geo": { + "country": "FRA" + } + }, + "user": { + "ext": { + "data": { + "gaid": [ + "gaid" + ], + "oaid": [ + "oaid" + ], + "clientTime": [ + "2018-11-02 16:34:07.981+1300" + ] + } + } + }, + "regs": { + "coppa": 1 + }, + "ext": { + } + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://adx-dre.op.hicloud.com/ppsadx/getResult", + "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": "fr", + "country": "FR", + "name": "Huawei Browser", + "pkgname": "com.example.pkgname1", + "version": "9.1.0.301" + }, + "multislot": [ + { + "adtype": 12, + "slotid": "testslotid", + "test": 1, + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + } + ], + "device": { + "height": 1920, + "language": "fr", + "oaid": "oaid", + "os": "android", + "type": 4, + "ip": "ip", + "localeCountry": "FR", + "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": "FR" + }, + "geo": { + }, + "network": { + "type": 0 + }, + "regs": { + "coppa": 1 + }, + "version": "3.4", + "clientAdRequestId": "test-req-id" + } + }, + "mockResponse": { + "status": 200, + "body": { + "ctrlSwitchs": "0", + "dsp1cost": 61, + "dspcost": 108, + "multiad": [ + { + "adtype": 12, + "content": [ + { + "contentid": "58025103", + "creativetype": 2, + "endtime": 1621428898335, + "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 + } + ], + "landingPageType": "3" + }, + "monitor": [ + { + "eventType": "click", + "url": [ + "http://test/click" + ] + }, + { + "eventType": "imp", + "url": [ + "http://test/imp" + ] + } + ], + "price": 2.8, + "starttime": 1621267200000, + "taskid": "48017658" + } + ], + "retcode30": 200, + "slotid": "testslotid" + } + ], + "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/interstitialVideoType.json b/adapters/huaweiads/huaweiadstest/exemplary/interstitialVideoType.json new file mode 100644 index 00000000000..d48e8abf677 --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/exemplary/interstitialVideoType.json @@ -0,0 +1,324 @@ +{ + "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": "testslotid", + "adtype": "interstitial", + "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": "en", + "model": "COL-TEST", + "os": "android", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "ip", + "pxratio": 23.01, + "geo": { + "country": "ATA" + } + }, + "user": { + "ext": { + "data": { + "gaid": [ + "gaid" + ], + "oaid": [ + "oaid" + ], + "clientTime": [ + "2018-08-10 20:01:11.214+0200" + ] + } + } + }, + "ext": { + } + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://adx-dre.op.hicloud.com/ppsadx/getResult", + "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": "AT", + "name": "Huawei Browser", + "pkgname": "com.wavehk.android", + "version": "9.1.0.301" + }, + "multislot": [ + { + "adtype": 12, + "slotid": "testslotid", + "h": 250, + "w": 300, + "test": 1 + } + ], + "device": { + "height": 1920, + "language": "en", + "oaid": "oaid", + "os": "android", + "type": 4, + "ip": "ip", + "localeCountry": "AT", + "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": "AT" + }, + "geo": { + }, + "network": { + "type": 0 + }, + "regs": { + }, + "version": "3.4", + "clientAdRequestId": "test-req-id" + } + }, + "mockResponse": { + "status": 200, + "body": { + "multiad": [ + { + "adtype": 12, + "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://consumer.huawei.com/content/dam/huawei-cbg-site/ecommerce/ae/2022/may/watch-gt-3-pro/subscribe-phase/video/update/MKT_Odin_Frigga_PV_EN_30s%20Horizontal%20SHM.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" + ] + }, + { + "eventType": "playResume", + "url": [ + "http://test/playResume" + ] + }, + { + "eventType": "playPause", + "url": [ + "http://test/playPause" + ] + }, + { + "eventType": "appOpen", + "url": [ + "http://test/appOpen" + ] + } + ], + "price": 2.8, + "starttime": 1620230400000, + "taskid": "48016632" + } + ], + "retcode30": 200, + "slotid": "testslotid" + } + ], + "retcode": 200 + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "HuaweiAds/test/00:00:06.038 ", + "adomain": [ + "huaweiads" + ], + "h": 500, + "w": 600, + "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/exemplary/nativeIncludeVideo.json b/adapters/huaweiads/huaweiadstest/exemplary/nativeIncludeVideo.json index ae3efabcc85..8297d11b824 100644 --- a/adapters/huaweiads/huaweiadstest/exemplary/nativeIncludeVideo.json +++ b/adapters/huaweiads/huaweiadstest/exemplary/nativeIncludeVideo.json @@ -96,7 +96,7 @@ "903" ], "h": 200, - "test": 0, + "test": 1, "w": 200 } ], @@ -125,7 +125,8 @@ }, "regs": { }, - "version": "3.4" + "version": "3.4", + "clientAdRequestId": "test-req-id" } }, "mockResponse": { @@ -140,7 +141,7 @@ "contentid": "58022259", "creativetype": 106, "endtime": 1621344684645, - "interactiontype": 2, + "interactiontype": 1, "landingTitle": 1, "metaData": { "adSign": "2", diff --git a/adapters/huaweiads/huaweiadstest/exemplary/nativeSingleImage.json b/adapters/huaweiads/huaweiadstest/exemplary/nativeSingleImage.json index ff8e686c0b5..1af8abc5c0a 100644 --- a/adapters/huaweiads/huaweiadstest/exemplary/nativeSingleImage.json +++ b/adapters/huaweiads/huaweiadstest/exemplary/nativeSingleImage.json @@ -91,10 +91,10 @@ "adtype": 3, "slotid": "u42ohmaufh", "detailedCreativeTypeList": [ - "909" + "901" ], "h": 200, - "test": 0, + "test": 1, "w": 200 } ], @@ -125,7 +125,8 @@ }, "regs": { }, - "version": "3.4" + "version": "3.4", + "clientAdRequestId": "test-req-id" } }, "mockResponse": { @@ -143,7 +144,7 @@ "creativetype": 106, "ctrlSwitchs": "001011101001010212", "endtime": 1621344684645, - "interactiontype": 2, + "interactiontype": 1, "landingTitle": 1, "metaData": { "adSign": "2", diff --git a/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImage.json b/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImage.json index 22ac3556569..7b5c9a54f44 100644 --- a/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImage.json +++ b/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImage.json @@ -93,7 +93,7 @@ "904" ], "h": 200, - "test": 0, + "test": 1, "w": 200 } ], @@ -122,7 +122,8 @@ }, "regs": { }, - "version": "3.4" + "version": "3.4", + "clientAdRequestId": "test-req-id" } }, "mockResponse": { @@ -137,7 +138,7 @@ "contentid": "58022259", "creativetype": 106, "endtime": 1621344684645, - "interactiontype": 2, + "interactiontype": 1, "landingTitle": 1, "metaData": { "adSign": "2", diff --git a/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImageIncludeIcon.json b/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImageIncludeIcon.json index 18f03538c13..e72f5f593c1 100644 --- a/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImageIncludeIcon.json +++ b/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImageIncludeIcon.json @@ -94,7 +94,7 @@ "904" ], "h": 200, - "test": 0, + "test": 1, "w": 200 } ], @@ -123,7 +123,8 @@ }, "regs": { }, - "version": "3.4" + "version": "3.4", + "clientAdRequestId": "test-req-id" } }, "mockResponse": { @@ -138,7 +139,7 @@ "contentid": "58022259", "creativetype": 106, "endtime": 1621344684645, - "interactiontype": 2, + "interactiontype": 1, "landingTitle": 1, "metaData": { "adSign": "2", diff --git a/adapters/huaweiads/huaweiadstest/exemplary/rewardedVideo.json b/adapters/huaweiads/huaweiadstest/exemplary/rewardedVideo.json new file mode 100644 index 00000000000..77308a62ca0 --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/exemplary/rewardedVideo.json @@ -0,0 +1,324 @@ +{ + "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": "testslotid", + "adtype": "rewarded", + "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": "en", + "model": "COL-TEST", + "os": "android", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "ip", + "pxratio": 23.01, + "geo": { + "country": "ATA" + } + }, + "user": { + "ext": { + "data": { + "gaid": [ + "gaid" + ], + "oaid": [ + "oaid" + ], + "clientTime": [ + "2018-08-10 20:01:11.214+0200" + ] + } + } + }, + "ext": { + } + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://adx-dre.op.hicloud.com/ppsadx/getResult", + "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": "AT", + "name": "Huawei Browser", + "pkgname": "com.wavehk.android", + "version": "9.1.0.301" + }, + "multislot": [ + { + "adtype": 7, + "slotid": "testslotid", + "test": 1, + "w": 300, + "h": 250 + } + ], + "device": { + "height": 1920, + "language": "en", + "oaid": "oaid", + "os": "android", + "type": 4, + "ip": "ip", + "localeCountry": "AT", + "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": "AT" + }, + "geo": { + }, + "network": { + "type": 0 + }, + "regs": { + }, + "version": "3.4", + "clientAdRequestId": "test-req-id" + } + }, + "mockResponse": { + "status": 200, + "body": { + "multiad": [ + { + "adtype": 7, + "content": [ + { + "contentid": "58001445", + "creativetype": 106, + "endtime": 1621344684645, + "interactiontype": 1, + "landingTitle": 1, + "metaData": { + "adSign": "2", + "mediaFile": { + "mime": "video/mp4", + "width": 720, + "height": 1280, + "fileSize": 10000, + "url": "https://test.png", + "sha256": "" + }, + "apkInfo": { + "appIcon": "https://appimg.dbankcdn.com/application/icon144/678727f6687b4382ade1fa4cfc77e165.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://appimg.dbankcdn.com/application/icon144/678727f6687b4382ade1fa4cfc77e165.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://consumer.huawei.com/content/dam/huawei-cbg-site/ecommerce/ae/2022/may/watch-gt-3-pro/subscribe-phase/video/update/MKT_Odin_Frigga_PV_EN_30s%20Horizontal%20SHM.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" + ] + }, + { + "eventType": "playResume", + "url": [ + "http://test/playResume" + ] + }, + { + "eventType": "playPause", + "url": [ + "http://test/playPause" + ] + }, + { + "eventType": "appOpen", + "url": [ + "http://test/appOpen" + ] + } + ], + "price": 2.8, + "starttime": 1620230400000, + "taskid": "48016632" + } + ], + "retcode30": 200, + "slotid": "testslotid" + } + ], + "retcode": 200 + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "HuaweiAds/test/00:00:06.038 ", + "adomain": [ + "huaweiads" + ], + "h": 500, + "w": 600, + "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/exemplary/rewardedVideo1.json b/adapters/huaweiads/huaweiadstest/exemplary/rewardedVideo1.json new file mode 100644 index 00000000000..1f5b8b9b2da --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/exemplary/rewardedVideo1.json @@ -0,0 +1,315 @@ +{ + "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": "testslotid", + "adtype": "rewarded", + "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": "en", + "model": "COL-TEST", + "os": "android", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "ip", + "pxratio": 23.01, + "geo": { + "country": "ATA" + } + }, + "user": { + "ext": { + "data": { + "gaid": [ + "gaid" + ], + "oaid": [ + "oaid" + ], + "clientTime": [ + "2018-08-10 20:01:11.214+0200" + ] + } + } + }, + "ext": { + } + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://adx-dre.op.hicloud.com/ppsadx/getResult", + "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": "AT", + "name": "Huawei Browser", + "pkgname": "com.wavehk.android", + "version": "9.1.0.301" + }, + "multislot": [ + { + "adtype": 7, + "slotid": "testslotid", + "test": 1, + "w": 300, + "h": 250 + } + ], + "device": { + "height": 1920, + "language": "en", + "oaid": "oaid", + "os": "android", + "type": 4, + "ip": "ip", + "localeCountry": "AT", + "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": "AT" + }, + "geo": { + }, + "network": { + "type": 0 + }, + "regs": { + }, + "version": "3.4", + "clientAdRequestId": "test-req-id" + } + }, + "mockResponse": { + "status": 200, + "body": { + "multiad": [ + { + "adtype": 7, + "content": [ + { + "contentid": "58001445", + "creativetype": 106, + "endtime": 1621344684645, + "interactiontype": 1, + "landingTitle": 1, + "metaData": { + "adSign": "2", + "mediaFile": { + "mime": "video/mp4", + "width": 720, + "height": 1280, + "fileSize": 10000, + "url": "https://test.png", + "sha256": "" + }, + "apkInfo": { + "appIcon": "https://appimg.dbankcdn.com/application/icon144/678727f6687b4382ade1fa4cfc77e165.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": [ + ], + "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://consumer.huawei.com/content/dam/huawei-cbg-site/ecommerce/ae/2022/may/watch-gt-3-pro/subscribe-phase/video/update/MKT_Odin_Frigga_PV_EN_30s%20Horizontal%20SHM.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" + ] + }, + { + "eventType": "playResume", + "url": [ + "http://test/playResume" + ] + }, + { + "eventType": "playPause", + "url": [ + "http://test/playPause" + ] + }, + { + "eventType": "appOpen", + "url": [ + "http://test/appOpen" + ] + } + ], + "price": 2.8, + "starttime": 1620230400000, + "taskid": "48016632" + } + ], + "retcode30": 200, + "slotid": "testslotid" + } + ], + "retcode": 200 + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "HuaweiAds/test/00:00:06.038 ", + "adomain": [ + "huaweiads" + ], + "h": 500, + "w": 600, + "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/exemplary/rewardedVideo2.json b/adapters/huaweiads/huaweiadstest/exemplary/rewardedVideo2.json new file mode 100644 index 00000000000..61ff7fa0c60 --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/exemplary/rewardedVideo2.json @@ -0,0 +1,299 @@ +{ + "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": "testslotid", + "adtype": "rewarded", + "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": "en", + "model": "COL-TEST", + "os": "android", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "ip", + "pxratio": 23.01, + "geo": { + "country": "ATA" + } + }, + "user": { + "ext": { + "data": { + "gaid": [ + "gaid" + ], + "oaid": [ + "oaid" + ], + "clientTime": [ + "2018-08-10 20:01:11.214+0200" + ] + } + } + }, + "ext": { + } + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://adx-dre.op.hicloud.com/ppsadx/getResult", + "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": "AT", + "name": "Huawei Browser", + "pkgname": "com.wavehk.android", + "version": "9.1.0.301" + }, + "multislot": [ + { + "adtype": 7, + "slotid": "testslotid", + "test": 1, + "w": 300, + "h": 250 + } + ], + "device": { + "height": 1920, + "language": "en", + "oaid": "oaid", + "os": "android", + "type": 4, + "ip": "ip", + "localeCountry": "AT", + "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": "AT" + }, + "geo": { + }, + "network": { + "type": 0 + }, + "regs": { + }, + "version": "3.4", + "clientAdRequestId": "test-req-id" + } + }, + "mockResponse": { + "status": 200, + "body": { + "multiad": [ + { + "adtype": 7, + "content": [ + { + "contentid": "58001445", + "creativetype": 106, + "endtime": 1621344684645, + "interactiontype": 1, + "landingTitle": 1, + "metaData": { + "adSign": "2", + "mediaFile": { + "mime": "video/mp4", + "width": 720, + "height": 1280, + "fileSize": 10000, + "url": "https://test.png", + "sha256": "" + }, + "apkInfo": { + "appIcon": "https://appimg.dbankcdn.com/application/icon144/678727f6687b4382ade1fa4cfc77e165.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": [ + ], + "imageInfo": [ + ], + "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://consumer.huawei.com/content/dam/huawei-cbg-site/ecommerce/ae/2022/may/watch-gt-3-pro/subscribe-phase/video/update/MKT_Odin_Frigga_PV_EN_30s%20Horizontal%20SHM.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" + ] + }, + { + "eventType": "playResume", + "url": [ + "http://test/playResume" + ] + }, + { + "eventType": "playPause", + "url": [ + "http://test/playPause" + ] + }, + { + "eventType": "appOpen", + "url": [ + "http://test/appOpen" + ] + } + ], + "price": 2.8, + "starttime": 1620230400000, + "taskid": "48016632" + } + ], + "retcode30": 200, + "slotid": "testslotid" + } + ], + "retcode": 200 + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "HuaweiAds/test/00:00:06.038 ", + "adomain": [ + "huaweiads" + ], + "h": 500, + "w": 600, + "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/exemplary/rewardedVideo3.json b/adapters/huaweiads/huaweiadstest/exemplary/rewardedVideo3.json new file mode 100644 index 00000000000..4e72f089c4b --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/exemplary/rewardedVideo3.json @@ -0,0 +1,306 @@ +{ + "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": "testslotid", + "adtype": "rewarded", + "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": "en", + "model": "COL-TEST", + "os": "android", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "ip", + "pxratio": 23.01, + "geo": { + "country": "ATA" + } + }, + "user": { + "ext": { + "data": { + "gaid": [ + "gaid" + ], + "oaid": [ + "oaid" + ], + "clientTime": [ + "2018-08-10 20:01:11.214+0200" + ] + } + } + }, + "ext": { + } + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://adx-dre.op.hicloud.com/ppsadx/getResult", + "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": "AT", + "name": "Huawei Browser", + "pkgname": "com.wavehk.android", + "version": "9.1.0.301" + }, + "multislot": [ + { + "adtype": 7, + "slotid": "testslotid", + "test": 1, + "w": 300, + "h": 250 + } + ], + "device": { + "height": 1920, + "language": "en", + "oaid": "oaid", + "os": "android", + "type": 4, + "ip": "ip", + "localeCountry": "AT", + "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": "AT" + }, + "geo": { + }, + "network": { + "type": 0 + }, + "regs": { + }, + "version": "3.4", + "clientAdRequestId": "test-req-id" + } + }, + "mockResponse": { + "status": 200, + "body": { + "multiad": [ + { + "adtype": 7, + "content": [ + { + "contentid": "58001445", + "creativetype": 106, + "endtime": 1621344684645, + "interactiontype": 1, + "landingTitle": 1, + "metaData": { + "adSign": "2", + "mediaFile": { + "mime": "video/mp4", + "width": 720, + "height": 1280, + "fileSize": 10000, + "url": "https://test.png", + "sha256": "" + }, + "apkInfo": { + "appIcon": "https://appimg.dbankcdn.com/application/icon144/678727f6687b4382ade1fa4cfc77e165.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, + "imageType": "img", + "sha256": "042479eccbda9a8d7d3aa3da73c42486854407835623a30ffff875cb578242d0", + "url": "https://pps-icon.png" + } + ], + "imageInfo": [ + ], + "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://consumer.huawei.com/content/dam/huawei-cbg-site/ecommerce/ae/2022/may/watch-gt-3-pro/subscribe-phase/video/update/MKT_Odin_Frigga_PV_EN_30s%20Horizontal%20SHM.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" + ] + }, + { + "eventType": "playResume", + "url": [ + "http://test/playResume" + ] + }, + { + "eventType": "playPause", + "url": [ + "http://test/playPause" + ] + }, + { + "eventType": "appOpen", + "url": [ + "http://test/appOpen" + ] + } + ], + "price": 2.8, + "starttime": 1620230400000, + "taskid": "48016632" + } + ], + "retcode30": 200, + "slotid": "testslotid" + } + ], + "retcode": 200 + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "HuaweiAds/test/00:00:06.038 ", + "adomain": [ + "huaweiads" + ], + "h": 500, + "w": 600, + "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/exemplary/rewardedVideo4.json b/adapters/huaweiads/huaweiadstest/exemplary/rewardedVideo4.json new file mode 100644 index 00000000000..0045e5cd17e --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/exemplary/rewardedVideo4.json @@ -0,0 +1,305 @@ +{ + "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": "testslotid", + "adtype": "rewarded", + "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": "en", + "model": "COL-TEST", + "os": "android", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "ip", + "pxratio": 23.01, + "geo": { + "country": "ATA" + } + }, + "user": { + "ext": { + "data": { + "gaid": [ + "gaid" + ], + "oaid": [ + "oaid" + ], + "clientTime": [ + "2018-08-10 20:01:11.214+0200" + ] + } + } + }, + "ext": { + } + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://adx-dre.op.hicloud.com/ppsadx/getResult", + "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": "AT", + "name": "Huawei Browser", + "pkgname": "com.wavehk.android", + "version": "9.1.0.301" + }, + "multislot": [ + { + "adtype": 7, + "slotid": "testslotid", + "test": 1, + "w": 300, + "h": 250 + } + ], + "device": { + "height": 1920, + "language": "en", + "oaid": "oaid", + "os": "android", + "type": 4, + "ip": "ip", + "localeCountry": "AT", + "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": "AT" + }, + "geo": { + }, + "network": { + "type": 0 + }, + "regs": { + }, + "version": "3.4", + "clientAdRequestId": "test-req-id" + } + }, + "mockResponse": { + "status": 200, + "body": { + "multiad": [ + { + "adtype": 7, + "content": [ + { + "contentid": "58001445", + "creativetype": 106, + "endtime": 1621344684645, + "interactiontype": 1, + "landingTitle": 1, + "metaData": { + "adSign": "2", + "mediaFile": { + "mime": "video/mp4", + "width": 720, + "height": 1280, + "fileSize": 10000, + "url": "https://test.png", + "sha256": "" + }, + "apkInfo": { + "appIcon": "https://appimg.dbankcdn.com/application/icon144/678727f6687b4382ade1fa4cfc77e165.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": [ + ], + "imageInfo": [ + { + "checkSha256Flag": 0, + "imageType": "img", + "sha256": "8baa56fdb2702b9fb044d95b328936160cd245764375cdb25a4ab504f4ae2e19", + "url": "http://image1.jpg" + } + ], + "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://consumer.huawei.com/content/dam/huawei-cbg-site/ecommerce/ae/2022/may/watch-gt-3-pro/subscribe-phase/video/update/MKT_Odin_Frigga_PV_EN_30s%20Horizontal%20SHM.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" + ] + }, + { + "eventType": "playResume", + "url": [ + "http://test/playResume" + ] + }, + { + "eventType": "playPause", + "url": [ + "http://test/playPause" + ] + }, + { + "eventType": "appOpen", + "url": [ + "http://test/appOpen" + ] + } + ], + "price": 2.8, + "starttime": 1620230400000, + "taskid": "48016632" + } + ], + "retcode30": 200, + "slotid": "testslotid" + } + ], + "retcode": 200 + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "HuaweiAds/test/00:00:06.038 ", + "adomain": [ + "huaweiads" + ], + "h": 500, + "w": 600, + "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/exemplary/rollVideo.json b/adapters/huaweiads/huaweiadstest/exemplary/rollVideo.json new file mode 100644 index 00000000000..f0429670360 --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/exemplary/rollVideo.json @@ -0,0 +1,329 @@ +{ + "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, + "maxduration": 10 + }, + "ext": { + "bidder": { + "slotid": "testslotid", + "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": "en", + "model": "COL-TEST", + "os": "android", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "ip", + "pxratio": 23.01, + "geo": { + "country": "ATA" + } + }, + "user": { + "ext": { + "data": { + "gaid": [ + "gaid" + ], + "oaid": [ + "oaid" + ], + "clientTime": [ + "2018-08-10 20:01:11.214+0200" + ] + } + } + }, + "ext": { + } + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://adx-dre.op.hicloud.com/ppsadx/getResult", + "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": "AT", + "name": "Huawei Browser", + "pkgname": "com.wavehk.android", + "version": "9.1.0.301" + }, + "multislot": [ + { + "adtype": 60, + "slotid": "testslotid", + "totalDuration": 10, + "test": 1, + "w": 300, + "h": 250 + } + ], + "device": { + "height": 1920, + "language": "en", + "oaid": "oaid", + "os": "android", + "type": 4, + "ip": "ip", + "localeCountry": "AT", + "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": "AT" + }, + "geo": { + }, + "network": { + "type": 0 + }, + "regs": { + }, + "version": "3.4", + "clientAdRequestId": "test-req-id" + } + }, + "mockResponse": { + "status": 200, + "body": { + "multiad": [ + { + "adtype": 60, + "content": [ + { + "contentid": "58001445", + "creativetype": 106, + "endtime": 1621344684645, + "interactiontype": 3, + "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", + "intent": "https%3A%2F%2Fhibobi.app.link%2FK1sog7A40hb", + "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://consumer.huawei.com/content/dam/huawei-cbg-site/ecommerce/ae/2022/may/watch-gt-3-pro/subscribe-phase/video/update/MKT_Odin_Frigga_PV_EN_30s%20Horizontal%20SHM.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" + ] + } + ], + "price": 2.8, + "starttime": 1620230400000, + "taskid": "48016632" + } + ], + "retcode30": 200, + "slotid": "testslotid" + } + ], + "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/exemplary/video.json b/adapters/huaweiads/huaweiadstest/exemplary/video.json index 64c54d5dc87..acc572c721f 100644 --- a/adapters/huaweiads/huaweiadstest/exemplary/video.json +++ b/adapters/huaweiads/huaweiadstest/exemplary/video.json @@ -22,7 +22,7 @@ "ext": { "bidder": { "slotid": "m8x9x3roll", - "adtype": "roll", + "adtype": "interstitial", "publisherid": "123123123", "signkey": "signkey", "keyid": "41", @@ -98,9 +98,11 @@ }, "multislot": [ { - "adtype": 60, + "adtype": 12, "slotid": "m8x9x3roll", - "test": 0 + "test": 1, + "w": 300, + "h": 250 } ], "device": { @@ -128,7 +130,8 @@ }, "regs": { }, - "version": "3.4" + "version": "3.4", + "clientAdRequestId": "test-req-id" } }, "mockResponse": { @@ -143,7 +146,7 @@ "contentid": "58001445", "creativetype": 106, "endtime": 1621344684645, - "interactiontype": 2, + "interactiontype": 1, "landingTitle": 1, "metaData": { "adSign": "2", diff --git a/adapters/huaweiads/huaweiadstest/supplemental/adtype_roll_missing_duration.json b/adapters/huaweiads/huaweiadstest/supplemental/adtype_roll_missing_duration.json index 9f41a7bc3fa..259589ee8db 100644 --- a/adapters/huaweiads/huaweiadstest/supplemental/adtype_roll_missing_duration.json +++ b/adapters/huaweiads/huaweiadstest/supplemental/adtype_roll_missing_duration.json @@ -4,6 +4,8 @@ "imp": [ { "id": "test-imp-id", + "video": { + }, "ext": { "bidder": { "slotid": "m8x9x3rzff", @@ -61,7 +63,7 @@ ], "expectedMakeRequestsErrors": [ { - "value": "GetHuaweiAdsReqAdslot30: MaxDuration is empty when adtype is roll.", + "value": "extract openrtb video failed: MaxDuration is empty when huaweiads adtype is roll.", "comparison": "literal" } ] diff --git a/adapters/huaweiads/huaweiadstest/supplemental/bad_request_incorrect_huawei_adtype1.json b/adapters/huaweiads/huaweiadstest/supplemental/bad_request_incorrect_huawei_adtype1.json new file mode 100644 index 00000000000..8c7a4a5355c --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/supplemental/bad_request_incorrect_huawei_adtype1.json @@ -0,0 +1,84 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "slotid": "testslotid", + "adtype": "native", + "publisherid": "123", + "signkey": "signkey", + "keyid": "41", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.example.p1", + "name": "Huawei Browser", + "ver": "9.1.0.301", + "content": { + "language": "zh" + } + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "zh", + "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": [ + ], + "expectedBidResponses": [ + ], + "expectedMakeRequestsErrors": [ + { + "value": "check openrtb format: request has banner, doesn't correspond to huawei adtype native", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/huaweiads/huaweiadstest/supplemental/bad_request_incorrect_huawei_adtype2.json b/adapters/huaweiads/huaweiadstest/supplemental/bad_request_incorrect_huawei_adtype2.json new file mode 100644 index 00000000000..2f3633631c0 --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/supplemental/bad_request_incorrect_huawei_adtype2.json @@ -0,0 +1,78 @@ +{ + "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": "testslotid", + "adtype": "banner", + "publisherid": "123", + "signkey": "signkey", + "keyid": "41", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.example.p1", + "name": "Huawei Browser", + "ver": "9.1.0.301", + "content": { + "language": "zh" + } + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "zh", + "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": [ + ], + "expectedBidResponses": [ + ], + "expectedMakeRequestsErrors": [ + { + "value": "check openrtb format: request has native, doesn't correspond to huawei adtype banner", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/huaweiads/huaweiadstest/supplemental/bad_request_incorrect_huawei_adtype3.json b/adapters/huaweiads/huaweiadstest/supplemental/bad_request_incorrect_huawei_adtype3.json new file mode 100644 index 00000000000..081b14bb061 --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/supplemental/bad_request_incorrect_huawei_adtype3.json @@ -0,0 +1,89 @@ +{ + "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": "testslotid", + "adtype": "native", + "publisherid": "123", + "signkey": "signkey", + "keyid": "41", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.example.p1", + "name": "Huawei Browser", + "ver": "9.1.0.301", + "content": { + "language": "zh" + } + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "zh", + "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": [ + ], + "expectedBidResponses": [ + ], + "expectedMakeRequestsErrors": [ + { + "value": "check openrtb format: request has video, doesn't correspond to huawei adtype native", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/huaweiads/huaweiadstest/supplemental/bad_request_missing_all_type.json b/adapters/huaweiads/huaweiadstest/supplemental/bad_request_missing_all_type.json new file mode 100644 index 00000000000..2eba92087ea --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/supplemental/bad_request_missing_all_type.json @@ -0,0 +1,74 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "ext": { + "bidder": { + "slotid": "testslotid", + "adtype": "banner", + "publisherid": "123", + "signkey": "signkey", + "keyid": "41", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.example.p1", + "name": "Huawei Browser", + "ver": "9.1.0.301", + "content": { + "language": "zh" + } + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "zh", + "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": [ + ], + "expectedBidResponses": [ + ], + "expectedMakeRequestsErrors": [ + { + "value": "check openrtb format: please choose one of our supported type banner, native, or video", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/huaweiads/huaweiadstest/supplemental/bad_request_no_support_audio.json b/adapters/huaweiads/huaweiadstest/supplemental/bad_request_no_support_audio.json new file mode 100644 index 00000000000..1392ac97b43 --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/supplemental/bad_request_no_support_audio.json @@ -0,0 +1,76 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "audio": { + }, + "ext": { + "bidder": { + "slotid": "testslotid", + "adtype": "banner", + "publisherid": "123", + "signkey": "signkey", + "keyid": "41", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.example.p1", + "name": "Huawei Browser", + "ver": "9.1.0.301", + "content": { + "language": "zh" + } + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "zh", + "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": [ + ], + "expectedBidResponses": [ + ], + "expectedMakeRequestsErrors": [ + { + "value": "check openrtb format: request has audio, not currently supported", + "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 index 327004f90ea..92f5e8b65f6 100644 --- a/adapters/huaweiads/huaweiadstest/supplemental/bad_response.json +++ b/adapters/huaweiads/huaweiadstest/supplemental/bad_response.json @@ -96,7 +96,7 @@ { "adtype": 8, "slotid": "m8x9x3rzff", - "test": 0, + "test": 1, "format": [ { "h": 250, @@ -132,7 +132,8 @@ }, "regs": { }, - "version": "3.4" + "version": "3.4", + "clientAdRequestId": "test-req-id" } }, "mockResponse": { @@ -221,7 +222,7 @@ "expectedMakeRequestsErrors": [], "expectedMakeBidsErrors": [ { - "value": "convertHuaweiAdsResp2BidderResp: multiad length is 0, get no ads from huawei side.", + "value": "convert huaweiads response to bidder response failed: multiad length is 0, get no ads from huawei side.", "comparison": "literal" } ] diff --git a/adapters/huaweiads/huaweiadstest/supplemental/bad_response_400.json b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_400.json index e06af96adae..b860e57c26d 100644 --- a/adapters/huaweiads/huaweiadstest/supplemental/bad_response_400.json +++ b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_400.json @@ -96,7 +96,7 @@ { "adtype": 8, "slotid": "m8x9x3rzff", - "test": 0, + "test": 1, "format": [ { "h": 250, @@ -132,7 +132,8 @@ }, "regs": { }, - "version": "3.4" + "version": "3.4", + "clientAdRequestId": "test-req-id" } }, "mockResponse": { diff --git a/adapters/huaweiads/huaweiadstest/supplemental/bad_response_503.json b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_503.json index 53b05eba0f9..8261a13fff4 100644 --- a/adapters/huaweiads/huaweiadstest/supplemental/bad_response_503.json +++ b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_503.json @@ -96,7 +96,7 @@ { "adtype": 8, "slotid": "m8x9x3rzff", - "test": 0, + "test": 1, "format": [ { "h": 250, @@ -132,7 +132,8 @@ }, "regs": { }, - "version": "3.4" + "version": "3.4", + "clientAdRequestId": "test-req-id" } }, "mockResponse": { diff --git a/adapters/huaweiads/huaweiadstest/supplemental/bad_response_dont_find_impid.json b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_dont_find_impid.json index c2cc3424b58..e94e5fb716d 100644 --- a/adapters/huaweiads/huaweiadstest/supplemental/bad_response_dont_find_impid.json +++ b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_dont_find_impid.json @@ -119,7 +119,7 @@ { "adtype": 8, "slotid": "m8x9x3rzf1", - "test": 0, + "test": 1, "format": [ { "h": 250, @@ -132,7 +132,7 @@ { "adtype": 8, "slotid": "m8x9x3rzf2", - "test": 0, + "test": 1, "format": [ { "h": 250, @@ -168,7 +168,8 @@ }, "regs": { }, - "version": "3.4" + "version": "3.4", + "clientAdRequestId": "test-req-id" } }, "mockResponse": { diff --git a/adapters/huaweiads/huaweiadstest/supplemental/bad_response_incorrect_huawei_adtype.json b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_incorrect_huawei_adtype.json new file mode 100644 index 00000000000..a0a93bda5e1 --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_incorrect_huawei_adtype.json @@ -0,0 +1,227 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "slotid": "testslotid", + "adtype": "banner", + "publisherid": "123", + "signkey": "signkey", + "keyid": "41", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.example.p1", + "name": "Huawei Browser", + "ver": "9.1.0.301", + "content": { + "language": "zh" + } + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "zh", + "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://acd.op.hicloud.com/ppsadx/getResult", + "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.example.pkgname1", + "version": "9.1.0.301" + }, + "multislot": [ + { + "adtype": 8, + "slotid": "testslotid", + "test": 1, + "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", + "clientAdRequestId": "test-req-id" + } + }, + "mockResponse": { + "status": 200, + "body": { + "multiad": [ + { + "adtype": 3, + "content": [ + { + "contentid": "58025103", + "creativetype": 2, + "endtime": 1621428898335, + "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 + } + ], + "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": "", + "secondUrl": "", + "versionCode": "284", + "versionName": "9.6.1.9" + }, + "label": "Banner_API", + "landingPageType": "3" + }, + "monitor": [ + { + "eventType": "click", + "url": [ + "http://test/click1" + ] + }, + { + "eventType": "imp", + "url": [ + "http://test/imp1" + ] + } + ], + "price": 2.8, + "starttime": 1621267200000, + "taskid": "48017658" + } + ], + "retcode30": 200, + "slotid": "testslotid" + } + ], + "retcode": 200 + } + } + } + ], + "expectedBidResponses": [ + ], + "expectedMakeRequestsErrors": [], + "expectedMakeBidsErrors": [ + { + "value": "generate Adm field from HuaweiAds response failed: openrtb banner should correspond to huaweiads adtype: banner or interstitial", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/huaweiads/huaweiadstest/supplemental/bad_response_not_intent.json b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_not_intent.json new file mode 100644 index 00000000000..d801946d661 --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_not_intent.json @@ -0,0 +1,227 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "slotid": "testslotid", + "adtype": "banner", + "publisherid": "123", + "signkey": "signkey", + "keyid": "41", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.example.p1", + "name": "Huawei Browser", + "ver": "9.1.0.301", + "content": { + "language": "zh" + } + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "zh", + "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://acd.op.hicloud.com/ppsadx/getResult", + "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.example.pkgname1", + "version": "9.1.0.301" + }, + "multislot": [ + { + "adtype": 8, + "slotid": "testslotid", + "test": 1, + "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", + "clientAdRequestId": "test-req-id" + } + }, + "mockResponse": { + "status": 200, + "body": { + "multiad": [ + { + "adtype": 8, + "content": [ + { + "contentid": "58025103", + "creativetype": 2, + "endtime": 1621428898335, + "interactiontype": 3, + "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 + } + ], + "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", + "secondUrl": "", + "versionCode": "284", + "versionName": "9.6.1.9" + }, + "label": "Banner_API", + "landingPageType": "3" + }, + "monitor": [ + { + "eventType": "click", + "url": [ + "http://test/click1" + ] + }, + { + "eventType": "imp", + "url": [ + "http://test/imp1" + ] + } + ], + "price": 2.8, + "starttime": 1621267200000, + "taskid": "48017658" + } + ], + "retcode30": 200, + "slotid": "testslotid" + } + ], + "retcode": 200 + } + } + } + ], + "expectedBidResponses": [ + ], + "expectedMakeRequestsErrors": [], + "expectedMakeBidsErrors": [ + { + "value": "generate Adm field from HuaweiAds response failed: content.MetaData.Intent in huaweiads resopnse is empty when interactiontype is appPromotion", + "comparison": "literal" + } + ] +} \ 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 index f4e5457c938..c38bc9f69b9 100644 --- a/adapters/huaweiads/huaweiadstest/supplemental/bad_response_not_native.json +++ b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_not_native.json @@ -11,7 +11,7 @@ "ext": { "bidder": { "slotid": "m8x9x3rzff", - "adtype": "banner", + "adtype": "native", "publisherid": "123", "signkey": "signkey", "keyid": "41", @@ -88,9 +88,9 @@ }, "multislot": [ { - "adtype": 8, + "adtype": 3, "slotid": "m8x9x3rzff", - "test": 0, + "test": 1, "detailedCreativeTypeList": [ "903" ], @@ -123,7 +123,8 @@ }, "regs": { }, - "version": "3.4" + "version": "3.4", + "clientAdRequestId": "test-req-id" } }, "mockResponse": { @@ -212,7 +213,7 @@ "expectedMakeRequestsErrors": [], "expectedMakeBidsErrors": [ { - "value": "getAdmFromHuaweiAdsContent failed: extractAdmNative: response is not a native ad", + "value": "generate Adm field from HuaweiAds response failed: extract Adm for Native ad: huaweiads response is not a native ad", "comparison": "literal" } ] diff --git a/adapters/huaweiads/huaweiadstest/supplemental/bad_response_retcode30_204.json b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_retcode30_204.json index adb456cb8da..466209d967e 100644 --- a/adapters/huaweiads/huaweiadstest/supplemental/bad_response_retcode30_204.json +++ b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_retcode30_204.json @@ -96,7 +96,7 @@ { "adtype": 8, "slotid": "m8x9x3rzff", - "test": 0, + "test": 1, "format": [ { "h": 250, @@ -132,7 +132,8 @@ }, "regs": { }, - "version": "3.4" + "version": "3.4", + "clientAdRequestId": "test-req-id" } }, "mockResponse": { diff --git a/adapters/huaweiads/huaweiadstest/supplemental/bad_response_retcode_210.json b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_retcode_210.json index 4d4cc06287c..4d9543d259a 100644 --- a/adapters/huaweiads/huaweiadstest/supplemental/bad_response_retcode_210.json +++ b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_retcode_210.json @@ -96,7 +96,7 @@ { "adtype": 8, "slotid": "m8x9x3rzff", - "test": 0, + "test": 1, "format": [ { "h": 250, @@ -132,7 +132,8 @@ }, "regs": { }, - "version": "3.4" + "version": "3.4", + "clientAdRequestId": "test-req-id" } }, "mockResponse": { diff --git a/adapters/huaweiads/huaweiadstest/supplemental/bad_response_retcode_408.json b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_retcode_408.json index e8778e46bb3..c7cc4d0534a 100644 --- a/adapters/huaweiads/huaweiadstest/supplemental/bad_response_retcode_408.json +++ b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_retcode_408.json @@ -96,7 +96,7 @@ { "adtype": 8, "slotid": "m8x9x3rzff", - "test": 0, + "test": 1, "format": [ { "h": 250, @@ -132,7 +132,8 @@ }, "regs": { }, - "version": "3.4" + "version": "3.4", + "clientAdRequestId": "test-req-id" } }, "mockResponse": { diff --git a/adapters/huaweiads/huaweiadstest/supplemental/bad_response_retcode_500.json b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_retcode_500.json index cd6440d1789..f0fdeb665e2 100644 --- a/adapters/huaweiads/huaweiadstest/supplemental/bad_response_retcode_500.json +++ b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_retcode_500.json @@ -96,7 +96,7 @@ { "adtype": 8, "slotid": "m8x9x3rzff", - "test": 0, + "test": 1, "format": [ { "h": 250, @@ -132,7 +132,8 @@ }, "regs": { }, - "version": "3.4" + "version": "3.4", + "clientAdRequestId": "test-req-id" } }, "mockResponse": { diff --git a/adapters/huaweiads/huaweiadstest/supplemental/missing_deviceid1.json b/adapters/huaweiads/huaweiadstest/supplemental/missing_deviceid1.json new file mode 100644 index 00000000000..170132be35a --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/supplemental/missing_deviceid1.json @@ -0,0 +1,66 @@ +{ + "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": { + } + }, + "httpcalls": [ + ], + "expectedBidResponses": [ + ], + "expectedMakeRequestsErrors": [ + { + "value": "getDeviceID: openRTBRequest.User.Ext is nil and device.Gaid is not specified.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/huaweiads/huaweiadstest/supplemental/missing_deviceid2.json b/adapters/huaweiads/huaweiadstest/supplemental/missing_deviceid2.json new file mode 100644 index 00000000000..133282239cf --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/supplemental/missing_deviceid2.json @@ -0,0 +1,75 @@ +{ + "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": "ios", + "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": "get gaid from openrtb Device.IFA failed, and get device id failed: Unmarshal openRTBRequest.User.Ext -> extUserDataHuaweiAds. Error: json: cannot unmarshal object into Go struct field ExtUserDataDeviceIdHuaweiAds.data.gaid of type []string", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/huaweiads/huaweiadstest/supplemental/missing_deviceid3.json b/adapters/huaweiads/huaweiadstest/supplemental/missing_deviceid3.json new file mode 100644 index 00000000000..b1c4396f17a --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/supplemental/missing_deviceid3.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": "ios", + "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_native_request.json b/adapters/huaweiads/huaweiadstest/supplemental/missing_native_request.json index d195f3fb59f..19814aebf0e 100644 --- a/adapters/huaweiads/huaweiadstest/supplemental/missing_native_request.json +++ b/adapters/huaweiads/huaweiadstest/supplemental/missing_native_request.json @@ -67,7 +67,7 @@ "expectedBidResponses": [ ], "expectedMakeRequestsErrors": [{ - "value": "extractAdmNative: imp.Native.Request is empty", + "value": "extract openrtb native failed: imp.Native.Request is empty", "comparison": "literal" }] } \ No newline at end of file diff --git a/adapters/huaweiads/mcc_list.go b/adapters/huaweiads/mcc_list.go new file mode 100644 index 00000000000..fff92013d4c --- /dev/null +++ b/adapters/huaweiads/mcc_list.go @@ -0,0 +1,243 @@ +package huaweiads + +var MccList = map[int]string{ + 202: "gr", //Greece + 204: "nl", //Netherlands (Kingdom of the) + 206: "be", //Belgium + 208: "fr", //France + 212: "mc", //Monaco (Principality of) + 213: "ad", //Andorra (Principality of) + 214: "es", //Spain + 216: "hu", //Hungary (Republic of) + 218: "ba", //Bosnia and Herzegovina + 219: "hr", //Croatia (Republic of) + 220: "rs", //Serbia and Montenegro + 222: "it", //Italy + 225: "va", //Vatican City State + 226: "ro", //Romania + 228: "ch", //Switzerland (Confederation of) + 230: "cz", //Czech Republic + 231: "sk", //Slovak Republic + 232: "at", //Austria + 234: "gb", //United Kingdom of Great Britain and Northern Ireland + 235: "gb", //United Kingdom of Great Britain and Northern Ireland + 238: "dk", //Denmark + 240: "se", //Sweden + 242: "no", //Norway + 244: "fi", //Finland + 246: "lt", //Lithuania (Republic of) + 247: "lv", //Latvia (Republic of) + 248: "ee", //Estonia (Republic of) + 250: "ru", //Russian Federation + 255: "ua", //Ukraine + 257: "by", //Belarus (Republic of) + 259: "md", //Moldova (Republic of) + 260: "pl", //Poland (Republic of) + 262: "de", //Germany (Federal Republic of) + 266: "gi", //Gibraltar + 268: "pt", //Portugal + 270: "lu", //Luxembourg + 272: "ie", //Ireland + 274: "is", //Iceland + 276: "al", //Albania (Republic of) + 278: "mt", //Malta + 280: "cy", //Cyprus (Republic of) + 282: "ge", //Georgia + 283: "am", //Armenia (Republic of) + 284: "bg", //Bulgaria (Republic of) + 286: "tr", //Turkey + 288: "fo", //Faroe Islands + 289: "ge", //Abkhazia (Georgia) + 290: "gl", //Greenland (Denmark) + 292: "sm", //San Marino (Republic of) + 293: "si", //Slovenia (Republic of) + 294: "mk", //The Former Yugoslav Republic of Macedonia + 295: "li", //Liechtenstein (Principality of) + 297: "me", //Montenegro (Republic of) + 302: "ca", //Canada + 308: "pm", //Saint Pierre and Miquelon (Collectivit territoriale de la Rpublique franaise) + 310: "us", //United States of America + 311: "us", //United States of America + 312: "us", //United States of America + 313: "us", //United States of America + 314: "us", //United States of America + 315: "us", //United States of America + 316: "us", //United States of America + 330: "pr", //Puerto Rico + 332: "vi", //United States Virgin Islands + 334: "mx", //Mexico + 338: "jm", //Jamaica + 340: "gp", //Guadeloupe (French Department of) + 342: "bb", //Barbados + 344: "ag", //Antigua and Barbuda + 346: "ky", //Cayman Islands + 348: "vg", //British Virgin Islands + 350: "bm", //Bermuda + 352: "gd", //Grenada + 354: "ms", //Montserrat + 356: "kn", //Saint Kitts and Nevis + 358: "lc", //Saint Lucia + 360: "vc", //Saint Vincent and the Grenadines + 362: "ai", //Netherlands Antilles + 363: "aw", //Aruba + 364: "bs", //Bahamas (Commonwealth of the) + 365: "ai", //Anguilla + 366: "dm", //Dominica (Commonwealth of) + 368: "cu", //Cuba + 370: "do", //Dominican Republic + 372: "ht", //Haiti (Republic of) + 374: "tt", //Trinidad and Tobago + 376: "tc", //Turks and Caicos Islands + 400: "az", //Azerbaijani Republic + 401: "kz", //Kazakhstan (Republic of) + 402: "bt", //Bhutan (Kingdom of) + 404: "in", //India (Republic of) + 405: "in", //India (Republic of) + 406: "in", //India (Republic of) + 410: "pk", //Pakistan (Islamic Republic of) + 412: "af", //Afghanistan + 413: "lk", //Sri Lanka (Democratic Socialist Republic of) + 414: "mm", //Myanmar (Union of) + 415: "lb", //Lebanon + 416: "jo", //Jordan (Hashemite Kingdom of) + 417: "sy", //Syrian Arab Republic + 418: "iq", //Iraq (Republic of) + 419: "kw", //Kuwait (State of) + 420: "sa", //Saudi Arabia (Kingdom of) + 421: "ye", //Yemen (Republic of) + 422: "om", //Oman (Sultanate of) + 423: "ps", //Palestine + 424: "ae", //United Arab Emirates + 425: "il", //Israel (State of) + 426: "bh", //Bahrain (Kingdom of) + 427: "qa", //Qatar (State of) + 428: "mn", //Mongolia + 429: "np", //Nepal + 430: "ae", //United Arab Emirates + 431: "ae", //United Arab Emirates + 432: "ir", //Iran (Islamic Republic of) + 434: "uz", //Uzbekistan (Republic of) + 436: "tj", //Tajikistan (Republic of) + 437: "kg", //Kyrgyz Republic + 438: "tm", //Turkmenistan + 440: "jp", //Japan + 441: "jp", //Japan + 450: "kr", //Korea (Republic of) + 452: "vn", //Viet Nam (Socialist Republic of) + 454: "hk", //"Hong Kong, China" + 455: "mo", //"Macao, China" + 456: "kh", //Cambodia (Kingdom of) + 457: "la", //Lao People's Democratic Republic + 460: "cn", //China (People's Republic of) + 461: "cn", //China (People's Republic of) + 466: "tw", //"Taiwan, China" + 467: "kp", //Democratic People's Republic of Korea + 470: "bd", //Bangladesh (People's Republic of) + 472: "mv", //Maldives (Republic of) + 502: "my", //Malaysia + 505: "au", //Australia + 510: "id", //Indonesia (Republic of) + 514: "tl", //Democratic Republic of Timor-Leste + 515: "ph", //Philippines (Republic of the) + 520: "th", //Thailand + 525: "sg", //Singapore (Republic of) + 528: "bn", //Brunei Darussalam + 530: "nz", //New Zealand + 534: "mp", //Northern Mariana Islands (Commonwealth of the) + 535: "gu", //Guam + 536: "nr", //Nauru (Republic of) + 537: "pg", //Papua New Guinea + 539: "to", //Tonga (Kingdom of) + 540: "sb", //Solomon Islands + 541: "vu", //Vanuatu (Republic of) + 542: "fj", //Fiji (Republic of) + 543: "wf", //Wallis and Futuna (Territoire franais d'outre-mer) + 544: "as", //American Samoa + 545: "ki", //Kiribati (Republic of) + 546: "nc", //New Caledonia (Territoire franais d'outre-mer) + 547: "pf", //French Polynesia (Territoire franais d'outre-mer) + 548: "ck", //Cook Islands + 549: "ws", //Samoa (Independent State of) + 550: "fm", //Micronesia (Federated States of) + 551: "mh", //Marshall Islands (Republic of the) + 552: "pw", //Palau (Republic of) + 553: "tv", //Tuvalu + 555: "nu", //Niue + 602: "eg", //Egypt (Arab Republic of) + 603: "dz", //Algeria (People's Democratic Republic of) + 604: "ma", //Morocco (Kingdom of) + 605: "tn", //Tunisia + 606: "ly", //Libya (Socialist People's Libyan Arab Jamahiriya) + 607: "gm", //Gambia (Republic of the) + 608: "sn", //Senegal (Republic of) + 609: "mr", //Mauritania (Islamic Republic of) + 610: "ml", //Mali (Republic of) + 611: "gn", //Guinea (Republic of) + 612: "ci", //Côte d'Ivoire (Republic of) + 613: "bf", //Burkina Faso + 614: "ne", //Niger (Republic of the) + 615: "tg", //Togolese Republic + 616: "bj", //Benin (Republic of) + 617: "mu", //Mauritius (Republic of) + 618: "lr", //Liberia (Republic of) + 619: "sl", //Sierra Leone + 620: "gh", //Ghana + 621: "ng", //Nigeria (Federal Republic of) + 622: "td", //Chad (Republic of) + 623: "cf", //Central African Republic + 624: "cm", //Cameroon (Republic of) + 625: "cv", //Cape Verde (Republic of) + 626: "st", //Sao Tome and Principe (Democratic Republic of) + 627: "gq", //Equatorial Guinea (Republic of) + 628: "ga", //Gabonese Republic + 629: "cg", //Congo (Republic of the) + 630: "cg", //Democratic Republic of the Congo + 631: "ao", //Angola (Republic of) + 632: "gw", //Guinea-Bissau (Republic of) + 633: "sc", //Seychelles (Republic of) + 634: "sd", //Sudan (Republic of the) + 635: "rw", //Rwanda (Republic of) + 636: "et", //Ethiopia (Federal Democratic Republic of) + 637: "so", //Somali Democratic Republic + 638: "dj", //Djibouti (Republic of) + 639: "ke", //Kenya (Republic of) + 640: "tz", //Tanzania (United Republic of) + 641: "ug", //Uganda (Republic of) + 642: "bi", //Burundi (Republic of) + 643: "mz", //Mozambique (Republic of) + 645: "zm", //Zambia (Republic of) + 646: "mg", //Madagascar (Republic of) + 647: "re", //Reunion (French Department of) + 648: "zw", //Zimbabwe (Republic of) + 649: "na", //Namibia (Republic of) + 650: "mw", //Malawi + 651: "ls", //Lesotho (Kingdom of) + 652: "bw", //Botswana (Republic of) + 653: "sz", //Swaziland (Kingdom of) + 654: "km", //Comoros (Union of the) + 655: "za", //South Africa (Republic of) + 657: "er", //Eritrea + 658: "sh", //Saint Helena, Ascension and Tristan da Cunha + 659: "ss", //South Sudan (Republic of) + 702: "bz", //Belize + 704: "gt", //Guatemala (Republic of) + 706: "sv", //El Salvador (Republic of) + 708: "hn", //Honduras (Republic of) + 710: "ni", //Nicaragua + 712: "cr", //Costa Rica + 714: "pa", //Panama (Republic of) + 716: "pe", //Peru + 722: "ar", //Argentine Republic + 724: "br", //Brazil (Federative Republic of) + 730: "cl", //Chile + 732: "co", //Colombia (Republic of) + 734: "ve", //Venezuela (Bolivarian Republic of) + 736: "bo", //Bolivia (Republic of) + 738: "gy", //Guyana + 740: "ec", //Ecuador + 742: "gf", //French Guiana (French Department of) + 744: "py", //Paraguay (Republic of) + 746: "sr", //Suriname (Republic of) + 748: "uy", //Uruguay (Eastern Republic of) + 750: "fk", //Falkland Islands (Malvinas) +} diff --git a/adapters/impactify/impactify.go b/adapters/impactify/impactify.go index 5d64be0351c..8204eb69190 100644 --- a/adapters/impactify/impactify.go +++ b/adapters/impactify/impactify.go @@ -3,13 +3,15 @@ package impactify import ( "encoding/json" "fmt" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "net/http" + "strings" + + "github.com/prebid/openrtb/v17/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 { @@ -175,7 +177,7 @@ func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, } // 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) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &adapter{ endpoint: config.Endpoint, } diff --git a/adapters/impactify/impactify_test.go b/adapters/impactify/impactify_test.go index a01a94b0322..550ea7ad498 100644 --- a/adapters/impactify/impactify_test.go +++ b/adapters/impactify/impactify_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderImpactify, config.Adapter{ - Endpoint: "https://sonic.impactify.media/bidder"}) + Endpoint: "https://sonic.impactify.media/bidder"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/improvedigital/improvedigital.go b/adapters/improvedigital/improvedigital.go index 3d4520e4ba1..27cf56ae3f9 100644 --- a/adapters/improvedigital/improvedigital.go +++ b/adapters/improvedigital/improvedigital.go @@ -4,19 +4,37 @@ import ( "encoding/json" "fmt" "net/http" + "strconv" "strings" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/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" ) +const ( + buyingTypeRTB = "rtb" + isRewardedInventory = "is_rewarded_inventory" + stateRewardedInventoryEnable = "1" + consentProvidersSettingsInputKey = "ConsentedProvidersSettings" + consentProvidersSettingsOutKey = "consented_providers_settings" + consentedProvidersKey = "consented_providers" +) + type ImprovedigitalAdapter struct { endpoint string } +// BidExt represents Improved Digital bid extension with line item ID and buying type values +type BidExt struct { + Improvedigital struct { + LineItemID int `json:"line_item_id"` + BuyingType string `json:"buying_type"` + } +} + // 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) { numRequests := len(request.Imp) @@ -36,6 +54,15 @@ func (a *ImprovedigitalAdapter) MakeRequests(request *openrtb2.BidRequest, reqIn } func (a *ImprovedigitalAdapter) makeRequest(request openrtb2.BidRequest, imp openrtb2.Imp) (*adapters.RequestData, error) { + // Handle Rewarded Inventory + impExt, err := getImpExtWithRewardedInventory(imp) + if err != nil { + return nil, err + } + if impExt != nil { + imp.Ext = impExt + } + request.Imp = []openrtb2.Imp{imp} userExtAddtlConsent, err := a.getAdditionalConsentProvidersUserExt(request) @@ -114,6 +141,19 @@ func (a *ImprovedigitalAdapter) MakeBids(internalRequest *openrtb2.BidRequest, e return nil, []error{err} } + if bid.Ext != nil { + var bidExt BidExt + err = json.Unmarshal(bid.Ext, &bidExt) + if err != nil { + return nil, []error{err} + } + + bidExtImprovedigital := bidExt.Improvedigital + if bidExtImprovedigital.LineItemID != 0 && bidExtImprovedigital.BuyingType != "" && bidExtImprovedigital.BuyingType != buyingTypeRTB { + bid.DealID = strconv.Itoa(bidExtImprovedigital.LineItemID) + } + } + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ Bid: &bid, BidType: bidType, @@ -124,7 +164,7 @@ func (a *ImprovedigitalAdapter) MakeBids(internalRequest *openrtb2.BidRequest, e } // Builder builds a new instance of the Improvedigital adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &ImprovedigitalAdapter{ endpoint: config.Endpoint, } @@ -160,12 +200,6 @@ func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, // This method responsible to clone request and convert additional consent providers string to array when additional consent provider found func (a *ImprovedigitalAdapter) getAdditionalConsentProvidersUserExt(request openrtb2.BidRequest) ([]byte, error) { - const ( - consentProvidersSettingsInputKey = "ConsentedProvidersSettings" - consentProvidersSettingsOutKey = "consented_providers_settings" - consentedProvidersKey = "consented_providers" - ) - var cpStr string // If user/user.ext not defined, no need to parse additional consent @@ -222,3 +256,32 @@ func (a *ImprovedigitalAdapter) getAdditionalConsentProvidersUserExt(request ope return extJson, nil } + +func getImpExtWithRewardedInventory(imp openrtb2.Imp) ([]byte, error) { + var ext = make(map[string]json.RawMessage) + if err := json.Unmarshal(imp.Ext, &ext); err != nil { + return nil, err + } + + prebidJSONValue, prebidJSONFound := ext["prebid"] + if !prebidJSONFound { + return nil, nil + } + + var prebidMap = make(map[string]json.RawMessage) + if err := json.Unmarshal(prebidJSONValue, &prebidMap); err != nil { + return nil, err + } + + if rewardedInventory, foundRewardedInventory := prebidMap[isRewardedInventory]; foundRewardedInventory && string(rewardedInventory) == stateRewardedInventoryEnable { + ext[isRewardedInventory] = json.RawMessage(`true`) + impExt, err := json.Marshal(ext) + if err != nil { + return nil, err + } + + return impExt, nil + } + + return nil, nil +} diff --git a/adapters/improvedigital/improvedigital_test.go b/adapters/improvedigital/improvedigital_test.go index 7a45faacff4..f648319c7f1 100644 --- a/adapters/improvedigital/improvedigital_test.go +++ b/adapters/improvedigital/improvedigital_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderImprovedigital, config.Adapter{ - Endpoint: "http://localhost/pbs"}) + Endpoint: "http://localhost/pbs"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/improvedigital/improvedigitaltest/supplemental/dealid.json b/adapters/improvedigital/improvedigitaltest/supplemental/dealid.json new file mode 100644 index 00000000000..012a5a6a3c5 --- /dev/null +++ b/adapters/improvedigital/improvedigitaltest/supplemental/dealid.json @@ -0,0 +1,594 @@ +{ + "mockBidRequest": { + "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 + } + ] + }, + "ext": { + "bidder": { + "placementId": 13245 + } + } + }, + { + "id": "test-imp-id-banner", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": 13245 + } + } + }, + { + "id": "test-imp-id-banner", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": 13245 + } + } + }, + { + "id": "test-imp-id-banner", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": 13245 + } + } + }, + { + "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", + "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 + } + ] + }, + "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, + "ext": { + "improvedigital": { + "line_item_id": 279820, + "bidder_id": 0, + "brand_name": "", + "buying_type": "classic", + "agency_id": "0" + } + } + } + ] + } + ], + "cur": "USD" + } + } + }, + { + "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 + } + ] + }, + "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, + "ext": { + "improvedigital": { + "line_item_id": 279820, + "bidder_id": 0, + "brand_name": "", + "buying_type": "rtb", + "agency_id": "0" + } + } + } + ] + } + ], + "cur": "USD" + } + } + }, + { + "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 + } + ] + }, + "ext": { + "bidder": { + "placementId": 13245 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "improvedigital", + "bid": [ + { + "id": "randomid", + "impid": "test-imp-id-banner", + "price": 0.5, + "adid": "12345678", + "adm": "some-test-ad-html", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "ext": { + "improvedigital": { + "bidder_id": 0, + "brand_name": "", + "buying_type": "rtb", + "agency_id": "0" + } + } + } + ] + } + ], + "cur": "USD" + } + } + }, + { + "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 + } + ] + }, + "ext": { + "bidder": { + "placementId": 13245 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "improvedigital", + "bid": [ + { + "id": "randomid", + "impid": "test-imp-id-banner", + "price": 0.5, + "adid": "12345678", + "adm": "some-test-ad-html", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "ext": { + "improvedigital": { + "line_item_id": 223344, + "bidder_id": 0, + "brand_name": "", + "buying_type": "", + "agency_id": "0" + } + } + } + ] + } + ], + "cur": "USD" + } + } + }, + { + "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 + } + ] + }, + "ext": { + "bidder": { + "placementId": 13245 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "improvedigital", + "bid": [ + { + "id": "randomid", + "impid": "test-imp-id-banner", + "price": 0.5, + "adid": "12345678", + "adm": "some-test-ad-html", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "ext": { + "improvedigital": { + "line_item_id": 223344, + "bidder_id": 0, + "brand_name": "", + "agency_id": "0" + } + } + } + ] + } + ], + "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, + "dealid": "279820", + "ext": { + "improvedigital": { + "line_item_id": 279820, + "bidder_id": 0, + "brand_name": "", + "buying_type": "classic", + "agency_id": "0" + } + } + }, + "type": "banner" + } + ] + }, + { + "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, + "ext": { + "improvedigital": { + "line_item_id": 279820, + "bidder_id": 0, + "brand_name": "", + "buying_type": "rtb", + "agency_id": "0" + } + } + }, + "type": "banner" + } + ] + }, + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "randomid", + "impid": "test-imp-id-banner", + "price": 0.5, + "adm": "some-test-ad-html", + "adid": "12345678", + "cid": "987", + "crid": "12345678", + "w": 300, + "h": 250, + "ext": { + "improvedigital": { + "bidder_id": 0, + "brand_name": "", + "buying_type": "rtb", + "agency_id": "0" + } + } + }, + "type": "banner" + } + ] + }, + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "randomid", + "impid": "test-imp-id-banner", + "price": 0.5, + "adm": "some-test-ad-html", + "adid": "12345678", + "cid": "987", + "crid": "12345678", + "w": 300, + "h": 250, + "ext": { + "improvedigital": { + "line_item_id": 223344, + "bidder_id": 0, + "brand_name": "", + "buying_type": "", + "agency_id": "0" + } + } + }, + "type": "banner" + } + ] + }, + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "randomid", + "impid": "test-imp-id-banner", + "price": 0.5, + "adm": "some-test-ad-html", + "adid": "12345678", + "cid": "987", + "crid": "12345678", + "w": 300, + "h": 250, + "ext": { + "improvedigital": { + "line_item_id": 223344, + "bidder_id": 0, + "brand_name": "", + "agency_id": "0" + } + } + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/improvedigital/improvedigitaltest/supplemental/rewarded-inventory.json b/adapters/improvedigital/improvedigitaltest/supplemental/rewarded-inventory.json new file mode 100644 index 00000000000..44921fe98f7 --- /dev/null +++ b/adapters/improvedigital/improvedigitaltest/supplemental/rewarded-inventory.json @@ -0,0 +1,177 @@ +{ + "mockBidRequest": { + "id": "rewarded-inventory-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [ + { + "id": "rewarded-inventory-imp-id-true", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": 13245 + }, + "prebid": { + "is_rewarded_inventory": 1 + } + } + }, + { + "id": "rewarded-inventory-imp-id-false", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": 13245 + }, + "prebid": { + "is_rewarded_inventory": 0 + } + } + }, + { + "id": "rewarded-inventory-imp-id-not-defined", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": 13245 + }, + "prebid": {} + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/pbs", + "body": { + "id": "rewarded-inventory-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [ + { + "id": "rewarded-inventory-imp-id-true", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": 13245 + }, + "prebid": { + "is_rewarded_inventory": 1 + }, + "is_rewarded_inventory": true + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": {} + } + }, + { + "expectedRequest": { + "uri": "http://localhost/pbs", + "body": { + "id": "rewarded-inventory-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [ + { + "id": "rewarded-inventory-imp-id-false", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": 13245 + }, + "prebid": { + "is_rewarded_inventory": 0 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": {} + } + }, + { + "expectedRequest": { + "uri": "http://localhost/pbs", + "body": { + "id": "rewarded-inventory-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [ + { + "id": "rewarded-inventory-imp-id-not-defined", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": 13245 + }, + "prebid": {} + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": {} + } + } + ], + "expectedBidResponses": [ + {},{},{} + ] +} diff --git a/adapters/infoawarebidder.go b/adapters/infoawarebidder.go index 6a07bda3a4d..f44d83bcb7b 100644 --- a/adapters/infoawarebidder.go +++ b/adapters/infoawarebidder.go @@ -3,7 +3,7 @@ package adapters import ( "fmt" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" @@ -13,12 +13,12 @@ import ( // media types defined in the static/bidder-info/{bidder}.yaml file. // // It adjusts incoming requests in the following ways: -// 1. If App or Site traffic is not supported by the info file, then requests from -// those sources will be rejected before the delegate is called. -// 2. If a given MediaType is not supported for the platform, then it will be set -// to nil before the request is forwarded to the delegate. -// 3. Any Imps which have no MediaTypes left will be removed. -// 4. If there are no valid Imps left, the delegate won't be called at all. +// 1. If App or Site traffic is not supported by the info file, then requests from +// those sources will be rejected before the delegate is called. +// 2. If a given MediaType is not supported for the platform, then it will be set +// to nil before the request is forwarded to the delegate. +// 3. Any Imps which have no MediaTypes left will be removed. +// 4. If there are no valid Imps left, the delegate won't be called at all. type InfoAwareBidder struct { Bidder info parsedBidderInfo @@ -37,13 +37,13 @@ func (i *InfoAwareBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *Ex if request.Site != nil { if !i.info.site.enabled { - return nil, []error{&errortypes.BadInput{Message: "this bidder does not support site requests"}} + return nil, []error{&errortypes.Warning{Message: "this bidder does not support site requests"}} } allowedMediaTypes = i.info.site } if request.App != nil { if !i.info.app.enabled { - return nil, []error{&errortypes.BadInput{Message: "this bidder does not support app requests"}} + return nil, []error{&errortypes.Warning{Message: "this bidder does not support app requests"}} } allowedMediaTypes = i.info.app } @@ -57,7 +57,7 @@ func (i *InfoAwareBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *Ex // If all imps in bid request come with unsupported media types, exit if numToFilter == len(request.Imp) { - return nil, append(errs, &errortypes.BadInput{Message: "Bid request didn't contain media types supported by the bidder"}) + return nil, append(errs, &errortypes.Warning{Message: "Bid request didn't contain media types supported by the bidder"}) } if numToFilter != 0 { @@ -78,19 +78,19 @@ func pruneImps(imps []openrtb2.Imp, allowedTypes parsedSupports) (int, []error) for i := 0; i < len(imps); i++ { if !allowedTypes.banner && imps[i].Banner != nil { imps[i].Banner = nil - errs = append(errs, &errortypes.BadInput{Message: fmt.Sprintf("request.imp[%d] uses banner, but this bidder doesn't support it", i)}) + errs = append(errs, &errortypes.Warning{Message: fmt.Sprintf("request.imp[%d] uses banner, but this bidder doesn't support it", i)}) } if !allowedTypes.video && imps[i].Video != nil { imps[i].Video = nil - errs = append(errs, &errortypes.BadInput{Message: fmt.Sprintf("request.imp[%d] uses video, but this bidder doesn't support it", i)}) + errs = append(errs, &errortypes.Warning{Message: fmt.Sprintf("request.imp[%d] uses video, but this bidder doesn't support it", i)}) } if !allowedTypes.audio && imps[i].Audio != nil { imps[i].Audio = nil - errs = append(errs, &errortypes.BadInput{Message: fmt.Sprintf("request.imp[%d] uses audio, but this bidder doesn't support it", i)}) + errs = append(errs, &errortypes.Warning{Message: fmt.Sprintf("request.imp[%d] uses audio, but this bidder doesn't support it", i)}) } if !allowedTypes.native && imps[i].Native != nil { imps[i].Native = nil - errs = append(errs, &errortypes.BadInput{Message: fmt.Sprintf("request.imp[%d] uses native, but this bidder doesn't support it", i)}) + errs = append(errs, &errortypes.Warning{Message: fmt.Sprintf("request.imp[%d] uses native, but this bidder doesn't support it", i)}) } if !hasAnyTypes(&imps[i]) { numToFilter = numToFilter + 1 diff --git a/adapters/infoawarebidder_test.go b/adapters/infoawarebidder_test.go index 7bd476b314d..5867119ad16 100644 --- a/adapters/infoawarebidder_test.go +++ b/adapters/infoawarebidder_test.go @@ -4,7 +4,7 @@ import ( "errors" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -30,7 +30,7 @@ func TestAppNotSupported(t *testing.T) { return } assert.EqualError(t, errs[0], "this bidder does not support app requests") - assert.IsType(t, &errortypes.BadInput{}, errs[0]) + assert.IsType(t, &errortypes.Warning{}, errs[0]) assert.Len(t, bids, 0) } @@ -52,7 +52,7 @@ func TestSiteNotSupported(t *testing.T) { return } assert.EqualError(t, errs[0], "this bidder does not support site requests") - assert.IsType(t, &errortypes.BadInput{}, errs[0]) + assert.IsType(t, &errortypes.Warning{}, errs[0]) assert.Len(t, bids, 0) } diff --git a/adapters/infytv/infytv.go b/adapters/infytv/infytv.go new file mode 100644 index 00000000000..b5be0440431 --- /dev/null +++ b/adapters/infytv/infytv.go @@ -0,0 +1,90 @@ +package infytv + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/prebid/openrtb/v17/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 InfyTV adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (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.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Bad Request. %s", string(responseData.Body)), + }} + } + + if responseData.StatusCode == http.StatusServiceUnavailable { + return nil, nil + } + + if responseData.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Something went wrong, please contact your Account Manager. Status Code: [ %d ] ", responseData.StatusCode), + }} + } + + var response openrtb2.BidResponse + if err := json.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Bad response, %s", err), + }} + } + + if len(response.SeatBid) == 0 { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Empty seatbid", + }} + } + + 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.BidTypeVideo, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + return bidResponse, nil +} diff --git a/adapters/infytv/infytv_test.go b/adapters/infytv/infytv_test.go new file mode 100644 index 00000000000..6834e99fa36 --- /dev/null +++ b/adapters/infytv/infytv_test.go @@ -0,0 +1,18 @@ +package infytv + +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: "https://test.infy.tv/pbs/openrtb"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + assert.NoError(t, buildErr) + adapterstest.RunJSONBidderTest(t, "infytvtest", bidder) +} diff --git a/adapters/infytv/infytvtest/exemplary/app.json b/adapters/infytv/infytvtest/exemplary/app.json new file mode 100644 index 00000000000..d4426bf7bde --- /dev/null +++ b/adapters/infytv/infytvtest/exemplary/app.json @@ -0,0 +1,258 @@ +{ + "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": { + "publisherId": "IY1014", + "placementId": "1999" + } + } + } + ], + "app": { + "id": "AA88S0101", + "name": "Crunchyroll", + "bundle": "com.adrise.crunchyroll", + "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb", + "cat": [ + "IAB1" + ], + "privacypolicy": 1, + "publisher": { + "id": "IY1002", + "name": "The View Point", + "domain": "krushmedia.com" + }, + "content": { + "language": "en" + } + }, + "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": "https://test.infy.tv/pbs/openrtb", + "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": { + "publisherId": "IY1014", + "placementId": "1999" + } + } + } + ], + "app": { + "id": "AA88S0101", + "name": "Crunchyroll", + "bundle": "com.adrise.crunchyroll", + "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb", + "cat": [ + "IAB1" + ], + "privacypolicy": 1, + "publisher": { + "id": "IY1002", + "name": "The View Point", + "domain": "krushmedia.com" + }, + "content": { + "language": "en" + } + }, + "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/infytv/infytvtest/exemplary/video.json b/adapters/infytv/infytvtest/exemplary/video.json new file mode 100644 index 00000000000..7eb9c80080c --- /dev/null +++ b/adapters/infytv/infytvtest/exemplary/video.json @@ -0,0 +1,280 @@ +{ + "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": { + "publisherId": "IY1014", + "placementId": "1999" + } + } + } + ], + "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": "https://test.infy.tv/pbs/openrtb", + "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": { + "publisherId": "IY1014", + "placementId": "1999" + } + } + } + ], + "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/infytv/infytvtest/supplemental/bad-response.json b/adapters/infytv/infytvtest/supplemental/bad-response.json new file mode 100644 index 00000000000..5141cdca702 --- /dev/null +++ b/adapters/infytv/infytvtest/supplemental/bad-response.json @@ -0,0 +1,202 @@ +{ + "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" + } + } + } + ], + "app": { + "id": "AA88S0101", + "name": "Crunchyroll", + "bundle": "com.adrise.crunchyroll", + "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb", + "cat": [ + "IAB1" + ], + "privacypolicy": 1, + "publisher": { + "id": "IY1002", + "name": "The View Point", + "domain": "krushmedia.com" + }, + "content": { + "language": "en" + } + }, + "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": "https://test.infy.tv/pbs/openrtb", + "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" + } + } + } + ], + "app": { + "id": "AA88S0101", + "name": "Crunchyroll", + "bundle": "com.adrise.crunchyroll", + "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb", + "cat": [ + "IAB1" + ], + "privacypolicy": 1, + "publisher": { + "id": "IY1002", + "name": "The View Point", + "domain": "krushmedia.com" + }, + "content": { + "language": "en" + } + }, + "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/infytv/infytvtest/supplemental/empty-seatbid.json b/adapters/infytv/infytvtest/supplemental/empty-seatbid.json new file mode 100644 index 00000000000..0db426fc478 --- /dev/null +++ b/adapters/infytv/infytvtest/supplemental/empty-seatbid.json @@ -0,0 +1,207 @@ +{ + "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" + } + } + } + ], + "app": { + "id": "AA88S0101", + "name": "Crunchyroll", + "bundle": "com.adrise.crunchyroll", + "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb", + "cat": [ + "IAB1" + ], + "privacypolicy": 1, + "publisher": { + "id": "IY1002", + "name": "The View Point", + "domain": "krushmedia.com" + }, + "content": { + "language": "en" + } + }, + "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": "https://test.infy.tv/pbs/openrtb", + "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" + } + } + } + ], + "app": { + "id": "AA88S0101", + "name": "Crunchyroll", + "bundle": "com.adrise.crunchyroll", + "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb", + "cat": [ + "IAB1" + ], + "privacypolicy": 1, + "publisher": { + "id": "IY1002", + "name": "The View Point", + "domain": "krushmedia.com" + }, + "content": { + "language": "en" + } + }, + "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": "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/infytv/infytvtest/supplemental/status-204.json b/adapters/infytv/infytvtest/supplemental/status-204.json new file mode 100644 index 00000000000..f432e241ac1 --- /dev/null +++ b/adapters/infytv/infytvtest/supplemental/status-204.json @@ -0,0 +1,196 @@ +{ + "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" + } + } + } + ], + "app": { + "id": "AA88S0101", + "name": "Crunchyroll", + "bundle": "com.adrise.crunchyroll", + "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb", + "cat": [ + "IAB1" + ], + "privacypolicy": 1, + "publisher": { + "id": "IY1002", + "name": "The View Point", + "domain": "krushmedia.com" + }, + "content": { + "language": "en" + } + }, + "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": "https://test.infy.tv/pbs/openrtb", + "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" + } + } + } + ], + "app": { + "id": "AA88S0101", + "name": "Crunchyroll", + "bundle": "com.adrise.crunchyroll", + "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb", + "cat": [ + "IAB1" + ], + "privacypolicy": 1, + "publisher": { + "id": "IY1002", + "name": "The View Point", + "domain": "krushmedia.com" + }, + "content": { + "language": "en" + } + }, + "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": 204, + "body": {} + } + } + ], + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/infytv/infytvtest/supplemental/status-400.json b/adapters/infytv/infytvtest/supplemental/status-400.json new file mode 100644 index 00000000000..44d55575128 --- /dev/null +++ b/adapters/infytv/infytvtest/supplemental/status-400.json @@ -0,0 +1,202 @@ +{ + "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" + } + } + } + ], + "app": { + "id": "AA88S0101", + "name": "Crunchyroll", + "bundle": "com.adrise.crunchyroll", + "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb", + "cat": [ + "IAB1" + ], + "privacypolicy": 1, + "publisher": { + "id": "IY1002", + "name": "The View Point", + "domain": "krushmedia.com" + }, + "content": { + "language": "en" + } + }, + "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": "https://test.infy.tv/pbs/openrtb", + "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" + } + } + } + ], + "app": { + "id": "AA88S0101", + "name": "Crunchyroll", + "bundle": "com.adrise.crunchyroll", + "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb", + "cat": [ + "IAB1" + ], + "privacypolicy": 1, + "publisher": { + "id": "IY1002", + "name": "The View Point", + "domain": "krushmedia.com" + }, + "content": { + "language": "en" + } + }, + "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": 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/infytv/infytvtest/supplemental/status-503.json b/adapters/infytv/infytvtest/supplemental/status-503.json new file mode 100644 index 00000000000..310ad85e8ce --- /dev/null +++ b/adapters/infytv/infytvtest/supplemental/status-503.json @@ -0,0 +1,195 @@ +{ + "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" + } + } + } + ], + "app": { + "id": "AA88S0101", + "name": "Crunchyroll", + "bundle": "com.adrise.crunchyroll", + "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb", + "cat": [ + "IAB1" + ], + "privacypolicy": 1, + "publisher": { + "id": "IY1002", + "name": "The View Point", + "domain": "krushmedia.com" + }, + "content": { + "language": "en" + } + }, + "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": "https://test.infy.tv/pbs/openrtb", + "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" + } + } + } + ], + "app": { + "id": "AA88S0101", + "name": "Crunchyroll", + "bundle": "com.adrise.crunchyroll", + "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb", + "cat": [ + "IAB1" + ], + "privacypolicy": 1, + "publisher": { + "id": "IY1002", + "name": "The View Point", + "domain": "krushmedia.com" + }, + "content": { + "language": "en" + } + }, + "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": 503 + } + } + ], + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/infytv/infytvtest/supplemental/unexpected-status.json b/adapters/infytv/infytvtest/supplemental/unexpected-status.json new file mode 100644 index 00000000000..bfb4b3f9b0b --- /dev/null +++ b/adapters/infytv/infytvtest/supplemental/unexpected-status.json @@ -0,0 +1,201 @@ +{ + "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" + } + } + } + ], + "app": { + "id": "AA88S0101", + "name": "Crunchyroll", + "bundle": "com.adrise.crunchyroll", + "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb", + "cat": [ + "IAB1" + ], + "privacypolicy": 1, + "publisher": { + "id": "IY1002", + "name": "The View Point", + "domain": "krushmedia.com" + }, + "content": { + "language": "en" + } + }, + "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": "https://test.infy.tv/pbs/openrtb", + "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" + } + } + } + ], + "app": { + "id": "AA88S0101", + "name": "Crunchyroll", + "bundle": "com.adrise.crunchyroll", + "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb", + "cat": [ + "IAB1" + ], + "privacypolicy": 1, + "publisher": { + "id": "IY1002", + "name": "The View Point", + "domain": "krushmedia.com" + }, + "content": { + "language": "en" + } + }, + "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": 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/infytv/params_test.go b/adapters/infytv/params_test.go new file mode 100644 index 00000000000..6719b102622 --- /dev/null +++ b/adapters/infytv/params_test.go @@ -0,0 +1,44 @@ +package infytv + +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.BidderInfyTV, 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.BidderInfyTV, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} + +var validParams = []string{ + `{"publisherId": "1598991608990"}`, + `{"publisherId": "1598991608990", "placementId": "9999"}`, +} + +var invalidParams = []string{ + `{"publisherId": 42}`, + `{"publisherId": 42, "placementId":9898}`, +} diff --git a/adapters/inmobi/inmobi.go b/adapters/inmobi/inmobi.go index 63baa8a4ba5..f0493be35a8 100644 --- a/adapters/inmobi/inmobi.go +++ b/adapters/inmobi/inmobi.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -17,7 +17,7 @@ type InMobiAdapter struct { } // Builder builds a new instance of the InMobi adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &InMobiAdapter{ endPoint: config.Endpoint, } diff --git a/adapters/inmobi/inmobi_test.go b/adapters/inmobi/inmobi_test.go index 0b662684f72..0b6f5ffbd0e 100644 --- a/adapters/inmobi/inmobi_test.go +++ b/adapters/inmobi/inmobi_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderInMobi, config.Adapter{ - Endpoint: "https://api.w.inmobi.com/showad/openrtb/bidder/prebid"}) + Endpoint: "https://api.w.inmobi.com/showad/openrtb/bidder/prebid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/interactiveoffers/interactiveoffers.go b/adapters/interactiveoffers/interactiveoffers.go index e77dfd5a563..d187baf6a2f 100644 --- a/adapters/interactiveoffers/interactiveoffers.go +++ b/adapters/interactiveoffers/interactiveoffers.go @@ -6,7 +6,7 @@ import ( "net/http" "text/template" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -95,7 +95,7 @@ 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) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (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) diff --git a/adapters/interactiveoffers/interactiveoffers_test.go b/adapters/interactiveoffers/interactiveoffers_test.go index 607bd6d2925..7805ae665bb 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://prebid-server.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04"}) + Endpoint: "https://prebid-server.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/invibes/invibes.go b/adapters/invibes/invibes.go index 505ef4040cb..6aedb913e4f 100644 --- a/adapters/invibes/invibes.go +++ b/adapters/invibes/invibes.go @@ -9,7 +9,7 @@ import ( "strings" "text/template" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -74,7 +74,7 @@ type InvibesAdapter struct { } // Builder builds a new instance of the Invibes adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (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) diff --git a/adapters/invibes/invibes_test.go b/adapters/invibes/invibes_test.go index 2b26c406c30..1ade5d276cd 100644 --- a/adapters/invibes/invibes_test.go +++ b/adapters/invibes/invibes_test.go @@ -11,7 +11,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderInvibes, config.Adapter{ - Endpoint: "https://{{.ZoneID}}.videostep.com/bid/ServerBidAdContent"}) + Endpoint: "https://{{.ZoneID}}.videostep.com/bid/ServerBidAdContent"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -22,7 +22,7 @@ func TestJsonSamples(t *testing.T) { func TestEndpointTemplateMalformed(t *testing.T) { _, buildErr := Builder(openrtb_ext.BidderInvibes, config.Adapter{ - Endpoint: "{{Malformed}}"}) + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Error(t, buildErr) } diff --git a/adapters/iqzone/iqzone.go b/adapters/iqzone/iqzone.go index 79dc04d835a..9c4790bd530 100644 --- a/adapters/iqzone/iqzone.go +++ b/adapters/iqzone/iqzone.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -16,7 +16,7 @@ type adapter struct { endpoint string } -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &adapter{ endpoint: config.Endpoint, } diff --git a/adapters/iqzone/iqzone_test.go b/adapters/iqzone/iqzone_test.go index 69ee77e2775..3e1afbe1909 100644 --- a/adapters/iqzone/iqzone_test.go +++ b/adapters/iqzone/iqzone_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderIQZone, config.Adapter{ - Endpoint: "http://smartssp-us-east.iqzone.com/pserver"}) + Endpoint: "http://smartssp-us-east.iqzone.com/pserver"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/ix/ix.go b/adapters/ix/ix.go index 1cfec69322d..20512ecbe00 100644 --- a/adapters/ix/ix.go +++ b/adapters/ix/ix.go @@ -11,10 +11,11 @@ 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/version" - "github.com/mxmCherry/openrtb/v15/native1" - native1response "github.com/mxmCherry/openrtb/v15/native1/response" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/native1" + native1response "github.com/prebid/openrtb/v17/native1/response" + "github.com/prebid/openrtb/v17/openrtb2" ) type IxAdapter struct { @@ -22,6 +23,17 @@ type IxAdapter struct { maxRequests int } +type ExtRequest struct { + Prebid *openrtb_ext.ExtRequestPrebid `json:"prebid"` + SChain *openrtb2.SupplyChain `json:"schain,omitempty"` + IxDiag *IxDiag `json:"ixdiag,omitempty"` +} + +type IxDiag struct { + PbsV string `json:"pbsv,omitempty"` + PbjsV string `json:"pbjsv,omitempty"` +} + func (a *IxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { nImp := len(request.Imp) if nImp > a.maxRequests { @@ -29,13 +41,18 @@ func (a *IxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters nImp = a.maxRequests } + errs := make([]error, 0) + + if err := BuildIxDiag(request); err != nil { + errs = append(errs, err) + } + // Multi-size banner imps are split into single-size requests. // The first size imp requests are added to the first slice. // Additional size requests are added to the second slice and are merged with the first at the end. // Preallocate the max possible size to avoid reallocating arrays. requests := make([]*adapters.RequestData, 0, a.maxRequests) multiSizeRequests := make([]*adapters.RequestData, 0, a.maxRequests-nImp) - errs := make([]error, 0, 1) headers := http.Header{ "Content-Type": {"application/json;charset=utf-8"}, @@ -148,18 +165,18 @@ func (a *IxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalReque }} } - // Until the time we support multi-format ad units, we'll use a bid request impression media type - // as a bid response bid type. They are linked by the impression id. - impMediaType := map[string]openrtb_ext.BidType{} + // Store media type per impression in a map for later use to set in bid.ext.prebid.type + // Won't work for multiple bid case with a multi-format ad unit. We expect to get type from exchange on such case. + impMediaTypeReq := map[string]openrtb_ext.BidType{} for _, imp := range internalRequest.Imp { if imp.Banner != nil { - impMediaType[imp.ID] = openrtb_ext.BidTypeBanner + impMediaTypeReq[imp.ID] = openrtb_ext.BidTypeBanner } else if imp.Video != nil { - impMediaType[imp.ID] = openrtb_ext.BidTypeVideo + impMediaTypeReq[imp.ID] = openrtb_ext.BidTypeVideo } else if imp.Native != nil { - impMediaType[imp.ID] = openrtb_ext.BidTypeNative + impMediaTypeReq[imp.ID] = openrtb_ext.BidTypeNative } else if imp.Audio != nil { - impMediaType[imp.ID] = openrtb_ext.BidTypeAudio + impMediaTypeReq[imp.ID] = openrtb_ext.BidTypeAudio } } @@ -170,9 +187,11 @@ func (a *IxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalReque for _, seatBid := range bidResponse.SeatBid { for _, bid := range seatBid.Bid { - bidType, ok := impMediaType[bid.ImpID] - if !ok { - errs = append(errs, fmt.Errorf("unmatched impression id: %s", bid.ImpID)) + + bidType, err := getMediaTypeForBid(bid, impMediaTypeReq) + if err != nil { + errs = append(errs, err) + continue } var bidExtVideo *openrtb_ext.ExtBidPrebidVideo @@ -222,8 +241,38 @@ func (a *IxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalReque return bidderResponse, errs } +func getMediaTypeForBid(bid openrtb2.Bid, impMediaTypeReq map[string]openrtb_ext.BidType) (openrtb_ext.BidType, error) { + switch bid.MType { + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner, nil + case openrtb2.MarkupVideo: + return openrtb_ext.BidTypeVideo, nil + case openrtb2.MarkupAudio: + return openrtb_ext.BidTypeAudio, nil + case openrtb2.MarkupNative: + return openrtb_ext.BidTypeNative, nil + } + + if bid.Ext != nil { + var bidExt openrtb_ext.ExtBid + err := json.Unmarshal(bid.Ext, &bidExt) + if err == nil && bidExt.Prebid != nil { + prebidType := string(bidExt.Prebid.Type) + if prebidType != "" { + return openrtb_ext.ParseBidType(prebidType) + } + } + } + + if bidType, ok := impMediaTypeReq[bid.ImpID]; ok { + return bidType, nil + } else { + return "", fmt.Errorf("unmatched impression id: %s", bid.ImpID) + } +} + // 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) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &IxAdapter{ URI: config.Endpoint, maxRequests: 20, @@ -277,3 +326,35 @@ func marshalJsonWithoutUnicode(v interface{}) (string, error) { // https://pkg.go.dev/encoding/json#Encoder.Encode return strings.TrimSuffix(sb.String(), "\n"), nil } + +func BuildIxDiag(request *openrtb2.BidRequest) error { + extRequest := &ExtRequest{} + if request.Ext != nil { + if err := json.Unmarshal(request.Ext, &extRequest); err != nil { + return err + } + } + ixdiag := &IxDiag{} + + if extRequest.Prebid != nil && extRequest.Prebid.Channel != nil { + ixdiag.PbjsV = extRequest.Prebid.Channel.Version + } + + // Slice commit hash out of version + if strings.Contains(version.Ver, "-") { + ixdiag.PbsV = version.Ver[:strings.Index(version.Ver, "-")] + } else if version.Ver != "" { + ixdiag.PbsV = version.Ver + } + + // Only set request.ext if ixDiag is not empty + if *ixdiag != (IxDiag{}) { + extRequest.IxDiag = ixdiag + extRequestJson, err := json.Marshal(extRequest) + if err != nil { + return err + } + request.Ext = extRequestJson + } + return nil +} diff --git a/adapters/ix/ix_test.go b/adapters/ix/ix_test.go index fc1d0f9a0a2..a4940ef38c0 100644 --- a/adapters/ix/ix_test.go +++ b/adapters/ix/ix_test.go @@ -8,14 +8,17 @@ import ( "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/version" + "github.com/stretchr/testify/assert" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/adcom1" + "github.com/prebid/openrtb/v17/openrtb2" ) const endpoint string = "http://host/endpoint" func TestJsonSamples(t *testing.T) { - if bidder, err := Builder(openrtb_ext.BidderIx, config.Adapter{Endpoint: endpoint}); err == nil { + if bidder, err := Builder(openrtb_ext.BidderIx, config.Adapter{Endpoint: endpoint}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}); err == nil { ixBidder := bidder.(*IxAdapter) ixBidder.maxRequests = 2 adapterstest.RunJSONBidderTest(t, "ixtest", bidder) @@ -35,7 +38,7 @@ func TestIxMakeBidsWithCategoryDuration(t *testing.T) { H: 360, MIMEs: []string{"video/mp4"}, MaxDuration: 60, - Protocols: []openrtb2.Protocol{2, 3, 5, 6}, + Protocols: []adcom1.MediaCreativeSubtype{2, 3, 5, 6}, }, Ext: json.RawMessage( `{ @@ -100,3 +103,126 @@ func TestIxMakeBidsWithCategoryDuration(t *testing.T) { t.Errorf("should not have any errors, errors=%v", errors) } } + +func TestBuildIxDiag(t *testing.T) { + testCases := []struct { + description string + request *openrtb2.BidRequest + expectedRequest *openrtb2.BidRequest + expectError bool + pbsVersion string + }{ + { + description: "Base Test", + request: &openrtb2.BidRequest{ + ID: "1", + Ext: json.RawMessage(`{"prebid":{"channel":{"name":"web","version":"7.20"}}}`), + }, + expectedRequest: &openrtb2.BidRequest{ + ID: "1", + Ext: json.RawMessage(`{"prebid":{"channel":{"name":"web","version":"7.20"}},"ixdiag":{"pbsv":"1.880","pbjsv":"7.20"}}`), + }, + expectError: false, + pbsVersion: "1.880-abcdef", + }, + { + description: "Base test for nil channel but non-empty ext prebid payload", + request: &openrtb2.BidRequest{ + ID: "1", + Ext: json.RawMessage(`{"prebid":{"server":{"externalurl":"http://localhost:8000"}}}`), + }, + expectedRequest: &openrtb2.BidRequest{ + ID: "1", + Ext: json.RawMessage(`{"prebid":{"server":{"externalurl":"http://localhost:8000","gvlid":0,"datacenter":""}},"ixdiag":{"pbsv":"1.880"}}`), + }, + expectError: false, + pbsVersion: "1.880-abcdef", + }, + { + description: "No Ext", + request: &openrtb2.BidRequest{ + ID: "1", + }, + expectedRequest: &openrtb2.BidRequest{ + ID: "1", + Ext: json.RawMessage(`{"prebid":null,"ixdiag":{"pbsv":"1.880"}}`), + }, + expectError: false, + pbsVersion: "1.880-abcdef", + }, + { + description: "PBS Version Two Hypens", + request: &openrtb2.BidRequest{ + ID: "1", + }, + expectedRequest: &openrtb2.BidRequest{ + ID: "1", + Ext: json.RawMessage(`{"prebid":null,"ixdiag":{"pbsv":"0.23.1"}}`), + }, + expectError: false, + pbsVersion: "0.23.1-3-g4ee257d8", + }, + { + description: "PBS Version no Hyphen", + request: &openrtb2.BidRequest{ + ID: "1", + Ext: json.RawMessage(`{"prebid":{"channel":{"name":"web","version":"7.20"}}}`), + }, + expectedRequest: &openrtb2.BidRequest{ + ID: "1", + Ext: json.RawMessage(`{"prebid":{"channel":{"name":"web","version":"7.20"}},"ixdiag":{"pbsv":"1.880","pbjsv":"7.20"}}`), + }, + expectError: false, + pbsVersion: "1.880", + }, + { + description: "PBS Version empty string", + request: &openrtb2.BidRequest{ + ID: "1", + Ext: json.RawMessage(`{"prebid":{"channel":{"name":"web","version":"7.20"}}}`), + }, + expectedRequest: &openrtb2.BidRequest{ + ID: "1", + Ext: json.RawMessage(`{"prebid":{"channel":{"name":"web","version":"7.20"}},"ixdiag":{"pbjsv":"7.20"}}`), + }, + expectError: false, + pbsVersion: "", + }, + { + description: "Error Test", + request: &openrtb2.BidRequest{ + ID: "1", + Ext: json.RawMessage(`{"prebid":"channel":{"name":"web","version":"7.20"}}}`), + }, + expectedRequest: &openrtb2.BidRequest{ + ID: "1", + Ext: nil, + }, + expectError: true, + pbsVersion: "1.880-abcdef", + }, + } + + for _, test := range testCases { + t.Run(test.description, func(t *testing.T) { + version.Ver = test.pbsVersion + err := BuildIxDiag(test.request) + if test.expectError { + assert.NotNil(t, err) + } else { + assert.Equal(t, test.expectedRequest, test.request) + assert.Nil(t, err) + } + }) + } +} + +func TestMakeRequestsErrIxDiag(t *testing.T) { + bidder := &IxAdapter{} + req := &openrtb2.BidRequest{ + ID: "1", + Ext: json.RawMessage(`{"prebid":"channel":{"name":"web","version":"7.20"}}}`), + } + _, errs := bidder.MakeRequests(req, nil) + assert.Len(t, errs, 1) +} diff --git a/adapters/ix/ixtest/exemplary/multi-format-with-ext-prebid-type.json b/adapters/ix/ixtest/exemplary/multi-format-with-ext-prebid-type.json new file mode 100644 index 00000000000..51ea1fd1e72 --- /dev/null +++ b/adapters/ix/ixtest/exemplary/multi-format-with-ext-prebid-type.json @@ -0,0 +1,154 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "multi-format-test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 940, + "h": 560 + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://host/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "multi-format-test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 940, + "h": 560 + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "v-2", + "impid": "multi-format-test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300, + "cat": [ + "IAB9-1" + ], + "ext": { + "ix": {}, + "prebid": { + "type": "video" + } + } + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "v-2", + "impid": "multi-format-test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "w": 300, + "h": 250, + "cat": [ + "IAB9-1" + ], + "ext": { + "ix": {}, + "prebid": { + "type": "video" + } + } + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/ix/ixtest/exemplary/multi-format-with-mtype.json b/adapters/ix/ixtest/exemplary/multi-format-with-mtype.json new file mode 100644 index 00000000000..ca8e82179f8 --- /dev/null +++ b/adapters/ix/ixtest/exemplary/multi-format-with-mtype.json @@ -0,0 +1,150 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "multi-format-test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 940, + "h": 560 + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://host/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "multi-format-test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 940, + "h": 560 + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "v-2", + "impid": "multi-format-test-imp-id", + "mtype": 2, + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300, + "cat": [ + "IAB9-1" + ], + "ext": { + "ix": {} + } + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "v-2", + "impid": "multi-format-test-imp-id", + "mtype": 2, + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "w": 300, + "h": 250, + "cat": [ + "IAB9-1" + ], + "ext": { + "ix": {} + } + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/ix/ixtest/supplemental/bad-imp-id.json b/adapters/ix/ixtest/supplemental/bad-imp-id.json index 1ca053b674e..5ca8daf234e 100644 --- a/adapters/ix/ixtest/supplemental/bad-imp-id.json +++ b/adapters/ix/ixtest/supplemental/bad-imp-id.json @@ -82,33 +82,6 @@ } } ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "7706636740145184841", - "impid": "bad-imp-id", - "price": 0.5, - "adm": "some-test-ad", - "adid": "29681110", - "adomain": [ - "https://advertiser.example.com" - ], - "cid": "958", - "crid": "29681110", - "w": 300, - "h": 250, - "ext": { - "ix": {} - } - }, - "type": "" - } - ] - } - ], "expectedMakeBidsErrors": [ { "value": "unmatched impression id: bad-imp-id", diff --git a/adapters/jixie/jixie.go b/adapters/jixie/jixie.go index b158dbba58e..9ba57a09abe 100644 --- a/adapters/jixie/jixie.go +++ b/adapters/jixie/jixie.go @@ -6,7 +6,7 @@ import ( "net/http" "strings" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -18,7 +18,7 @@ type adapter struct { } // Builder builds a new instance of the Jixie adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &adapter{ endpoint: config.Endpoint, } diff --git a/adapters/jixie/jixie_test.go b/adapters/jixie/jixie_test.go index 289dd241f23..56260a6c04d 100644 --- a/adapters/jixie/jixie_test.go +++ b/adapters/jixie/jixie_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderJixie, config.Adapter{ - Endpoint: "https://hb.jixie.io/v2/hbsvrpost"}) + Endpoint: "https://hb.jixie.io/v2/hbsvrpost"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/kargo/kargo.go b/adapters/kargo/kargo.go new file mode 100644 index 00000000000..3ff634d7dff --- /dev/null +++ b/adapters/kargo/kargo.go @@ -0,0 +1,90 @@ +package kargo + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/prebid/openrtb/v17/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 +} +type kargoExt struct { + MediaType string `json:"mediaType"` +} + +// Builder builds a new instance of the Kargo adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + bidder := &adapter{ + URI: config.Endpoint, // base url of bidding server + } + return bidder, nil +} + +// MakeRequests creates outgoing requests to the Kargo bidding server. +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.URI, + Body: requestJSON, + } + + return []*adapters.RequestData{requestData}, nil +} + +// MakeBids receives a bid response from the Kargo bidding server and creates bids for the publishers auction. +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, bid := range seatBid.Bid { + b := &adapters.TypedBid{ + Bid: &seatBid.Bid[i], + BidType: getMediaTypeForBid(bid.Ext), + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + return bidResponse, nil +} + +func getMediaTypeForBid(ext json.RawMessage) openrtb_ext.BidType { + var impExt kargoExt + if err := json.Unmarshal(ext, &impExt); err == nil { + switch impExt.MediaType { + case string(openrtb_ext.BidTypeVideo): + return openrtb_ext.BidTypeVideo + case string(openrtb_ext.BidTypeNative): + return openrtb_ext.BidTypeNative + } + } + return openrtb_ext.BidTypeBanner +} diff --git a/adapters/kargo/kargo_test.go b/adapters/kargo/kargo_test.go new file mode 100644 index 00000000000..482ef2361cf --- /dev/null +++ b/adapters/kargo/kargo_test.go @@ -0,0 +1,20 @@ +package kargo + +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.BidderKargo, config.Adapter{ + Endpoint: "http://example.com/bid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "kargotest", bidder) +} diff --git a/adapters/kargo/kargotest/exemplary/banner.json b/adapters/kargo/kargotest/exemplary/banner.json new file mode 100644 index 00000000000..db14ee8b792 --- /dev/null +++ b/adapters/kargo/kargotest/exemplary/banner.json @@ -0,0 +1,207 @@ +{ + "mockBidRequest": { + "id": "5f4d1e01", + "at": 1, + "imp": [ + { + "id": "8b1bdcca", + "banner": { + "w": 300, + "h": 300, + "format": [ + { + "w": 300, + "h": 250 + } + ], + "pos": 0, + "expdir": [1, 2, 3, 4, 5] + }, + "tagid": "73", + "secure": 1, + "iframebuster": ["ALL"], + "ext": { + "adSlotID": "11523" + } + } + ], + "site": { + "id": "123", + "domain": "www.dailymail.co.uk", + "cat": ["IAB7"], + "page": "https://www.dailymail.co.uk/", + "ref": "https://www.dailymail.co.uk/", + "mobile": 1, + "publisher": { + "id": "1" + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.1 Safari/537", + "geo": { + "ipservice": 4, + "country": "US", + "region": "CO", + "metro": "751", + "city": "SomeCityInCo", + "zip": "11223" + }, + "ip": "127.0.0.1", + "devicetype": 2, + "make": "Apple", + "model": "iPhone", + "os": "Windows", + "osv": "10.0", + "carrier": "none", + "language": "en", + "connectiontype": 2, + "dnt": 0 + }, + "user": { + "id": "07fb48ed", + "ext": { + }, + "buyeruid": "345" + }, + "regs": { + "ext": { + "us_privacy": "1" + } + }, + "tmax": 200, + "ext": { + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/bid", + "body": { + "id": "5f4d1e01", + "at": 1, + "imp": [ + { + "id": "8b1bdcca", + "banner": { + "w": 300, + "h": 300, + "format": [ + { + "w": 300, + "h": 250 + } + ], + "pos": 0, + "expdir": [1, 2, 3, 4, 5] + }, + "tagid": "73", + "secure": 1, + "iframebuster": ["ALL"], + "ext": { + "adSlotID": "11523" + } + } + ], + "site": { + "id": "123", + "domain": "www.dailymail.co.uk", + "cat": ["IAB7"], + "page": "https://www.dailymail.co.uk/", + "ref": "https://www.dailymail.co.uk/", + "mobile": 1, + "publisher": { + "id": "1" + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.1 Safari/537", + "geo": { + "ipservice": 4, + "country": "US", + "region": "CO", + "metro": "751", + "city": "SomeCityInCo", + "zip": "11223" + }, + "ip": "127.0.0.1", + "devicetype": 2, + "make": "Apple", + "model": "iPhone", + "os": "Windows", + "osv": "10.0", + "carrier": "none", + "language": "en", + "connectiontype": 2, + "dnt": 0 + }, + "user": { + "id": "07fb48ed", + "ext": { + }, + "buyeruid": "345" + }, + "regs": { + "ext": { + "us_privacy": "1" + } + }, + "tmax": 200, + "ext": { + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5f4d1e01", + "seatbid": [ + { + "bid": [ + { + "id": "67sadsac", + "impid": "8b1bdcca", + "price": 10, + "nurl": "http://example.com/win/10", + "adm": "
ad
", + "adomain": ["example.com"], + "cid": "test-cid", + "crid": "test-crid", + "cat": ["IAB13"], + "w": 300, + "h": 300, + "ext": {"mediaType": "banner"} + } + ], + "seat": "_b345" + } + ], + "bidid": "67sadsac", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "67sadsac", + "impid": "8b1bdcca", + "price": 10, + "nurl": "http://example.com/win/10", + "adm": "
ad
", + "adomain": ["example.com"], + "cid": "test-cid", + "crid": "test-crid", + "cat": ["IAB13"], + "w": 300, + "h": 300, + "ext": {"mediaType": "banner"} + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/kargo/kargotest/exemplary/native.json b/adapters/kargo/kargotest/exemplary/native.json new file mode 100644 index 00000000000..d0f03d6104f --- /dev/null +++ b/adapters/kargo/kargotest/exemplary/native.json @@ -0,0 +1,220 @@ +{ + "mockBidRequest": { + "id": "5f4d1e01", + "at": 1, + "imp": [ + { + "id": "8b1bdcca", + "banner": { + "w": 300, + "h": 300, + "format": [ + { + "w": 300, + "h": 250 + } + ], + "pos": 0, + "expdir": [1,2,3,4,5] + }, + "native": { + "request" : "{\"plcmttype\":2,\"privacy\":0,\"assets\":[{\"id\":5,\"required\":0,\"title\":{\"len\":90}},{\"id\":6,\"required\":0,\"img\":{\"wmin\":620,\"hmin\":300,\"type\":3}},{\"id\":10,\"required\":0,\"data\":{\"len\":90,\"type\":1}},{\"id\":8,\"required\":0,\"data\":{\"len\":14,\"type\":12}},{\"id\":7,\"required\":0,\"img\":{\"wmin\":80,\"hmin\":80,\"type\":1}},{\"id\":11,\"required\":0,\"data\":{\"len\":140,\"type\":2}}]}" + }, + "tagid": "73", + "secure": 1, + "iframebuster": [ + "ALL" + ], + "ext": { + "adSlotID": "11523" + } + } + ], + "site": { + "id": "123", + "domain": "www.dailymail.co.uk", + "cat": ["IAB7"], + "page": "https://www.dailymail.co.uk/", + "ref": "https://www.dailymail.co.uk/", + "mobile": 1, + "publisher": { + "id": "1" + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.1 Safari/537", + "geo": { + "ipservice": 4, + "country": "US", + "region": "CO", + "metro": "751", + "city": "Ft Collins", + "zip": "80524" + }, + "ip": "127.0.0.1", + "devicetype": 2, + "make": "Apple", + "model": "iPhone", + "os": "Windows", + "osv": "10.0", + "carrier": "none", + "language": "en", + "connectiontype": 2, + "dnt": 0 + }, + "user": { + "id": "07fb48ed", + "ext": { + }, + "buyeruid": "345" + }, + "regs": { + "ext": { + "us_privacy": "1" + } + }, + "tmax": 200, + "ext": { + } + } + , + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/bid", + "body": { + "id": "5f4d1e01", + "at": 1, + "imp": [ + { + "id": "8b1bdcca", + "banner": { + "w": 300, + "h": 300, + "format": [ + { + "w": 300, + "h": 250 + } + ], + "pos": 0, + "expdir": [1,2,3,4,5] + }, + "native": { + "request" : "{\"plcmttype\":2,\"privacy\":0,\"assets\":[{\"id\":5,\"required\":0,\"title\":{\"len\":90}},{\"id\":6,\"required\":0,\"img\":{\"wmin\":620,\"hmin\":300,\"type\":3}},{\"id\":10,\"required\":0,\"data\":{\"len\":90,\"type\":1}},{\"id\":8,\"required\":0,\"data\":{\"len\":14,\"type\":12}},{\"id\":7,\"required\":0,\"img\":{\"wmin\":80,\"hmin\":80,\"type\":1}},{\"id\":11,\"required\":0,\"data\":{\"len\":140,\"type\":2}}]}" + }, + "tagid": "73", + "secure": 1, + "iframebuster": [ + "ALL" + ], + "ext": { + "adSlotID": "11523" + } + } + ], + "site": { + "id": "123", + "domain": "www.dailymail.co.uk", + "cat": ["IAB7"], + "page": "https://www.dailymail.co.uk/", + "ref": "https://www.dailymail.co.uk/", + "mobile": 1, + "publisher": { + "id": "1" + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.1 Safari/537", + "geo": { + "ipservice": 4, + "country": "US", + "region": "CO", + "metro": "751", + "city": "Ft Collins", + "zip": "80524" + }, + "ip": "127.0.0.1", + "devicetype": 2, + "make": "Apple", + "model": "iPhone", + "os": "Windows", + "osv": "10.0", + "carrier": "none", + "language": "en", + "connectiontype": 2, + "dnt": 0 + }, + "user": { + "id": "07fb48ed", + "ext": { + }, + "buyeruid": "345" + }, + "regs": { + "ext": { + "us_privacy": "1" + } + }, + "tmax": 200, + "ext": { + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5f4d1e01", + "seatbid": [ + { + "bid": [ + { + "id": "67sadsac", + "impid": "8b1bdcca", + "price": 10, + "nurl": "http://example.com/win/10", + "adm": "
ad
", + "adomain": ["example.com"], + "cid": "test-cid", + "crid": "test-crid", + "cat": ["IAB13"], + "w": 300, + "h": 300, + "ext": {"mediaType": "native"} + } + ], + "seat": "_b345" + } + ], + "bidid": "67sadsac", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "67sadsac", + "impid": "8b1bdcca", + "price": 10, + "nurl": "http://example.com/win/10", + "adm": "
ad
", + "adomain": ["example.com"], + "cid": "test-cid", + "crid": "test-crid", + "cat": ["IAB13"], + "w": 300, + "h": 300, + "ext": {"mediaType": "native"} + }, + "type": "native" + } + ] + } + ] + } + \ No newline at end of file diff --git a/adapters/kargo/kargotest/exemplary/video.json b/adapters/kargo/kargotest/exemplary/video.json new file mode 100644 index 00000000000..92a2124976f --- /dev/null +++ b/adapters/kargo/kargotest/exemplary/video.json @@ -0,0 +1,224 @@ +{ + "mockBidRequest": { + "id": "5f4d1e01", + "at": 1, + "imp": [ + { + "id": "8b1bdcca", + "banner": { + "w": 300, + "h": 300, + "format": [ + { + "w": 300, + "h": 250 + } + ], + "pos": 0, + "expdir": [1,2,3,4,5] + }, + "video": { + "mimes": null, + "skip":0, + "playbackmethod":[2] + }, + "tagid": "73", + "secure": 1, + "iframebuster": [ + "ALL" + ], + "ext": { + "adSlotID": "11523" + } + } + ], + "site": { + "id": "123", + "domain": "www.dailymail.co.uk", + "cat": ["IAB7"], + "page": "https://www.dailymail.co.uk/", + "ref": "https://www.dailymail.co.uk/", + "mobile": 1, + "publisher": { + "id": "1" + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.1 Safari/537", + "geo": { + "ipservice": 4, + "country": "US", + "region": "CO", + "metro": "751", + "city": "Ft Collins", + "zip": "80524" + }, + "ip": "127.0.0.1", + "devicetype": 2, + "make": "Apple", + "model": "iPhone", + "os": "Windows", + "osv": "10.0", + "carrier": "none", + "language": "en", + "connectiontype": 2, + "dnt": 0 + }, + "user": { + "id": "07fb48ed", + "ext": { + }, + "buyeruid": "345" + }, + "regs": { + "ext": { + "us_privacy": "1" + } + }, + "tmax": 200, + "ext": { + } + } + , + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/bid", + "body": { + "id": "5f4d1e01", + "at": 1, + "imp": [ + { + "id": "8b1bdcca", + "banner": { + "w": 300, + "h": 300, + "format": [ + { + "w": 300, + "h": 250 + } + ], + "pos": 0, + "expdir": [1,2,3,4,5] + }, + "video": { + "mimes": null, + "skip":0, + "playbackmethod":[2] + }, + "tagid": "73", + "secure": 1, + "iframebuster": [ + "ALL" + ], + "ext": { + "adSlotID": "11523" + } + } + ], + "site": { + "id": "123", + "domain": "www.dailymail.co.uk", + "cat": ["IAB7"], + "page": "https://www.dailymail.co.uk/", + "ref": "https://www.dailymail.co.uk/", + "mobile": 1, + "publisher": { + "id": "1" + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.1 Safari/537", + "geo": { + "ipservice": 4, + "country": "US", + "region": "CO", + "metro": "751", + "city": "Ft Collins", + "zip": "80524" + }, + "ip": "127.0.0.1", + "devicetype": 2, + "make": "Apple", + "model": "iPhone", + "os": "Windows", + "osv": "10.0", + "carrier": "none", + "language": "en", + "connectiontype": 2, + "dnt": 0 + }, + "user": { + "id": "07fb48ed", + "ext": { + }, + "buyeruid": "345" + }, + "regs": { + "ext": { + "us_privacy": "1" + } + }, + "tmax": 200, + "ext": { + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5f4d1e01", + "seatbid": [ + { + "bid": [ + { + "id": "67sadsac", + "impid": "8b1bdcca", + "price": 10, + "nurl": "http://example.com/win/10", + "adm": "
ad
", + "adomain": ["example.com"], + "cid": "test-cid", + "crid": "test-crid", + "cat": ["IAB13"], + "w": 300, + "h": 300, + "ext": {"mediaType": "video"} + } + ], + "seat": "_b345" + } + ], + "bidid": "67sadsac", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "67sadsac", + "impid": "8b1bdcca", + "price": 10, + "nurl": "http://example.com/win/10", + "adm": "
ad
", + "adomain": ["example.com"], + "cid": "test-cid", + "crid": "test-crid", + "cat": ["IAB13"], + "w": 300, + "h": 300, + "ext": {"mediaType": "video"} + }, + "type": "video" + } + ] + } + ] + } + \ No newline at end of file diff --git a/adapters/kargo/kargotest/supplemental/status-bad-request.json b/adapters/kargo/kargotest/supplemental/status-bad-request.json new file mode 100644 index 00000000000..3cb98431def --- /dev/null +++ b/adapters/kargo/kargotest/supplemental/status-bad-request.json @@ -0,0 +1,36 @@ +{ + "mockBidRequest": { + "id": "5f4d1e01", + "at": 1, + "imp": [ + ], + "tmax": 200 + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/bid", + "body": { + "id": "5f4d1e01", + "at": 1, + "imp": [ + ], + "tmax": 200 + } + }, + "mockResponse": { + "status": 400, + "body": { + } + } + } + ], + "expectedBidResponses": [], + "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/kargo/kargotest/supplemental/status-no-content.json b/adapters/kargo/kargotest/supplemental/status-no-content.json new file mode 100644 index 00000000000..8c6e0544589 --- /dev/null +++ b/adapters/kargo/kargotest/supplemental/status-no-content.json @@ -0,0 +1,130 @@ +{ + "mockBidRequest": { + "id": "5f4d1e01", + "at": 1, + "imp": [ + { + "id": "8b1bdcca", + "banner": { + "w": 300, + "h": 300, + "format": [ + { + "w": 300, + "h": 250 + } + ], + "pos": 0, + "expdir": [1, 2, 3, 4, 5] + }, + "video": { + "mimes": null + }, + "native": { + "request": "" + }, + "pmp": { + }, + "tagid": "73", + "secure": 1, + "iframebuster": ["ALL"], + "ext": {} + } + ], + "site": { + "id": "123", + "domain": "www.dailymail.co.uk", + "cat": ["IAB7"], + "page": "https://www.dailymail.co.uk/", + "ref": "https://www.dailymail.co.uk/", + "mobile": 1, + "publisher": { + "id": "1" + } + }, + "user": { + "id": "07fb48ed", + "ext": { + "consent": "consent opt-out" + }, + "buyeruid": "345" + }, + "regs": { + "ext": { + "us_privacy": "1" + } + }, + "tmax": 200, + "ext": {} + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/bid", + "body": { + "id": "5f4d1e01", + "at": 1, + "imp": [ + { + "id": "8b1bdcca", + "banner": { + "w": 300, + "h": 300, + "format": [ + { + "w": 300, + "h": 250 + } + ], + "pos": 0, + "expdir": [1, 2, 3, 4, 5] + }, + "video": { + "mimes": null + }, + "native": { + "request": "" + }, + "pmp": { + }, + "tagid": "73", + "secure": 1, + "iframebuster": ["ALL"], + "ext": {} + } + ], + "site": { + "id": "123", + "domain": "www.dailymail.co.uk", + "cat": ["IAB7"], + "page": "https://www.dailymail.co.uk/", + "ref": "https://www.dailymail.co.uk/", + "mobile": 1, + "publisher": { + "id": "1" + } + }, + "user": { + "id": "07fb48ed", + "ext": { + "consent": "consent opt-out" + }, + "buyeruid": "345" + }, + "regs": { + "ext": { + "us_privacy": "1" + } + }, + "tmax": 200, + "ext": {} + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/kargo/params_test.go b/adapters/kargo/params_test.go new file mode 100644 index 00000000000..44937637480 --- /dev/null +++ b/adapters/kargo/params_test.go @@ -0,0 +1,47 @@ +package kargo + +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.BidderKargo, 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.BidderKargo, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} + +var validParams = []string{ + `{"adSlotID": ""}`, + `{"adSlotID": "11523"}`, +} + +var invalidParams = []string{ + `{"adSlotID": 42}`, + `{"adSlotID": }`, + `{"adSlotId": "32321"}`, + `{"id": }`, + `{}`, +} diff --git a/adapters/kayzen/kayzen.go b/adapters/kayzen/kayzen.go index ea5408aaff3..66be7868c4d 100644 --- a/adapters/kayzen/kayzen.go +++ b/adapters/kayzen/kayzen.go @@ -6,7 +6,7 @@ import ( "net/http" "text/template" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -19,7 +19,7 @@ type adapter struct { } // Builder builds a new instance of the Kayzen adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (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) diff --git a/adapters/kayzen/kayzen_test.go b/adapters/kayzen/kayzen_test.go index 4cd07470ebb..dc6a7db4735 100644 --- a/adapters/kayzen/kayzen_test.go +++ b/adapters/kayzen/kayzen_test.go @@ -12,7 +12,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderKayzen, config.Adapter{ Endpoint: "https://example-{{.ZoneID}}.com/?exchange={{.AccountID}}", - }) + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -23,7 +23,7 @@ func TestJsonSamples(t *testing.T) { func TestEndpointTemplateMalformed(t *testing.T) { _, buildErr := Builder(openrtb_ext.BidderKayzen, config.Adapter{ - Endpoint: "{{Malformed}}"}) + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Error(t, buildErr) } diff --git a/adapters/kidoz/kidoz.go b/adapters/kidoz/kidoz.go index 43372dc2f39..d587822e820 100644 --- a/adapters/kidoz/kidoz.go +++ b/adapters/kidoz/kidoz.go @@ -6,7 +6,7 @@ import ( "net/http" "strconv" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -160,7 +160,7 @@ func (a *KidozAdapter) MakeBids(request *openrtb2.BidRequest, _ *adapters.Reques } // Builder builds a new instance of the Kidoz adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &KidozAdapter{ endpoint: config.Endpoint, } diff --git a/adapters/kidoz/kidoz_test.go b/adapters/kidoz/kidoz_test.go index 3fd807ea454..a0b665f304f 100644 --- a/adapters/kidoz/kidoz_test.go +++ b/adapters/kidoz/kidoz_test.go @@ -5,7 +5,7 @@ import ( "net/http" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" @@ -15,7 +15,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderKidoz, config.Adapter{ - Endpoint: "http://example.com/prebid"}) + Endpoint: "http://example.com/prebid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -47,7 +47,7 @@ func makeBidRequest() *openrtb2.BidRequest { func TestMakeRequests(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderKidoz, config.Adapter{ - Endpoint: "http://example.com/prebid"}) + Endpoint: "http://example.com/prebid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -67,7 +67,7 @@ func TestMakeRequests(t *testing.T) { func TestMakeBids(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderKidoz, config.Adapter{ - Endpoint: "http://example.com/prebid"}) + Endpoint: "http://example.com/prebid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/krushmedia/krushmedia.go b/adapters/krushmedia/krushmedia.go index 488fd0fab8d..b5dc0a7ba6f 100644 --- a/adapters/krushmedia/krushmedia.go +++ b/adapters/krushmedia/krushmedia.go @@ -7,7 +7,7 @@ import ( "strconv" "text/template" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -20,7 +20,7 @@ type KrushmediaAdapter struct { } // Builder builds a new instance of the KrushmediaA adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (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) diff --git a/adapters/krushmedia/krushmedia_test.go b/adapters/krushmedia/krushmedia_test.go index 7bdea503569..7ffbc0a9361 100644 --- a/adapters/krushmedia/krushmedia_test.go +++ b/adapters/krushmedia/krushmedia_test.go @@ -11,7 +11,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderKrushmedia, config.Adapter{ - Endpoint: "http://example.com/?c=rtb&m=req&key={{.AccountID}}"}) + Endpoint: "http://example.com/?c=rtb&m=req&key={{.AccountID}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -22,7 +22,7 @@ func TestJsonSamples(t *testing.T) { func TestEndpointTemplateMalformed(t *testing.T) { _, buildErr := Builder(openrtb_ext.BidderKrushmedia, config.Adapter{ - Endpoint: "{{Malformed}}"}) + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Error(t, buildErr) } diff --git a/adapters/kubient/kubient.go b/adapters/kubient/kubient.go index a99e9005105..753f2ebba0e 100644 --- a/adapters/kubient/kubient.go +++ b/adapters/kubient/kubient.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" @@ -14,7 +14,7 @@ import ( ) // Builder builds a new instance of the Kubient adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &KubientAdapter{ endpoint: config.Endpoint, } diff --git a/adapters/kubient/kubient_test.go b/adapters/kubient/kubient_test.go index 19eb3e8ff13..292bb20641a 100644 --- a/adapters/kubient/kubient_test.go +++ b/adapters/kubient/kubient_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderKubient, config.Adapter{ - Endpoint: "http://127.0.0.1:5000/bid"}) + Endpoint: "http://127.0.0.1:5000/bid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/lockerdome/lockerdome.go b/adapters/lockerdome/lockerdome.go index 28c966be6de..6c1626f6588 100644 --- a/adapters/lockerdome/lockerdome.go +++ b/adapters/lockerdome/lockerdome.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -150,7 +150,7 @@ func (adapter *LockerDomeAdapter) MakeBids(openRTBRequest *openrtb2.BidRequest, } // Builder builds a new instance of the LockerDome adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &LockerDomeAdapter{ endpoint: config.Endpoint, } diff --git a/adapters/lockerdome/lockerdome_test.go b/adapters/lockerdome/lockerdome_test.go index 6ac495d5d7c..1f807044b27 100644 --- a/adapters/lockerdome/lockerdome_test.go +++ b/adapters/lockerdome/lockerdome_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderLockerDome, config.Adapter{ - Endpoint: "https://lockerdome.com/ladbid/prebidserver/openrtb2"}) + Endpoint: "https://lockerdome.com/ladbid/prebidserver/openrtb2"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/lockerdome/params_test.go b/adapters/lockerdome/params_test.go index 815246571e3..c2c9185e374 100644 --- a/adapters/lockerdome/params_test.go +++ b/adapters/lockerdome/params_test.go @@ -8,7 +8,7 @@ import ( ) // This file tests static/bidder-params/lockerdome.json -// and validates the format of the external API: request.imp[i].ext.lockerdome +// and validates the format of the external API: request.imp[i].ext.prebid.bidder.lockerdome // TestValidParams makes sure that the LockerDome schema accepts all imp.ext fields which we intend to support. func TestValidParams(t *testing.T) { diff --git a/adapters/logicad/logicad.go b/adapters/logicad/logicad.go index 982723d0d0a..17439ad8c75 100644 --- a/adapters/logicad/logicad.go +++ b/adapters/logicad/logicad.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -116,7 +116,7 @@ func createBidRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext return &bidRequest } -//MakeBids translates Logicad bid response to prebid-server specific format +// MakeBids translates Logicad bid response to prebid-server specific format func (adapter *LogicadAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil @@ -146,11 +146,14 @@ func (adapter *LogicadAdapter) MakeBids(internalRequest *openrtb2.BidRequest, ex BidType: openrtb_ext.BidTypeBanner, }) } + if bidResp.Cur != "" { + bidResponse.Currency = bidResp.Cur + } return bidResponse, nil } // Builder builds a new instance of the Logicad adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &LogicadAdapter{ endpoint: config.Endpoint, } diff --git a/adapters/logicad/logicad_test.go b/adapters/logicad/logicad_test.go index 820aad9751d..98295cc4a28 100644 --- a/adapters/logicad/logicad_test.go +++ b/adapters/logicad/logicad_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderLogicad, config.Adapter{ - Endpoint: "https://localhost/adrequest/prebidserver"}) + Endpoint: "https://localhost/adrequest/prebidserver"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/lunamedia/lunamedia.go b/adapters/lunamedia/lunamedia.go index 5c4852645e6..3852372d672 100644 --- a/adapters/lunamedia/lunamedia.go +++ b/adapters/lunamedia/lunamedia.go @@ -6,7 +6,7 @@ import ( "net/http" "text/template" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -18,7 +18,7 @@ type LunaMediaAdapter struct { EndpointTemplate *template.Template } -//MakeRequests prepares request information for prebid-server core +// MakeRequests prepares request information for prebid-server core func (adapter *LunaMediaAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)) if len(request.Imp) == 0 { @@ -86,7 +86,7 @@ func validateImpression(impExt *openrtb_ext.ExtImpLunaMedia) error { return nil } -//Alter impression info to comply with LunaMedia platform requirements +// Alter impression info to comply with LunaMedia platform requirements func compatImpression(imp *openrtb2.Imp) error { imp.Ext = nil //do not forward ext to LunaMedia platform if imp.Banner != nil { @@ -183,7 +183,7 @@ func (adapter *LunaMediaAdapter) buildEndpointURL(params *openrtb_ext.ExtImpLuna return macros.ResolveMacros(adapter.EndpointTemplate, endpointParams) } -//MakeBids translates LunaMedia bid response to prebid-server specific format +// MakeBids translates LunaMedia bid response to prebid-server specific format func (adapter *LunaMediaAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { var msg = "" if response.StatusCode == http.StatusNoContent { @@ -228,7 +228,7 @@ func getMediaTypeForImpID(impID string, imps []openrtb2.Imp) openrtb_ext.BidType } // Builder builds a new instance of the LunaMedia adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { urlTemplate, err := template.New("endpointTemplate").Parse(config.Endpoint) if err != nil { return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) diff --git a/adapters/lunamedia/lunamedia_test.go b/adapters/lunamedia/lunamedia_test.go index 4149060c809..ef109d92573 100644 --- a/adapters/lunamedia/lunamedia_test.go +++ b/adapters/lunamedia/lunamedia_test.go @@ -11,7 +11,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderLunaMedia, config.Adapter{ - Endpoint: "http://api.lunamedia.io/xp/get?pubid={{.PublisherID}}"}) + Endpoint: "http://api.lunamedia.io/xp/get?pubid={{.PublisherID}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -22,7 +22,7 @@ func TestJsonSamples(t *testing.T) { func TestEndpointTemplateMalformed(t *testing.T) { _, buildErr := Builder(openrtb_ext.BidderLunaMedia, config.Adapter{ - Endpoint: "{{Malformed}}"}) + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Error(t, buildErr) } diff --git a/adapters/madvertise/madvertise.go b/adapters/madvertise/madvertise.go index 6d37ccfcc80..8c3ea10eb51 100644 --- a/adapters/madvertise/madvertise.go +++ b/adapters/madvertise/madvertise.go @@ -6,7 +6,8 @@ import ( "net/http" "text/template" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/adcom1" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -18,7 +19,7 @@ type adapter struct { endpointTemplate *template.Template } -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (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) @@ -149,13 +150,13 @@ func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.R return bidResponse, nil } -func getMediaTypeForBid(attr []openrtb2.CreativeAttribute) openrtb_ext.BidType { +func getMediaTypeForBid(attr []adcom1.CreativeAttribute) openrtb_ext.BidType { for i := 0; i < len(attr); i++ { - if attr[i] == openrtb2.CreativeAttribute(16) { + if attr[i] == adcom1.AttrHasSkipButton { return openrtb_ext.BidTypeVideo - } else if attr[i] == openrtb2.CreativeAttribute(6) { + } else if attr[i] == adcom1.AttrVideoAuto { return openrtb_ext.BidTypeVideo - } else if attr[i] == openrtb2.CreativeAttribute(7) { + } else if attr[i] == adcom1.AttrVideoUser { return openrtb_ext.BidTypeVideo } } diff --git a/adapters/madvertise/madvertise_test.go b/adapters/madvertise/madvertise_test.go index 924f25c1f8e..1d48b6dab3c 100644 --- a/adapters/madvertise/madvertise_test.go +++ b/adapters/madvertise/madvertise_test.go @@ -11,14 +11,14 @@ import ( func TestEndpointTemplateMalformed(t *testing.T) { _, buildErr := Builder(openrtb_ext.BidderMadvertise, config.Adapter{ - Endpoint: "{{Malformed}}"}) + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Error(t, buildErr) } func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderMadvertise, config.Adapter{ - Endpoint: "https://mobile.mng-ads.com/bidrequest{{.ZoneID}}"}) + Endpoint: "https://mobile.mng-ads.com/bidrequest{{.ZoneID}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.NoError(t, buildErr, "Builder returned unexpected error %v", buildErr) diff --git a/adapters/madvertise/params_test.go b/adapters/madvertise/params_test.go index 7c138bf48ac..4b73d57d43e 100644 --- a/adapters/madvertise/params_test.go +++ b/adapters/madvertise/params_test.go @@ -9,7 +9,7 @@ import ( // This file actually intends to test static/bidder-params/Madvertise.json // -// These also validate the format of the external API: request.imp[i].ext.Madvertise +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.madvertise // TestValidParams makes sure that the Madvertise schema accepts all imp.ext fields which we intend to support. func TestValidParams(t *testing.T) { diff --git a/adapters/marsmedia/marsmedia.go b/adapters/marsmedia/marsmedia.go index 65ffff25c15..db5a4d17f32 100644 --- a/adapters/marsmedia/marsmedia.go +++ b/adapters/marsmedia/marsmedia.go @@ -6,7 +6,7 @@ import ( "net/http" "strconv" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -164,7 +164,7 @@ func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { } // Builder builds a new instance of the Marsmedia adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &MarsmediaAdapter{ URI: config.Endpoint, } diff --git a/adapters/marsmedia/marsmedia_test.go b/adapters/marsmedia/marsmedia_test.go index ab87bf773a4..8fe01a72f73 100644 --- a/adapters/marsmedia/marsmedia_test.go +++ b/adapters/marsmedia/marsmedia_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderMarsmedia, config.Adapter{ - Endpoint: "http://bid306.rtbsrv.com/bidder/?bid=f3xtet"}) + Endpoint: "http://bid306.rtbsrv.com/bidder/?bid=f3xtet"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/marsmedia/params_test.go b/adapters/marsmedia/params_test.go index 4a91ed6dcaa..f78ad1c3dc4 100644 --- a/adapters/marsmedia/params_test.go +++ b/adapters/marsmedia/params_test.go @@ -9,7 +9,7 @@ import ( // This file actually intends to test static/bidder-params/marsmedia.json // -// These also validate the format of the external API: request.imp[i].ext.marsmedia +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.marsmedia // TestValidParams makes sure that the Marsmedia schema accepts all imp.ext fields which we intend to support. func TestValidParams(t *testing.T) { diff --git a/adapters/medianet/medianet.go b/adapters/medianet/medianet.go index 091cca2f2b8..3e4f2e74ff6 100644 --- a/adapters/medianet/medianet.go +++ b/adapters/medianet/medianet.go @@ -6,7 +6,7 @@ import ( "net/http" "net/url" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -82,7 +82,7 @@ func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest } // Builder builds a new instance of the Medianet adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { url := buildEndpoint(config.Endpoint, config.ExtraAdapterInfo) return &adapter{ endpoint: url, diff --git a/adapters/medianet/medianet_test.go b/adapters/medianet/medianet_test.go index 82bb19784f7..6403b3f2eb0 100644 --- a/adapters/medianet/medianet_test.go +++ b/adapters/medianet/medianet_test.go @@ -1,9 +1,10 @@ package medianet import ( - "github.com/stretchr/testify/assert" "testing" + "github.com/stretchr/testify/assert" + "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" @@ -13,7 +14,7 @@ func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderMedianet, config.Adapter{ Endpoint: "https://example.media.net/rtb/prebid", ExtraAdapterInfo: "http://localhost:8080/extrnal_url", - }) + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -24,7 +25,7 @@ func TestJsonSamples(t *testing.T) { func TestEndpointTemplateMalformed(t *testing.T) { _, buildErr := Builder(openrtb_ext.BidderMedianet, config.Adapter{ - Endpoint: "{{Malformed}}"}) + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Nil(t, buildErr) } diff --git a/adapters/mgid/mgid.go b/adapters/mgid/mgid.go index 95ede0ab5c4..df4b7b2fd0e 100644 --- a/adapters/mgid/mgid.go +++ b/adapters/mgid/mgid.go @@ -6,7 +6,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -169,7 +169,7 @@ func (a *MgidAdapter) MakeBids(bidReq *openrtb2.BidRequest, unused *adapters.Req } // Builder builds a new instance of the Mgid adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &MgidAdapter{ endpoint: config.Endpoint, } diff --git a/adapters/mgid/mgid_test.go b/adapters/mgid/mgid_test.go index 7d30045168d..fba3d8b09c3 100644 --- a/adapters/mgid/mgid_test.go +++ b/adapters/mgid/mgid_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderMgid, config.Adapter{ - Endpoint: "https://prebid.mgid.com/prebid/"}) + Endpoint: "https://prebid.mgid.com/prebid/"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/mobfoxpb/mobfoxpb.go b/adapters/mobfoxpb/mobfoxpb.go index 7fcf416a480..102dd453fc5 100644 --- a/adapters/mobfoxpb/mobfoxpb.go +++ b/adapters/mobfoxpb/mobfoxpb.go @@ -7,7 +7,7 @@ import ( "strings" "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -29,7 +29,7 @@ type adapter struct { } // Builder builds a new instance of the Mobfox adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &adapter{ URI: config.Endpoint, } diff --git a/adapters/mobfoxpb/mobfoxpb_test.go b/adapters/mobfoxpb/mobfoxpb_test.go index 56ad948bcde..401396adc8d 100644 --- a/adapters/mobfoxpb/mobfoxpb_test.go +++ b/adapters/mobfoxpb/mobfoxpb_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderMobfoxpb, config.Adapter{ - Endpoint: "http://example.com/?c=__route__&m=__method__&key=__key__"}) + Endpoint: "http://example.com/?c=__route__&m=__method__&key=__key__"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) } diff --git a/adapters/mobilefuse/mobilefuse.go b/adapters/mobilefuse/mobilefuse.go index 3437a0f6dba..5d5cad1f3b2 100644 --- a/adapters/mobilefuse/mobilefuse.go +++ b/adapters/mobilefuse/mobilefuse.go @@ -7,7 +7,7 @@ import ( "strconv" "text/template" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -19,8 +19,16 @@ type MobileFuseAdapter struct { EndpointTemplate *template.Template } +type ExtMf struct { + MediaType string `json:"media_type"` +} + +type BidExt struct { + Mf ExtMf `json:"mf"` +} + // Builder builds a new instance of the MobileFuse adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (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) @@ -71,9 +79,12 @@ func (adapter *MobileFuseAdapter) MakeBids(incomingRequest *openrtb2.BidRequest, for _, seatbid := range incomingBidResponse.SeatBid { for i := range seatbid.Bid { + bidType := adapter.getBidType(seatbid.Bid[i]) + seatbid.Bid[i].Ext = nil + outgoingBidResponse.Bids = append(outgoingBidResponse.Bids, &adapters.TypedBid{ Bid: &seatbid.Bid[i], - BidType: adapter.getBidType(seatbid.Bid[i].ImpID, incomingRequest.Imp), + BidType: bidType, }) } } @@ -172,10 +183,6 @@ func (adapter *MobileFuseAdapter) getValidImps(bidRequest *openrtb2.BidRequest, for _, imp := range bidRequest.Imp { if imp.Banner != nil || imp.Video != nil { - if imp.Banner != nil && imp.Video != nil { - imp.Video = nil - } - imp.TagID = strconv.Itoa(ext.PlacementId) imp.Ext = nil validImps = append(validImps, imp) @@ -187,9 +194,14 @@ func (adapter *MobileFuseAdapter) getValidImps(bidRequest *openrtb2.BidRequest, return validImps } -func (adapter *MobileFuseAdapter) getBidType(imp_id string, imps []openrtb2.Imp) openrtb_ext.BidType { - if imps[0].Video != nil { - return openrtb_ext.BidTypeVideo +func (adapter *MobileFuseAdapter) getBidType(bid openrtb2.Bid) openrtb_ext.BidType { + if bid.Ext != nil { + var bidExt BidExt + err := json.Unmarshal(bid.Ext, &bidExt) + + if err == nil && bidExt.Mf.MediaType == "video" { + return openrtb_ext.BidTypeVideo + } } return openrtb_ext.BidTypeBanner diff --git a/adapters/mobilefuse/mobilefuse_test.go b/adapters/mobilefuse/mobilefuse_test.go index 3abe627fab0..09d46faff66 100644 --- a/adapters/mobilefuse/mobilefuse_test.go +++ b/adapters/mobilefuse/mobilefuse_test.go @@ -11,7 +11,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderMobileFuse, config.Adapter{ - Endpoint: "http://mfx.mobilefuse.com/openrtb?pub_id={{.PublisherID}}"}) + Endpoint: "http://mfx.mobilefuse.com/openrtb?pub_id={{.PublisherID}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -22,7 +22,7 @@ func TestJsonSamples(t *testing.T) { func TestEndpointTemplateMalformed(t *testing.T) { _, buildErr := Builder(openrtb_ext.BidderMobileFuse, config.Adapter{ - Endpoint: "{{Malformed}}"}) + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Error(t, buildErr) } diff --git a/adapters/mobilefuse/mobilefusetest/exemplary/multi-format.json b/adapters/mobilefuse/mobilefusetest/exemplary/multi-format.json index d1bff8fee0b..259572d14b1 100644 --- a/adapters/mobilefuse/mobilefusetest/exemplary/multi-format.json +++ b/adapters/mobilefuse/mobilefusetest/exemplary/multi-format.json @@ -50,6 +50,17 @@ } ] }, + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 320, + "h": 480 + }, "tagid": "123456" } ] diff --git a/adapters/mobilefuse/mobilefusetest/exemplary/multi-imps.json b/adapters/mobilefuse/mobilefusetest/exemplary/multi-imps.json index 8a35088755d..568e51af61b 100644 --- a/adapters/mobilefuse/mobilefusetest/exemplary/multi-imps.json +++ b/adapters/mobilefuse/mobilefusetest/exemplary/multi-imps.json @@ -77,7 +77,12 @@ "adm": "some-test-ad", "crid": "test-crid", "h": 50, - "w": 320 + "w": 320, + "ext": { + "mf": { + "media_type": "banner" + } + } } ] } diff --git a/adapters/mobilefuse/mobilefusetest/exemplary/simple-banner.json b/adapters/mobilefuse/mobilefusetest/exemplary/simple-banner.json index b46b847ddc9..b70c2801755 100644 --- a/adapters/mobilefuse/mobilefusetest/exemplary/simple-banner.json +++ b/adapters/mobilefuse/mobilefusetest/exemplary/simple-banner.json @@ -60,7 +60,12 @@ "adm": "some-test-ad", "crid": "test-crid", "h": 50, - "w": 320 + "w": 320, + "ext": { + "mf": { + "media_type": "banner" + } + } } ] } diff --git a/adapters/mobilefuse/mobilefusetest/exemplary/simple-video.json b/adapters/mobilefuse/mobilefusetest/exemplary/simple-video.json index 6515317a005..b290652ae8f 100644 --- a/adapters/mobilefuse/mobilefusetest/exemplary/simple-video.json +++ b/adapters/mobilefuse/mobilefusetest/exemplary/simple-video.json @@ -65,7 +65,12 @@ "adm": "some-test-ad", "crid": "test-crid", "w": 320, - "h": 480 + "h": 480, + "ext": { + "mf": { + "media_type": "video" + } + } } ] } diff --git a/adapters/nanointeractive/nanointeractive.go b/adapters/nanointeractive/nanointeractive.go index a2ec89b0d5b..9e5c2f416f6 100644 --- a/adapters/nanointeractive/nanointeractive.go +++ b/adapters/nanointeractive/nanointeractive.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -156,7 +156,7 @@ func checkImp(imp *openrtb2.Imp) (string, error) { } // Builder builds a new instance of the NanoInteractive adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &NanoInteractiveAdapter{ endpoint: config.Endpoint, } diff --git a/adapters/nanointeractive/nanointeractive_test.go b/adapters/nanointeractive/nanointeractive_test.go index d0955511f8b..fb108b8dd58 100644 --- a/adapters/nanointeractive/nanointeractive_test.go +++ b/adapters/nanointeractive/nanointeractive_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderNanoInteractive, config.Adapter{ - Endpoint: "https://ad.audiencemanager.de/hbs"}) + Endpoint: "https://ad.audiencemanager.de/hbs"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/nanointeractive/params_test.go b/adapters/nanointeractive/params_test.go index b290f3d94b1..a50425c770b 100644 --- a/adapters/nanointeractive/params_test.go +++ b/adapters/nanointeractive/params_test.go @@ -9,7 +9,7 @@ import ( // This file actually intends to test static/bidder-params/nanointeractive.json // -// These also validate the format of the external API: request.imp[i].ext.nanointeracive +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.nanointeracive // TestValidParams makes sure that the NanoInteractive schema accepts all imp.ext fields which we intend to support. func TestValidParams(t *testing.T) { diff --git a/adapters/nextmillennium/nextmillennium.go b/adapters/nextmillennium/nextmillennium.go index bcb0b287d77..d7e0bfbca99 100644 --- a/adapters/nextmillennium/nextmillennium.go +++ b/adapters/nextmillennium/nextmillennium.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -16,19 +16,17 @@ 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"` +type nmExtPrebidStoredRequest struct { + ID string `json:"id"` +} +type nmExtPrebid struct { + StoredRequest nmExtPrebidStoredRequest `json:"storedrequest"` +} +type nextMillJsonExt struct { + Prebid nmExtPrebid `json:"prebid"` } -//MakeRequests prepares request information for prebid-server core +// 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 { @@ -98,12 +96,8 @@ func (adapter *adapter) buildAdapterRequest(prebidBidRequest *openrtb2.BidReques Headers: headers}, nil } -func createBidRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ImpExtNextMillennium) *NextMillenniumBidRequest { +func createBidRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ImpExtNextMillennium) *openrtb2.BidRequest { placementID := params.PlacementID - bidRequest := NextMillenniumBidRequest{ - ID: prebidBidRequest.ID, - Test: uint8(prebidBidRequest.Test), - } if params.GroupID != "" { domain := "" @@ -126,12 +120,19 @@ func createBidRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext placementID = fmt.Sprintf("g%s;%s;%s", params.GroupID, size, domain) } - - bidRequest.Ext.Prebid.StoredRequest.ID = placementID + ext := nextMillJsonExt{} + ext.Prebid.StoredRequest.ID = placementID + jsonExt, err := json.Marshal(ext) + if err != nil { + return prebidBidRequest + } + bidRequest := *prebidBidRequest + bidRequest.Ext = jsonExt + bidRequest.Imp[0].Ext = jsonExt return &bidRequest } -//MakeBids translates NextMillennium bid response to prebid-server specific format +// 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 { @@ -167,7 +168,7 @@ func (adapter *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalR } // 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) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { return &adapter{ endpoint: config.Endpoint, }, nil diff --git a/adapters/nextmillennium/nextmillennium_test.go b/adapters/nextmillennium/nextmillennium_test.go index c5573bb3272..bd75596691f 100644 --- a/adapters/nextmillennium/nextmillennium_test.go +++ b/adapters/nextmillennium/nextmillennium_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderNextMillennium, config.Adapter{ - Endpoint: "https://pbs.nextmillmedia.com/openrtb2/auction"}) + Endpoint: "https://pbs.nextmillmedia.com/openrtb2/auction"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-empty-group-id.json b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-empty-group-id.json index dd9ee32d8ad..7a628d0fd91 100644 --- a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-empty-group-id.json +++ b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-empty-group-id.json @@ -38,7 +38,31 @@ "id": "7819" } } - } + }, + "imp": [ + { + "banner": { + "format": [ + { + "h": 250, + "w": 320 + }, + { + "h": 300, + "w": 320 + } + ] + }, + "ext": { + "prebid": { + "storedrequest": { + "id": "7819" + } + } + }, + "id": "testimpid" + } + ] } }, "mockResponse": { diff --git a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-group-id.json b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-group-id.json index 72d22b280c8..3538113014d 100644 --- a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-group-id.json +++ b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-group-id.json @@ -49,6 +49,42 @@ "id": "g7819;320x250;www.example.com" } } + }, + "imp": [ + { + "banner": { + "format": [ + { + "h": 250, + "w": 320 + }, + { + "h": 300, + "w": 320 + } + ], + "h": 250, + "w": 320 + }, + "ext": { + "prebid": { + "storedrequest": { + "id": "g7819;320x250;www.example.com" + } + } + }, + "id": "testimpid" + } + ], + "site": { + "domain": "www.example.com", + "ext": { + "amp": 0 + }, + "page": "http://www.example.com", + "publisher": { + "domain": "example.com" + } } } }, diff --git a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-group-id_app.json b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-group-id_app.json index 6633a063cc8..25c1f36deff 100644 --- a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-group-id_app.json +++ b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-group-id_app.json @@ -36,13 +36,42 @@ "uri": "https://pbs.nextmillmedia.com/openrtb2/auction", "body":{ "id": "testid", + "app": { + "domain": "www.example.com" + }, "ext": { "prebid": { "storedrequest": { "id": "g7819;320x250;www.example.com" } } - } + }, + "imp": [ + { + "banner": { + "format": [ + { + "h": 250, + "w": 320 + }, + { + "h": 300, + "w": 320 + } + ], + "h": 250, + "w": 320 + }, + "ext": { + "prebid": { + "storedrequest": { + "id": "g7819;320x250;www.example.com" + } + } + }, + "id": "testimpid" + } + ] } }, "mockResponse": { diff --git a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-only-width.json b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-only-width.json index 2dc1f3b6469..79c9894f424 100644 --- a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-only-width.json +++ b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-only-width.json @@ -25,13 +25,31 @@ "uri": "https://pbs.nextmillmedia.com/openrtb2/auction", "body":{ "id": "testid", + "app": { + "domain": "www.example.com" + }, "ext": { "prebid": { "storedrequest": { "id": "g7819;;www.example.com" } } - } + }, + "imp": [ + { + "banner": { + "w": 320 + }, + "ext": { + "prebid": { + "storedrequest": { + "id": "g7819;;www.example.com" + } + } + }, + "id": "testimpid" + } + ] } }, "mockResponse": { diff --git a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-wh.json b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-wh.json index 9e3385fd901..6af7816eb21 100644 --- a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-wh.json +++ b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-wh.json @@ -26,13 +26,32 @@ "uri": "https://pbs.nextmillmedia.com/openrtb2/auction", "body":{ "id": "testid", + "app": { + "domain": "www.example.com" + }, "ext": { "prebid": { "storedrequest": { "id": "g7819;320x250;www.example.com" } } - } + }, + "imp": [ + { + "banner": { + "h": 250, + "w": 320 + }, + "ext": { + "prebid": { + "storedrequest": { + "id": "g7819;320x250;www.example.com" + } + } + }, + "id": "testimpid" + } + ] } }, "mockResponse": { diff --git a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-wo-domain.json b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-wo-domain.json index 6a1694bac68..181dab3548b 100644 --- a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-wo-domain.json +++ b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-wo-domain.json @@ -39,7 +39,33 @@ "id": "g7819;320x250;" } } - } + }, + "imp": [ + { + "banner": { + "format": [ + { + "h": 250, + "w": 320 + }, + { + "h": 300, + "w": 320 + } + ], + "h": 250, + "w": 320 + }, + "ext": { + "prebid": { + "storedrequest": { + "id": "g7819;320x250;" + } + } + }, + "id": "testimpid" + } + ] } }, "mockResponse": { diff --git a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-wo-size.json b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-wo-size.json index 9da3ccf965c..f2422033b81 100644 --- a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-wo-size.json +++ b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-wo-size.json @@ -37,7 +37,31 @@ "id": "g7819;320x250;" } } - } + }, + "imp": [ + { + "banner": { + "format": [ + { + "h": 250, + "w": 320 + }, + { + "h": 300, + "w": 320 + } + ] + }, + "ext": { + "prebid": { + "storedrequest": { + "id": "g7819;320x250;" + } + } + }, + "id": "testimpid" + } + ] } }, "mockResponse": { diff --git a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner.json b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner.json index ff6386d3c89..fa8834fa998 100644 --- a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner.json +++ b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner.json @@ -39,7 +39,33 @@ "id": "7819" } } - } + }, + "imp": [ + { + "banner": { + "format": [ + { + "h": 250, + "w": 320 + }, + { + "h": 300, + "w": 320 + } + ], + "h": 250, + "w": 320 + }, + "ext": { + "prebid": { + "storedrequest": { + "id": "7819" + } + } + }, + "id": "testimpid" + } + ] } }, "mockResponse": { diff --git a/adapters/nextmillennium/nextmillenniumtest/exemplary/empty-banner-obj.json b/adapters/nextmillennium/nextmillenniumtest/exemplary/empty-banner-obj.json index 52f53bdbf10..cc9f9eb980b 100644 --- a/adapters/nextmillennium/nextmillenniumtest/exemplary/empty-banner-obj.json +++ b/adapters/nextmillennium/nextmillenniumtest/exemplary/empty-banner-obj.json @@ -26,7 +26,20 @@ "id": "g7819;;" } } - } + }, + "imp": [ + { + "banner": {}, + "ext": { + "prebid": { + "storedrequest": { + "id": "g7819;;" + } + } + }, + "id": "testimpid" + } + ] } }, "mockResponse": { diff --git a/adapters/nextmillennium/nextmillenniumtest/supplemental/empty-seatbid.json b/adapters/nextmillennium/nextmillenniumtest/supplemental/empty-seatbid.json index 8c344d57214..dd070d62427 100644 --- a/adapters/nextmillennium/nextmillenniumtest/supplemental/empty-seatbid.json +++ b/adapters/nextmillennium/nextmillenniumtest/supplemental/empty-seatbid.json @@ -38,7 +38,33 @@ "id": "7819" } } - } + }, + "imp": [ + { + "banner": { + "format": [ + { + "h": 250, + "w": 320 + }, + { + "h": 300, + "w": 320 + } + ], + "h": 250, + "w": 320 + }, + "ext": { + "prebid": { + "storedrequest": { + "id": "7819" + } + } + }, + "id": "testimpid" + } + ] } }, "mockResponse": { diff --git a/adapters/nextmillennium/nextmillenniumtest/supplemental/error-response.json b/adapters/nextmillennium/nextmillenniumtest/supplemental/error-response.json index 1fa4b195f28..3e8047fae54 100644 --- a/adapters/nextmillennium/nextmillenniumtest/supplemental/error-response.json +++ b/adapters/nextmillennium/nextmillenniumtest/supplemental/error-response.json @@ -34,7 +34,29 @@ "id": "7819" } } - } + }, + "imp": [ + { + "banner": { + "format": [ + { + "h": 300, + "w": 300 + } + ], + "h": 250, + "w": 320 + }, + "ext": { + "prebid": { + "storedrequest": { + "id": "7819" + } + } + }, + "id": "test-imp-id" + } + ] } }, "mockResponse": { diff --git a/adapters/nextmillennium/nextmillenniumtest/supplemental/no-content.json b/adapters/nextmillennium/nextmillenniumtest/supplemental/no-content.json index a12897a4230..c4710a376d0 100644 --- a/adapters/nextmillennium/nextmillenniumtest/supplemental/no-content.json +++ b/adapters/nextmillennium/nextmillenniumtest/supplemental/no-content.json @@ -34,7 +34,29 @@ "id": "7819" } } - } + }, + "imp": [ + { + "banner": { + "format": [ + { + "h": 300, + "w": 300 + } + ], + "h": 250, + "w": 320 + }, + "ext": { + "prebid": { + "storedrequest": { + "id": "7819" + } + } + }, + "id": "test-imp-id" + } + ] } }, "mockResponse": { diff --git a/adapters/ninthdecimal/ninthdecimal.go b/adapters/ninthdecimal/ninthdecimal.go index 17d55edb87a..d39b6232879 100755 --- a/adapters/ninthdecimal/ninthdecimal.go +++ b/adapters/ninthdecimal/ninthdecimal.go @@ -6,7 +6,7 @@ import ( "net/http" "text/template" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -18,7 +18,7 @@ type NinthDecimalAdapter struct { EndpointTemplate *template.Template } -//MakeRequests prepares request information for prebid-server core +// MakeRequests prepares request information for prebid-server core func (adapter *NinthDecimalAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)) if len(request.Imp) == 0 { @@ -86,7 +86,7 @@ func validateImpression(impExt *openrtb_ext.ExtImpNinthDecimal) error { return nil } -//Alter impression info to comply with NinthDecimal platform requirements +// Alter impression info to comply with NinthDecimal platform requirements func compatImpression(imp *openrtb2.Imp) error { imp.Ext = nil //do not forward ext to NinthDecimal platform if imp.Banner != nil { @@ -183,7 +183,7 @@ func (adapter *NinthDecimalAdapter) buildEndpointURL(params *openrtb_ext.ExtImpN return macros.ResolveMacros(adapter.EndpointTemplate, endpointParams) } -//MakeBids translates NinthDecimal bid response to prebid-server specific format +// MakeBids translates NinthDecimal bid response to prebid-server specific format func (adapter *NinthDecimalAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { var msg = "" if response.StatusCode == http.StatusNoContent { @@ -228,7 +228,7 @@ func getMediaTypeForImpID(impID string, imps []openrtb2.Imp) openrtb_ext.BidType } // Builder builds a new instance of the NinthDecimal adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (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) diff --git a/adapters/ninthdecimal/ninthdecimal_test.go b/adapters/ninthdecimal/ninthdecimal_test.go index ccb8114f8a9..8932ac22f58 100755 --- a/adapters/ninthdecimal/ninthdecimal_test.go +++ b/adapters/ninthdecimal/ninthdecimal_test.go @@ -11,7 +11,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderNinthDecimal, config.Adapter{ - Endpoint: "http://rtb.ninthdecimal.com/xp/get?pubid={{.PublisherID}}"}) + Endpoint: "http://rtb.ninthdecimal.com/xp/get?pubid={{.PublisherID}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -22,7 +22,7 @@ func TestJsonSamples(t *testing.T) { func TestEndpointTemplateMalformed(t *testing.T) { _, buildErr := Builder(openrtb_ext.BidderNinthDecimal, config.Adapter{ - Endpoint: "{{Malformed}}"}) + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Error(t, buildErr) } diff --git a/adapters/nobid/nobid.go b/adapters/nobid/nobid.go index f8db812d9ca..afb3b7789ee 100644 --- a/adapters/nobid/nobid.go +++ b/adapters/nobid/nobid.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -18,7 +18,7 @@ type NoBidAdapter struct { } // Builder builds a new instance of the NoBid adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &NoBidAdapter{ endpoint: config.Endpoint, } diff --git a/adapters/nobid/nobid_test.go b/adapters/nobid/nobid_test.go index 674d189d661..a8775b74d18 100644 --- a/adapters/nobid/nobid_test.go +++ b/adapters/nobid/nobid_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderNoBid, config.Adapter{ - Endpoint: "http://ads.servenobid.com/ortb_adreq?tek=pbs"}) + Endpoint: "http://ads.servenobid.com/ortb_adreq?tek=pbs"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/onetag/onetag.go b/adapters/onetag/onetag.go index 30ebf492cab..fd8b991a3aa 100644 --- a/adapters/onetag/onetag.go +++ b/adapters/onetag/onetag.go @@ -6,7 +6,7 @@ import ( "net/http" "text/template" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -18,7 +18,7 @@ type adapter struct { endpointTemplate *template.Template } -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (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) diff --git a/adapters/onetag/onetag_test.go b/adapters/onetag/onetag_test.go index 9f7c8e50115..5550f076a99 100644 --- a/adapters/onetag/onetag_test.go +++ b/adapters/onetag/onetag_test.go @@ -11,14 +11,14 @@ import ( func TestEndpointTemplateMalformed(t *testing.T) { _, buildErr := Builder(openrtb_ext.BidderOneTag, config.Adapter{ - Endpoint: "{{Malformed}}"}) + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Error(t, buildErr) } func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderOneTag, config.Adapter{ - Endpoint: "https://example.com/prebid-server/{{.PublisherID}}"}) + Endpoint: "https://example.com/prebid-server/{{.PublisherID}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.NoError(t, buildErr, "Builder returned unexpected error %v", buildErr) diff --git a/adapters/openrtb_util.go b/adapters/openrtb_util.go index 2816e7305ad..506cd27e297 100644 --- a/adapters/openrtb_util.go +++ b/adapters/openrtb_util.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" ) @@ -34,27 +34,3 @@ func ExtractReqExtBidderParamsMap(bidRequest *openrtb2.BidRequest) (map[string]j return bidderParams, nil } - -func ExtractReqExtBidderParamsEmbeddedMap(bidRequest *openrtb2.BidRequest) (map[string]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 { - if err := json.Unmarshal(bidRequest.Ext, &reqExt); 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 - if err := json.Unmarshal(reqExt.Prebid.BidderParams, &bidderParams); err != nil { - return nil, err - } - - return bidderParams, nil -} diff --git a/adapters/openrtb_util_test.go b/adapters/openrtb_util_test.go index 4a5d33b0bed..85356713adb 100644 --- a/adapters/openrtb_util_test.go +++ b/adapters/openrtb_util_test.go @@ -5,7 +5,7 @@ import ( "errors" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/stretchr/testify/assert" ) @@ -49,53 +49,3 @@ func TestExtractAdapterReqBidderParamsMap(t *testing.T) { }) } } - -func TestExtractReqExtBidderParamsMap(t *testing.T) { - tests := []struct { - name string - givenBidRequest *openrtb2.BidRequest - want map[string]map[string]json.RawMessage - wantErr error - }{ - { - name: "nil req", - givenBidRequest: nil, - want: nil, - wantErr: errors.New("error bidRequest should not be nil"), - }, - { - name: "nil req.ext", - givenBidRequest: &openrtb2.BidRequest{Ext: nil}, - want: nil, - wantErr: nil, - }, - { - name: "malformed req.ext", - givenBidRequest: &openrtb2.BidRequest{Ext: json.RawMessage("malformed")}, - want: nil, - wantErr: errors.New("error decoding Request.ext : invalid character 'm' looking for beginning of value"), - }, - { - name: "nil req.ext.prebid", - givenBidRequest: &openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{}}`)}, - want: nil, - wantErr: nil, - }, - { - name: "extract bidder params from req.Ext for input request before adapter code", - givenBidRequest: &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: nil, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := ExtractReqExtBidderParamsEmbeddedMap(tt.givenBidRequest) - assert.Equal(t, tt.wantErr, err, "err") - assert.Equal(t, tt.want, got, "result") - }) - } -} diff --git a/adapters/openweb/openweb.go b/adapters/openweb/openweb.go index 9c6c48c586b..cd6280d29b3 100644 --- a/adapters/openweb/openweb.go +++ b/adapters/openweb/openweb.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -183,7 +183,7 @@ func validateImpression(imp *openrtb2.Imp) (int, error) { } // 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) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &adapter{ endpoint: config.Endpoint, } diff --git a/adapters/openweb/openweb_test.go b/adapters/openweb/openweb_test.go index 007bcf524f1..6332c409623 100644 --- a/adapters/openweb/openweb_test.go +++ b/adapters/openweb/openweb_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderOpenWeb, config.Adapter{ - Endpoint: "http://ghb.spotim.market/pbs/ortb"}) + Endpoint: "http://ghb.spotim.market/pbs/ortb"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/openweb/params_test.go b/adapters/openweb/params_test.go index 62bdaf9397e..df614df715f 100644 --- a/adapters/openweb/params_test.go +++ b/adapters/openweb/params_test.go @@ -8,7 +8,7 @@ import ( ) // 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 +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.openweb // TestValidParams makes sure that the openweb schema accepts all imp.ext fields which we intend to support. func TestValidParams(t *testing.T) { diff --git a/adapters/openx/openx.go b/adapters/openx/openx.go index 208b06f7c86..2240bd7642c 100644 --- a/adapters/openx/openx.go +++ b/adapters/openx/openx.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -149,7 +149,7 @@ func preprocess(imp *openrtb2.Imp, reqExt *openxReqExt) error { if imp.Video != nil { videoCopy := *imp.Video - if bidderExt.Prebid != nil && bidderExt.Prebid.IsRewardedInventory == 1 { + if bidderExt.Prebid != nil && bidderExt.Prebid.IsRewardedInventory != nil && *bidderExt.Prebid.IsRewardedInventory == 1 { videoCopy.Ext = json.RawMessage(`{"rewarded":1}`) } else { videoCopy.Ext = nil @@ -218,7 +218,7 @@ func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { } // Builder builds a new instance of the Openx adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &OpenxAdapter{ endpoint: config.Endpoint, } diff --git a/adapters/openx/openx_test.go b/adapters/openx/openx_test.go index ea90dc875da..0ff3ce882db 100644 --- a/adapters/openx/openx_test.go +++ b/adapters/openx/openx_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" @@ -14,7 +14,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderOpenx, config.Adapter{ - Endpoint: "http://rtb.openx.net/prebid"}) + Endpoint: "http://rtb.openx.net/prebid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -35,7 +35,7 @@ func TestResponseWithCurrencies(t *testing.T) { func assertCurrencyInBidResponse(t *testing.T, expectedCurrency string, currency *string) { bidder, buildErr := Builder(openrtb_ext.BidderOpenx, config.Adapter{ - Endpoint: "http://rtb.openx.net/prebid"}) + Endpoint: "http://rtb.openx.net/prebid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/openx/params_test.go b/adapters/openx/params_test.go index b7ea970ab1f..94775b57cb0 100644 --- a/adapters/openx/params_test.go +++ b/adapters/openx/params_test.go @@ -9,7 +9,7 @@ import ( // This file actually intends to test static/bidder-params/openx.json // -// These also validate the format of the external API: request.imp[i].ext.openx +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.openx // TestValidParams makes sure that the openx schema accepts all imp.ext fields which we intend to support. func TestValidParams(t *testing.T) { diff --git a/adapters/operaads/operaads.go b/adapters/operaads/operaads.go index 5912807b09f..3dbfe029149 100644 --- a/adapters/operaads/operaads.go +++ b/adapters/operaads/operaads.go @@ -14,7 +14,7 @@ import ( "github.com/prebid/prebid-server/macros" "github.com/prebid/prebid-server/openrtb_ext" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" ) type adapter struct { @@ -27,7 +27,7 @@ var ( ) // 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) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { epTemplate, err := template.New("endpoint").Parse(config.Endpoint) if err != nil { return nil, err diff --git a/adapters/operaads/operaads_test.go b/adapters/operaads/operaads_test.go index eb4280b68e9..fca277fa937 100644 --- a/adapters/operaads/operaads_test.go +++ b/adapters/operaads/operaads_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderOperaads, config.Adapter{ - Endpoint: "http://example.com/operaads/ortb/v2/{{.PublisherID}}?ep={{.AccountID}}"}) + Endpoint: "http://example.com/operaads/ortb/v2/{{.PublisherID}}?ep={{.AccountID}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/operaads/params_test.go b/adapters/operaads/params_test.go index e998127b001..57a60ce9c53 100644 --- a/adapters/operaads/params_test.go +++ b/adapters/operaads/params_test.go @@ -9,7 +9,7 @@ import ( // 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 +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.operaads // TestValidParams makes sure that the operaads schema accepts all imp.ext fields which we intend to support. func TestValidParams(t *testing.T) { diff --git a/adapters/orbidder/orbidder.go b/adapters/orbidder/orbidder.go index f2dda85e290..cb2a1431f3f 100644 --- a/adapters/orbidder/orbidder.go +++ b/adapters/orbidder/orbidder.go @@ -3,13 +3,15 @@ package orbidder import ( "encoding/json" "fmt" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "net/http" + "strings" + + "github.com/prebid/openrtb/v17/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 OrbidderAdapter struct { @@ -139,7 +141,7 @@ func (rcv OrbidderAdapter) MakeBids(_ *openrtb2.BidRequest, _ *adapters.RequestD } // Builder builds a new instance of the Orbidder adapter for the given bidder with the given config. -func Builder(_ openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(_ openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &OrbidderAdapter{ endpoint: config.Endpoint, } diff --git a/adapters/orbidder/orbidder_test.go b/adapters/orbidder/orbidder_test.go index bbda1c06223..3b7d4007f85 100644 --- a/adapters/orbidder/orbidder_test.go +++ b/adapters/orbidder/orbidder_test.go @@ -3,11 +3,12 @@ package orbidder import ( "encoding/json" "errors" - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/stretchr/testify/mock" "testing" + "github.com/prebid/openrtb/v17/openrtb2" + "github.com/stretchr/testify/mock" + + "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" @@ -38,7 +39,7 @@ func TestPreprocessExtensions(t *testing.T) { func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderOrbidder, config.Adapter{ - Endpoint: "https://orbidder-test"}) + Endpoint: "https://orbidder-test"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/orbidder/params_test.go b/adapters/orbidder/params_test.go index 19c4ed8d9d4..2e130f7a9bd 100644 --- a/adapters/orbidder/params_test.go +++ b/adapters/orbidder/params_test.go @@ -2,13 +2,14 @@ package orbidder import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/openrtb_ext" ) // This file actually intends to test static/bidder-params/orbidder.json // -// These also validate the format of the external API: request.imp[i].ext.orbidder +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.orbidder // TestValidParams makes sure that the orbidder schema accepts all imp.ext fields which we intend to support. func TestValidParams(t *testing.T) { diff --git a/adapters/outbrain/outbrain.go b/adapters/outbrain/outbrain.go index 6b121cb4732..a01b535afbc 100644 --- a/adapters/outbrain/outbrain.go +++ b/adapters/outbrain/outbrain.go @@ -5,9 +5,9 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/native1" - nativeResponse "github.com/mxmCherry/openrtb/v15/native1/response" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/native1" + nativeResponse "github.com/prebid/openrtb/v17/native1/response" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -19,7 +19,7 @@ type adapter struct { } // Builder builds a new instance of the Outbrain adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &adapter{ endpoint: config.Endpoint, } diff --git a/adapters/outbrain/outbrain_test.go b/adapters/outbrain/outbrain_test.go index 533bad388ce..9e667dae83a 100644 --- a/adapters/outbrain/outbrain_test.go +++ b/adapters/outbrain/outbrain_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderOutbrain, config.Adapter{ - Endpoint: "http://example.com/bid"}) + Endpoint: "http://example.com/bid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/pangle/pangle.go b/adapters/pangle/pangle.go index da86a904e5c..5bb889a6087 100644 --- a/adapters/pangle/pangle.go +++ b/adapters/pangle/pangle.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -38,7 +38,7 @@ type bidExt struct { /* Builder */ -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &adapter{ Endpoint: config.Endpoint, } @@ -51,7 +51,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters func getAdType(imp openrtb2.Imp, parsedImpExt *wrappedExtImpBidder) int { // video if imp.Video != nil { - if parsedImpExt != nil && parsedImpExt.Prebid != nil && parsedImpExt.Prebid.IsRewardedInventory == 1 { + if parsedImpExt != nil && parsedImpExt.Prebid != nil && parsedImpExt.Prebid.IsRewardedInventory != nil && *parsedImpExt.Prebid.IsRewardedInventory == 1 { return 7 } if imp.Instl == 1 { diff --git a/adapters/pangle/pangle_test.go b/adapters/pangle/pangle_test.go index 89d5eee56c3..3653a60c81c 100644 --- a/adapters/pangle/pangle_test.go +++ b/adapters/pangle/pangle_test.go @@ -12,7 +12,7 @@ func TestJsonSamples(t *testing.T) { conf := config.Adapter{ Endpoint: "https://pangle.io/api/get_ads", } - bidder, buildErr := Builder(openrtb_ext.BidderPangle, conf) + bidder, buildErr := Builder(openrtb_ext.BidderPangle, conf, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) } diff --git a/adapters/pangle/pangletest/exemplary/app_video_rewarded.json b/adapters/pangle/pangletest/exemplary/app_video_rewarded.json index d23dff1e516..0a484ae06d1 100644 --- a/adapters/pangle/pangletest/exemplary/app_video_rewarded.json +++ b/adapters/pangle/pangletest/exemplary/app_video_rewarded.json @@ -75,9 +75,7 @@ "ext": { "adtype": 7, "prebid": { - "bidder": null, - "is_rewarded_inventory": 1, - "storedrequest": null + "is_rewarded_inventory": 1 }, "is_prebid": true, "bidder": { diff --git a/adapters/pangle/pangletest/supplemental/appid_placementid_check.json b/adapters/pangle/pangletest/supplemental/appid_placementid_check.json index 4808cb2bbb9..9f021496412 100644 --- a/adapters/pangle/pangletest/supplemental/appid_placementid_check.json +++ b/adapters/pangle/pangletest/supplemental/appid_placementid_check.json @@ -77,9 +77,7 @@ "ext": { "adtype": 7, "prebid": { - "bidder": null, - "is_rewarded_inventory": 1, - "storedrequest": null + "is_rewarded_inventory": 1 }, "is_prebid": true, "networkids": { diff --git a/adapters/playwire/playwire.go b/adapters/playwire/playwire.go index f0ec7817da2..c14d6cb1e2d 100644 --- a/adapters/playwire/playwire.go +++ b/adapters/playwire/playwire.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -154,7 +154,7 @@ func (a *PlaywireAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa } // Builder builds a new instance of the Playwire adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &PlaywireAdapter{ endpoint: config.Endpoint, } diff --git a/adapters/playwire_ortb/playwire_ortb.go b/adapters/playwire_ortb/playwire_ortb.go index a1b7bacbac3..63d9578aeae 100644 --- a/adapters/playwire_ortb/playwire_ortb.go +++ b/adapters/playwire_ortb/playwire_ortb.go @@ -7,7 +7,7 @@ import ( "strconv" "strings" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -188,7 +188,7 @@ func validateVideoParams(video *openrtb2.Video) (err error) { } // Builder builds a new instance of the PlaywireOrtb adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &PlaywireOrtbAdapter{ URI: config.Endpoint, } diff --git a/adapters/pubmatic/params_test.go b/adapters/pubmatic/params_test.go index c8a300b9910..a5a7773f7af 100644 --- a/adapters/pubmatic/params_test.go +++ b/adapters/pubmatic/params_test.go @@ -9,7 +9,7 @@ import ( // This file actually intends to test static/bidder-params/pubmatic.json // -// These also validate the format of the external API: request.imp[i].ext.pubmatic +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.pubmatic // TestValidParams makes sure that the pubmatic schema accepts all imp.ext fields which we intend to support. func TestValidParams(t *testing.T) { diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go index 00c9c906f96..5040622de39 100644 --- a/adapters/pubmatic/pubmatic.go +++ b/adapters/pubmatic/pubmatic.go @@ -2,13 +2,15 @@ package pubmatic import ( "encoding/json" + "errors" "fmt" "net/http" "strconv" "strings" + "github.com/buger/jsonparser" "github.com/golang/glog" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -22,8 +24,10 @@ type PubmaticAdapter struct { } type pubmaticBidExt struct { - BidType *int `json:"BidType,omitempty"` - VideoCreativeInfo *pubmaticBidExtVideo `json:"video,omitempty"` + BidType *int `json:"BidType,omitempty"` + VideoCreativeInfo *pubmaticBidExtVideo `json:"video,omitempty"` + Marketplace string `json:"marketplace,omitempty"` + PrebidDealPriority int `json:"prebiddealpriority,omitempty"` } type pubmaticWrapperExt struct { @@ -37,12 +41,7 @@ type pubmaticBidExtVideo struct { type ExtImpBidderPubmatic struct { adapters.ExtImpBidder - Data *ExtData `json:"data,omitempty"` -} - -type ExtData struct { - AdServer *ExtAdServer `json:"adserver"` - PBAdSlot string `json:"pbadslot"` + Data json.RawMessage `json:"data,omitempty"` } type ExtAdServer struct { @@ -50,26 +49,39 @@ type ExtAdServer struct { AdSlot string `json:"adslot"` } +type marketplaceReqExt struct { + AllowedBidders []string `json:"allowedbidders,omitempty"` +} + +type extRequestAdServer struct { + Wrapper *pubmaticWrapperExt `json:"wrapper,omitempty"` + Acat []string `json:"acat,omitempty"` + Marketplace *marketplaceReqExt `json:"marketplace,omitempty"` + openrtb_ext.ExtRequest +} + const ( dctrKeyName = "key_val" pmZoneIDKeyName = "pmZoneId" pmZoneIDKeyNameOld = "pmZoneID" ImpExtAdUnitKey = "dfp_ad_unit_code" AdServerGAM = "gam" + AdServerKey = "adserver" + PBAdslotKey = "pbadslot" ) func (a *PubmaticAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)) pubID := "" - var wrapperExt *pubmaticWrapperExt extractWrapperExtFromImp := true extractPubIDFromImp := true - wrapperExt, acat, err := extractPubmaticExtFromRequest(request) + newReqExt, err := extractPubmaticExtFromRequest(request) if err != nil { return nil, []error{err} } + wrapperExt := newReqExt.Wrapper if wrapperExt != nil && wrapperExt.ProfileID != 0 && wrapperExt.VersionID != 0 { extractWrapperExtFromImp = false } @@ -114,20 +126,12 @@ func (a *PubmaticAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ad return nil, errs } - reqExt := make(map[string]interface{}) - if len(acat) > 0 { - reqExt["acat"] = acat - } - if wrapperExt != nil { - reqExt["wrapper"] = wrapperExt - } - if len(reqExt) > 0 { - rawExt, err := json.Marshal(reqExt) - if err != nil { - return nil, []error{err} - } - request.Ext = rawExt + newReqExt.Wrapper = wrapperExt + rawExt, err := json.Marshal(newReqExt) + if err != nil { + return nil, []error{err} } + request.Ext = rawExt if request.Site != nil { siteCopy := *request.Site @@ -234,8 +238,8 @@ func parseImpressionObject(imp *openrtb2.Imp, extractWrapperExtFromImp, extractP var pubID string // PubMatic supports banner and video impressions. - if imp.Banner == nil && imp.Video == nil { - return wrapExt, pubID, fmt.Errorf("Invalid MediaType. PubMatic only supports Banner and Video. Ignoring ImpID=%s", imp.ID) + if imp.Banner == nil && imp.Video == nil && imp.Native == nil { + return wrapExt, pubID, fmt.Errorf("invalid MediaType. PubMatic only supports Banner, Video and Native. Ignoring ImpID=%s", imp.ID) } if imp.Audio != nil { @@ -296,12 +300,8 @@ func parseImpressionObject(imp *openrtb2.Imp, extractWrapperExtFromImp, extractP 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 - } + if len(bidderExt.Data) > 0 { + populateFirstPartyDataImpAttributes(bidderExt.Data, extMap) } imp.Ext = nil @@ -316,31 +316,74 @@ func parseImpressionObject(imp *openrtb2.Imp, extractWrapperExtFromImp, extractP } // extractPubmaticExtFromRequest parse the req.ext to fetch wrapper and acat params -func extractPubmaticExtFromRequest(request *openrtb2.BidRequest) (*pubmaticWrapperExt, []string, error) { - var acat []string - var wrpExt *pubmaticWrapperExt - reqExtBidderParams, err := adapters.ExtractReqExtBidderParamsMap(request) +func extractPubmaticExtFromRequest(request *openrtb2.BidRequest) (extRequestAdServer, error) { + // req.ext.prebid would always be there and Less nil cases to handle, more safe! + var pmReqExt extRequestAdServer + + if request == nil || len(request.Ext) == 0 { + return pmReqExt, nil + } + + reqExt := &openrtb_ext.ExtRequest{} + err := json.Unmarshal(request.Ext, &reqExt) if err != nil { - return nil, acat, err + return pmReqExt, fmt.Errorf("error decoding Request.ext : %s", err.Error()) + } + pmReqExt.ExtRequest = *reqExt + + reqExtBidderParams := make(map[string]json.RawMessage) + if reqExt.Prebid.BidderParams != nil { + err = json.Unmarshal(reqExt.Prebid.BidderParams, &reqExtBidderParams) + if err != nil { + return pmReqExt, err + } } //get request ext bidder params if wrapperObj, present := reqExtBidderParams["wrapper"]; present && len(wrapperObj) != 0 { - wrpExt = &pubmaticWrapperExt{} + wrpExt := &pubmaticWrapperExt{} err = json.Unmarshal(wrapperObj, wrpExt) if err != nil { - return nil, acat, err + return pmReqExt, err } + pmReqExt.Wrapper = wrpExt } if acatBytes, ok := reqExtBidderParams["acat"]; ok { + var acat []string err = json.Unmarshal(acatBytes, &acat) + if err != nil { + return pmReqExt, err + } for i := 0; i < len(acat); i++ { acat[i] = strings.TrimSpace(acat[i]) } + pmReqExt.Acat = acat + } + + if allowedBidders := getAlternateBidderCodesFromRequestExt(reqExt); allowedBidders != nil { + pmReqExt.Marketplace = &marketplaceReqExt{AllowedBidders: allowedBidders} + } + + return pmReqExt, nil +} + +func getAlternateBidderCodesFromRequestExt(reqExt *openrtb_ext.ExtRequest) []string { + if reqExt == nil || reqExt.Prebid.AlternateBidderCodes == nil { + return nil + } + + allowedBidders := []string{"pubmatic"} + if reqExt.Prebid.AlternateBidderCodes.Enabled { + if pmABC, ok := reqExt.Prebid.AlternateBidderCodes.Bidders["pubmatic"]; ok && pmABC.Enabled { + if pmABC.AllowedBidderCodes == nil || (len(pmABC.AllowedBidderCodes) == 1 && pmABC.AllowedBidderCodes[0] == "*") { + return []string{"all"} + } + return append(allowedBidders, pmABC.AllowedBidderCodes...) + } } - return wrpExt, acat, err + return allowedBidders } func addKeywordsToExt(keywords []*openrtb_ext.ExtImpPubmaticKeyVal, extMap map[string]interface{}) { @@ -384,31 +427,172 @@ func (a *PubmaticAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa for _, sb := range bidResp.SeatBid { for i := 0; i < len(sb.Bid); i++ { bid := sb.Bid[i] - impVideo := &openrtb_ext.ExtBidPrebidVideo{} - if len(bid.Cat) > 1 { bid.Cat = bid.Cat[0:1] } + typedBid := &adapters.TypedBid{ + Bid: &bid, + BidType: openrtb_ext.BidTypeBanner, + BidVideo: &openrtb_ext.ExtBidPrebidVideo{}, + } + var bidExt *pubmaticBidExt - bidType := openrtb_ext.BidTypeBanner - if err := json.Unmarshal(bid.Ext, &bidExt); err == nil && bidExt != nil { + err := json.Unmarshal(bid.Ext, &bidExt) + if err != nil { + errs = append(errs, err) + } else if bidExt != nil { + typedBid.Seat = openrtb_ext.BidderName(bidExt.Marketplace) + typedBid.BidType = getBidType(bidExt) + if bidExt.PrebidDealPriority > 0 { + typedBid.DealPriority = bidExt.PrebidDealPriority + } + if bidExt.VideoCreativeInfo != nil && bidExt.VideoCreativeInfo.Duration != nil { - impVideo.Duration = *bidExt.VideoCreativeInfo.Duration + typedBid.BidVideo.Duration = *bidExt.VideoCreativeInfo.Duration + } + } + + if typedBid.BidType == openrtb_ext.BidTypeNative { + bid.AdM, err = getNativeAdm(bid.AdM) + if err != nil { + errs = append(errs, err) } - bidType = getBidType(bidExt) } - bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ - Bid: &bid, - BidType: bidType, - BidVideo: impVideo, - }) + bidResponse.Bids = append(bidResponse.Bids, typedBid) } } + if bidResp.Cur != "" { + bidResponse.Currency = bidResp.Cur + } return bidResponse, errs } +func getNativeAdm(adm string) (string, error) { + var err error + nativeAdm := make(map[string]interface{}) + err = json.Unmarshal([]byte(adm), &nativeAdm) + if err != nil { + return adm, errors.New("unable to unmarshal native adm") + } + + // move bid.adm.native to bid.adm + if _, ok := nativeAdm["native"]; ok { + //using jsonparser to avoid marshaling, encode escape, etc. + value, _, _, err := jsonparser.Get([]byte(adm), string(openrtb_ext.BidTypeNative)) + if err != nil { + return adm, errors.New("unable to get native adm") + } + adm = string(value) + } + + return adm, nil +} + +// getMapFromJSON converts JSON to map +func getMapFromJSON(source json.RawMessage) map[string]interface{} { + if source != nil { + dataMap := make(map[string]interface{}) + err := json.Unmarshal(source, &dataMap) + if err == nil { + return dataMap + } + } + return nil +} + +// populateFirstPartyDataImpAttributes will parse imp.ext.data and populate imp extMap +func populateFirstPartyDataImpAttributes(data json.RawMessage, extMap map[string]interface{}) { + + dataMap := getMapFromJSON(data) + + if dataMap == nil { + return + } + + populateAdUnitKey(data, dataMap, extMap) + populateDctrKey(dataMap, extMap) +} + +// populateAdUnitKey parses data object to read and populate DFP adunit key +func populateAdUnitKey(data json.RawMessage, dataMap, extMap map[string]interface{}) { + + if name, err := jsonparser.GetString(data, "adserver", "name"); err == nil && name == AdServerGAM { + if adslot, err := jsonparser.GetString(data, "adserver", "adslot"); err == nil && adslot != "" { + extMap[ImpExtAdUnitKey] = adslot + } + } + + //imp.ext.dfp_ad_unit_code is not set, then check pbadslot in imp.ext.data + if extMap[ImpExtAdUnitKey] == nil && dataMap[PBAdslotKey] != nil { + extMap[ImpExtAdUnitKey] = dataMap[PBAdslotKey].(string) + } +} + +// populateDctrKey reads key-val pairs from imp.ext.data and add it in imp.ext.key_val +func populateDctrKey(dataMap, extMap map[string]interface{}) { + var dctr strings.Builder + + //append dctr key if already present in extMap + if extMap[dctrKeyName] != nil { + dctr.WriteString(extMap[dctrKeyName].(string)) + } + + for key, val := range dataMap { + + //ignore 'pbaslot' and 'adserver' key as they are not targeting keys + if key == PBAdslotKey || key == AdServerKey { + continue + } + + //separate key-val pairs in dctr string by pipe(|) + if dctr.Len() > 0 { + dctr.WriteString("|") + } + + //trimming spaces from key + key = strings.TrimSpace(key) + + switch typedValue := val.(type) { + case string: + if _, err := fmt.Fprintf(&dctr, "%s=%s", key, strings.TrimSpace(typedValue)); err != nil { + continue + } + + case float64, bool: + if _, err := fmt.Fprintf(&dctr, "%s=%v", key, typedValue); err != nil { + continue + } + + case []interface{}: + if valStrArr := getStringArray(typedValue); len(valStrArr) > 0 { + valStr := strings.Join(valStrArr[:], ",") + if _, err := fmt.Fprintf(&dctr, "%s=%s", key, valStr); err != nil { + continue + } + } + } + } + + if dctrStr := dctr.String(); dctrStr != "" { + extMap[dctrKeyName] = strings.TrimSuffix(dctrStr, "|") + } +} + +// getStringArray converts interface of type string array to string array +func getStringArray(array []interface{}) []string { + aString := make([]string, len(array)) + for i, v := range array { + if str, ok := v.(string); ok { + aString[i] = strings.TrimSpace(str) + } else { + return nil + } + } + return aString +} + // getBidType returns the bid type specified in the response bid.ext func getBidType(bidExt *pubmaticBidExt) openrtb_ext.BidType { // setting "banner" as the default bid type @@ -436,7 +620,7 @@ func logf(msg string, args ...interface{}) { } // 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) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &PubmaticAdapter{ URI: config.Endpoint, } diff --git a/adapters/pubmatic/pubmatic_test.go b/adapters/pubmatic/pubmatic_test.go index 4985d052033..9e7a97d2de4 100644 --- a/adapters/pubmatic/pubmatic_test.go +++ b/adapters/pubmatic/pubmatic_test.go @@ -2,9 +2,12 @@ package pubmatic import ( "encoding/json" + "net/http" + "sort" + "strings" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" @@ -14,7 +17,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderPubmatic, config.Adapter{ - Endpoint: "https://hbopenbid.pubmatic.com/translator?source=prebid-server"}) + Endpoint: "https://hbopenbid.pubmatic.com/translator?source=prebid-server"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -157,23 +160,39 @@ func TestExtractPubmaticExtFromRequest(t *testing.T) { request *openrtb2.BidRequest } tests := []struct { - name string - args args - expectedWrapperExt *pubmaticWrapperExt - expectedAcat []string - wantErr bool + name string + args args + expectedReqExt extRequestAdServer + wantErr bool }{ { - name: "Empty bidder param", - wantErr: true, + name: "nil request", + args: args{ + request: nil, + }, + wantErr: false, }, { - name: "Pubmatic wrapper ext missing/empty", + name: "nil req.ext", + args: args{ + request: &openrtb2.BidRequest{Ext: nil}, + }, + wantErr: false, + }, + { + name: "Pubmatic wrapper ext missing/empty (empty bidderparms)", args: args{ request: &openrtb2.BidRequest{ Ext: json.RawMessage(`{"prebid":{"bidderparams":{}}}`), }, }, + expectedReqExt: extRequestAdServer{ + ExtRequest: openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + BidderParams: json.RawMessage("{}"), + }, + }, + }, wantErr: false, }, { @@ -183,14 +202,21 @@ func TestExtractPubmaticExtFromRequest(t *testing.T) { Ext: json.RawMessage(`{"prebid":{"bidderparams":{"wrapper":{"profile":123,"version":456}}}}`), }, }, - expectedWrapperExt: &pubmaticWrapperExt{ProfileID: 123, VersionID: 456}, - wantErr: false, + expectedReqExt: extRequestAdServer{ + Wrapper: &pubmaticWrapperExt{ProfileID: 123, VersionID: 456}, + ExtRequest: openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + BidderParams: json.RawMessage(`{"wrapper":{"profile":123,"version":456}}`), + }, + }, + }, + wantErr: false, }, { name: "Invalid Pubmatic wrapper ext", args: args{ request: &openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"bidderparams":{"wrapper":{"profile":"123","version":456}}}}`), + Ext: json.RawMessage(`{"prebid":{"bidderparams":"}}}`), }, }, wantErr: true, @@ -202,28 +228,59 @@ func TestExtractPubmaticExtFromRequest(t *testing.T) { Ext: json.RawMessage(`{"prebid":{"bidderparams":{"acat":[" drg \t","dlu","ssr"],"wrapper":{"profile":123,"version":456}}}}`), }, }, - expectedWrapperExt: &pubmaticWrapperExt{ProfileID: 123, VersionID: 456}, - expectedAcat: []string{"drg", "dlu", "ssr"}, - wantErr: false, + expectedReqExt: extRequestAdServer{ + Wrapper: &pubmaticWrapperExt{ProfileID: 123, VersionID: 456}, + Acat: []string{"drg", "dlu", "ssr"}, + ExtRequest: openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + BidderParams: json.RawMessage(`{"acat":[" drg \t","dlu","ssr"],"wrapper":{"profile":123,"version":456}}`), + }, + }, + }, + wantErr: false, }, { - name: "Invalid Pubmatic acat ext. We are ok with acat being non nil in this case as we are returning unmarshal error", + name: "Invalid Pubmatic acat ext", args: args{ request: &openrtb2.BidRequest{ Ext: json.RawMessage(`{"prebid":{"bidderparams":{"acat":[1,3,4],"wrapper":{"profile":123,"version":456}}}}`), }, }, - expectedWrapperExt: &pubmaticWrapperExt{ProfileID: 123, VersionID: 456}, - expectedAcat: []string{"", "", ""}, - wantErr: true, + expectedReqExt: extRequestAdServer{ + Wrapper: &pubmaticWrapperExt{ProfileID: 123, VersionID: 456}, + ExtRequest: openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + BidderParams: json.RawMessage(`{"acat":[1,3,4],"wrapper":{"profile":123,"version":456}}`), + }, + }, + }, + wantErr: true, + }, + { + name: "Valid Pubmatic marketplace ext", + args: args{ + request: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":{"pubmatic":{"enabled":true,"allowedbiddercodes":["groupm"]}}},"bidderparams":{"wrapper":{"profile":123,"version":456}}}}`), + }, + }, + expectedReqExt: extRequestAdServer{ + Marketplace: &marketplaceReqExt{AllowedBidders: []string{"pubmatic", "groupm"}}, + Wrapper: &pubmaticWrapperExt{ProfileID: 123, VersionID: 456}, + ExtRequest: openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + BidderParams: json.RawMessage(`{"wrapper":{"profile":123,"version":456}}`), + AlternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{Enabled: true, Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{"pubmatic": {Enabled: true, AllowedBidderCodes: []string{"groupm"}}}}, + }, + }, + }, + wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - gotWrapperExt, gotAcat, err := extractPubmaticExtFromRequest(tt.args.request) + gotReqExt, err := extractPubmaticExtFromRequest(tt.args.request) assert.Equal(t, tt.wantErr, err != nil) - assert.Equal(t, tt.expectedWrapperExt, gotWrapperExt) - assert.Equal(t, tt.expectedAcat, gotAcat) + assert.Equal(t, tt.expectedReqExt, gotReqExt) }) } } @@ -264,3 +321,387 @@ func TestPubmaticAdapter_MakeRequests(t *testing.T) { }) } } + +func TestPubmaticAdapter_MakeBids(t *testing.T) { + type fields struct { + URI string + } + type args struct { + internalRequest *openrtb2.BidRequest + externalRequest *adapters.RequestData + response *adapters.ResponseData + } + tests := []struct { + name string + fields fields + args args + wantErr []error + wantResp *adapters.BidderResponse + }{ + { + name: "happy path, valid response with all bid params", + args: args{ + response: &adapters.ResponseData{ + StatusCode: http.StatusOK, + Body: []byte(`{"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": "testdeal", "ext":{"dspid": 6, "deal_channel": 1, "prebiddealpriority": 1}}]}], "bidid": "5778926625248726496", "cur": "USD"}`), + }, + }, + wantErr: nil, + wantResp: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "7706636740145184841", + ImpID: "test-imp-id", + Price: 0.500000, + AdID: "29681110", + AdM: "some-test-ad", + ADomain: []string{"pubmatic.com"}, + CrID: "29681110", + H: 250, + W: 300, + DealID: "testdeal", + Ext: json.RawMessage(`{"dspid": 6, "deal_channel": 1, "prebiddealpriority": 1}`), + }, + DealPriority: 1, + BidType: openrtb_ext.BidTypeBanner, + BidVideo: &openrtb_ext.ExtBidPrebidVideo{}, + }, + }, + Currency: "USD", + }, + }, + { + name: "ignore invalid prebiddealpriority", + args: args{ + response: &adapters.ResponseData{ + StatusCode: http.StatusOK, + Body: []byte(`{"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": "testdeal", "ext":{"dspid": 6, "deal_channel": 1, "prebiddealpriority": -1}}]}], "bidid": "5778926625248726496", "cur": "USD"}`), + }, + }, + wantErr: nil, + wantResp: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "7706636740145184841", + ImpID: "test-imp-id", + Price: 0.500000, + AdID: "29681110", + AdM: "some-test-ad", + ADomain: []string{"pubmatic.com"}, + CrID: "29681110", + H: 250, + W: 300, + DealID: "testdeal", + Ext: json.RawMessage(`{"dspid": 6, "deal_channel": 1, "prebiddealpriority": -1}`), + }, + BidType: openrtb_ext.BidTypeBanner, + BidVideo: &openrtb_ext.ExtBidPrebidVideo{}, + }, + }, + Currency: "USD", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &PubmaticAdapter{ + URI: tt.fields.URI, + } + gotResp, gotErr := a.MakeBids(tt.args.internalRequest, tt.args.externalRequest, tt.args.response) + assert.Equal(t, tt.wantErr, gotErr, gotErr) + assert.Equal(t, tt.wantResp, gotResp) + }) + } +} + +func Test_getAlternateBidderCodesFromRequest(t *testing.T) { + type args struct { + bidRequest *openrtb2.BidRequest + } + tests := []struct { + name string + args args + want []string + }{ + { + name: "request.ext nil", + args: args{ + bidRequest: &openrtb2.BidRequest{Ext: nil}, + }, + want: nil, + }, + { + name: "alternatebiddercodes not present in request.ext", + args: args{ + bidRequest: &openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{}}`)}, + }, + want: nil, + }, + { + name: "alternatebiddercodes feature disabled", + args: args{ + bidRequest: &openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":false,"bidders":{"pubmatic":{"enabled":true,"allowedbiddercodes":["groupm"]}}}}}`)}, + }, + want: []string{"pubmatic"}, + }, + { + name: "alternatebiddercodes disabled at bidder level", + args: args{ + bidRequest: &openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":{"pubmatic":{"enabled":false,"allowedbiddercodes":["groupm"]}}}}}`)}, + }, + want: []string{"pubmatic"}, + }, + { + name: "alternatebiddercodes list not defined", + args: args{ + bidRequest: &openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":{"pubmatic":{"enabled":true}}}}}`)}, + }, + want: []string{"all"}, + }, + { + name: "wildcard in alternatebiddercodes list", + args: args{ + bidRequest: &openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":{"pubmatic":{"enabled":true,"allowedbiddercodes":["*"]}}}}}`)}, + }, + want: []string{"all"}, + }, + { + name: "empty alternatebiddercodes list", + args: args{ + bidRequest: &openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":{"pubmatic":{"enabled":true,"allowedbiddercodes":[]}}}}}`)}, + }, + want: []string{"pubmatic"}, + }, + { + name: "only groupm in alternatebiddercodes allowed", + args: args{ + bidRequest: &openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":{"pubmatic":{"enabled":true,"allowedbiddercodes":["groupm"]}}}}}`)}, + }, + want: []string{"pubmatic", "groupm"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var reqExt *openrtb_ext.ExtRequest + if len(tt.args.bidRequest.Ext) > 0 { + err := json.Unmarshal(tt.args.bidRequest.Ext, &reqExt) + if err != nil { + t.Errorf("getAlternateBidderCodesFromRequest() = %v", err) + } + } + + got := getAlternateBidderCodesFromRequestExt(reqExt) + assert.ElementsMatch(t, got, tt.want, tt.name) + }) + } +} + +func TestPopulateFirstPartyDataImpAttributes(t *testing.T) { + type args struct { + data json.RawMessage + impExtMap map[string]interface{} + } + tests := []struct { + name string + args args + expectedImpExt map[string]interface{} + }{ + { + name: "Only Targeting present in imp.ext.data", + args: args{ + data: json.RawMessage(`{"sport":["rugby","cricket"]}`), + impExtMap: map[string]interface{}{}, + }, + expectedImpExt: map[string]interface{}{ + "key_val": "sport=rugby,cricket", + }, + }, + { + name: "Targeting and adserver object present in imp.ext.data", + args: args{ + data: json.RawMessage(`{"adserver": {"name": "gam","adslot": "/1111/home"},"pbadslot": "/2222/home","sport":["rugby","cricket"]}`), + impExtMap: map[string]interface{}{}, + }, + expectedImpExt: map[string]interface{}{ + "dfp_ad_unit_code": "/1111/home", + "key_val": "sport=rugby,cricket", + }, + }, + { + name: "Targeting and pbadslot key present in imp.ext.data ", + args: args{ + data: json.RawMessage(`{"pbadslot": "/2222/home","sport":["rugby","cricket"]}`), + impExtMap: map[string]interface{}{}, + }, + expectedImpExt: map[string]interface{}{ + "dfp_ad_unit_code": "/2222/home", + "key_val": "sport=rugby,cricket", + }, + }, + { + name: "Targeting and Invalid Adserver object in imp.ext.data", + args: args{ + data: json.RawMessage(`{"adserver": "invalid","sport":["rugby","cricket"]}`), + impExtMap: map[string]interface{}{}, + }, + expectedImpExt: map[string]interface{}{ + "key_val": "sport=rugby,cricket", + }, + }, + { + name: "key_val already present in imp.ext.data", + args: args{ + data: json.RawMessage(`{"sport":["rugby","cricket"]}`), + impExtMap: map[string]interface{}{ + "key_val": "k1=v1|k2=v2", + }, + }, + expectedImpExt: map[string]interface{}{ + "key_val": "k1=v1|k2=v2|sport=rugby,cricket", + }, + }, + { + name: "int data present in imp.ext.data", + args: args{ + data: json.RawMessage(`{"age": 25}`), + impExtMap: map[string]interface{}{}, + }, + expectedImpExt: map[string]interface{}{ + "key_val": "age=25", + }, + }, + { + name: "float data present in imp.ext.data", + args: args{ + data: json.RawMessage(`{"floor": 0.15}`), + impExtMap: map[string]interface{}{}, + }, + expectedImpExt: map[string]interface{}{ + "key_val": "floor=0.15", + }, + }, + { + name: "bool data present in imp.ext.data", + args: args{ + data: json.RawMessage(`{"k1": true}`), + impExtMap: map[string]interface{}{}, + }, + expectedImpExt: map[string]interface{}{ + "key_val": "k1=true", + }, + }, + { + name: "imp.ext.data is not present", + args: args{ + data: nil, + impExtMap: map[string]interface{}{}, + }, + expectedImpExt: map[string]interface{}{}, + }, + { + name: "string with spaces present in imp.ext.data", + args: args{ + data: json.RawMessage(`{" category ": " cinema "}`), + impExtMap: map[string]interface{}{}, + }, + expectedImpExt: map[string]interface{}{ + "key_val": "category=cinema", + }, + }, + { + name: "string array with spaces present in imp.ext.data", + args: args{ + data: json.RawMessage(`{" country\t": [" India", "\tChina "]}`), + impExtMap: map[string]interface{}{}, + }, + expectedImpExt: map[string]interface{}{ + "key_val": "country=India,China", + }, + }, + { + name: "Invalid data present in imp.ext.data", + args: args{ + data: json.RawMessage(`{"country": [1, "India"],"category":"movies"}`), + impExtMap: map[string]interface{}{}, + }, + expectedImpExt: map[string]interface{}{ + "key_val": "category=movies", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + populateFirstPartyDataImpAttributes(tt.args.data, tt.args.impExtMap) + assert.Equal(t, tt.expectedImpExt, tt.args.impExtMap) + }) + } +} + +func TestPopulateFirstPartyDataImpAttributesForMultipleAttributes(t *testing.T) { + impExtMap := map[string]interface{}{ + "key_val": "k1=v1|k2=v2", + } + data := json.RawMessage(`{"sport":["rugby","cricket"],"pageType":"article","age":30,"floor":1.25}`) + expectedKeyValArr := []string{"age=30", "floor=1.25", "k1=v1", "k2=v2", "pageType=article", "sport=rugby,cricket"} + + populateFirstPartyDataImpAttributes(data, impExtMap) + + //read dctr value and split on "|" for comparison + actualKeyValArr := strings.Split(impExtMap[dctrKeyName].(string), "|") + sort.Strings(actualKeyValArr) + assert.Equal(t, expectedKeyValArr, actualKeyValArr) +} + +func TestGetStringArray(t *testing.T) { + tests := []struct { + name string + input []interface{} + output []string + }{ + { + name: "Valid String Array", + input: append(make([]interface{}, 0), "hello", "world"), + output: []string{"hello", "world"}, + }, + { + name: "Invalid String Array", + input: append(make([]interface{}, 0), 1, 2), + output: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := getStringArray(tt.input) + assert.Equal(t, tt.output, got) + }) + } +} + +func TestGetMapFromJSON(t *testing.T) { + tests := []struct { + name string + input json.RawMessage + output map[string]interface{} + }{ + { + name: "Valid JSON", + input: json.RawMessage(`{"buyid":"testBuyId"}`), + output: map[string]interface{}{ + "buyid": "testBuyId", + }, + }, + { + name: "Invalid JSON", + input: json.RawMessage(`{"buyid":}`), + output: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := getMapFromJSON(tt.input) + assert.Equal(t, tt.output, got) + }) + } +} diff --git a/adapters/pubmatic/pubmatictest/exemplary/banner.json b/adapters/pubmatic/pubmatictest/exemplary/banner.json index 5012192a4ea..74150f5aa83 100644 --- a/adapters/pubmatic/pubmatictest/exemplary/banner.json +++ b/adapters/pubmatic/pubmatictest/exemplary/banner.json @@ -89,7 +89,12 @@ "profile": 5123, "version":1 }, - "acat": ["drg","dlu","ssr"] + "acat": ["drg","dlu","ssr"], + "prebid": { + "bidderparams": { + "acat": ["drg","dlu","ssr"] + } + } } } }, @@ -113,7 +118,8 @@ "dealid":"test deal", "ext": { "dspid": 6, - "deal_channel": 1 + "deal_channel": 1, + "prebiddealpriority": 1 } }] } @@ -143,7 +149,8 @@ "dealid":"test deal", "ext": { "dspid": 6, - "deal_channel": 1 + "deal_channel": 1, + "prebiddealpriority": 1 } }, "type": "banner" diff --git a/adapters/pubmatic/pubmatictest/exemplary/native.json b/adapters/pubmatic/pubmatictest/exemplary/native.json new file mode 100644 index 00000000000..8d44d194d8d --- /dev/null +++ b/adapters/pubmatic/pubmatictest/exemplary/native.json @@ -0,0 +1,115 @@ +{ + "mockBidRequest": { + "id": "test-native-request", + "imp": [ + { + "id": "test-native-imp", + "native": { + "request": "{\"assets\":[{\"id\":1,\"img\":{\"ext\":{\"image1\":\"image2\"},\"h\": 250,\"mimes\":[\"image\/gif\",\"image\/png\"],\"type\":3,\"w\":300},\"required\":1}]}" + }, + "ext": { + "bidder": { + "publisherId": "999", + "wrapper": { + "version": 1, + "profile": 5123 + } + } + } + } + ], + "ext": {}, + "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-native-request", + "imp": [ + { + "id": "test-native-imp", + "native": { + "request": "{\"assets\":[{\"id\":1,\"img\":{\"ext\":{\"image1\":\"image2\"},\"h\": 250,\"mimes\":[\"image\/gif\",\"image\/png\"],\"type\":3,\"w\":300},\"required\":1}]}" + } + } + ], + "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 + }, + "prebid": {} + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-native-request", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "test-native-request", + "impid": "test-native-imp", + "price": 0.500000, + "adid": "some-test-id", + "adm": "{\"native\":{\"assets\":[{\"id\":2,\"img\":{\"h\":90,\"url\":\"//ads.pubmatic.com/AdTag/native/728x90.png\",\"w\":728}},{\"data\":{\"value\":\"Sponsored By PubMatic\"},\"id\":4},{\"id\":3,\"img\":{\"h\":90,\"url\":\"//ads.pubmatic.com/AdTag/native/728x90.png\",\"w\":728}},{\"id\":1,\"title\":{\"text\":\"Native Test Title\"}},{\"data\":{\"value\":\"Sponsored By PubMatic\"},\"id\":5}],\"imptrackers\":[\"http://clicktracker.com/AdTag/9bde02d0-6017-11e4-9df7-005056967c35\"],\"jstracker\":\"\"}", + "ext": { + "prebid": { + "type": "native" + } + } + } + ], + "seat": "123" + } + ], + "bidid": "8141327771600527856", + "cur": "EUR" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "id": "some-id", + "impid": "test-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\": 3000,\"h\": 2250}},{\"id\": 1,\"title\":{\"text\":\"This is an example Prebid Native creative\"}},{\"id\": 3,\"data\":{\"value\":\"Prebid.org\"}},{\"id\": 4,\"data\":{\"value\":\"This is a Prebid Native Creative. There are many like it, but this one is mine.\"}}],\"link\":{\"url\":\"http://example.com/click?AAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwhdYz3ZyNFNG3fXpZUyLXNZ0o5aAAAAACrElgC-AwAAvgMAAAIAAAC98iUEeP4QAAAAAABVU0QAVVNEAAEAAQARIAAAAAABAgQCAAAAAAEAhBaSXgAAAAA./pp=1/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=1\"],\"jstracker\":\"\"}", + "ext": { + "prebid": { + "type": "native" + } + } + }, + "type": "native" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/seedingAlliance/seedingAlliancetest/supplemental/invalid_tag_id.json b/adapters/seedingAlliance/seedingAlliancetest/supplemental/invalid_tag_id.json new file mode 100644 index 00000000000..47d4f1f1aad --- /dev/null +++ b/adapters/seedingAlliance/seedingAlliancetest/supplemental/invalid_tag_id.json @@ -0,0 +1,30 @@ +{ + "expectedMakeRequestsErrors": [ + { + "value": "could not unmarshal openrtb_ext.ImpExtSeedingAlliance: json: cannot unmarshal number into Go struct field ImpExtSeedingAlliance.adUnitID of type string", + "comparison": "literal" + } + ], + "mockBidRequest": { + "id": "test-request-id", + "site": { + "publisher": { + "id": "foo", + "name": "foo" + } + }, + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" + }, + "ext": { + "bidder": { + "adUnitId": 1234 + } + } + } + ] + } +} \ No newline at end of file diff --git a/adapters/seedingAlliance/seedingAlliancetest/supplemental/status_bad_request.json b/adapters/seedingAlliance/seedingAlliancetest/supplemental/status_bad_request.json new file mode 100644 index 00000000000..55f35ccb0f8 --- /dev/null +++ b/adapters/seedingAlliance/seedingAlliancetest/supplemental/status_bad_request.json @@ -0,0 +1,72 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "adUnitId": "example-tag-id" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://mockup.seeding-alliance.de/", + "body": { + "cur": [ + "EUR" + ], + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "example-tag-id", + "ext": { + "bidder": { + "adUnitId": "example-tag-id" + } + } + } + ] + } + }, + "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/seedingAlliance/seedingAlliancetest/supplemental/status_no_content.json b/adapters/seedingAlliance/seedingAlliancetest/supplemental/status_no_content.json new file mode 100644 index 00000000000..f51aa12ed5f --- /dev/null +++ b/adapters/seedingAlliance/seedingAlliancetest/supplemental/status_no_content.json @@ -0,0 +1,66 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "adUnitId": "example-tag-id" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://mockup.seeding-alliance.de/", + "body": { + "cur": [ + "EUR" + ], + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "example-tag-id", + "ext": { + "bidder": { + "adUnitId": "example-tag-id" + } + } + } + ] + } + }, + "mockResponse": { + "status": 204 + } + } + ] +} \ No newline at end of file diff --git a/adapters/seedingAlliance/seedingAlliancetest/supplemental/status_not_ok.json b/adapters/seedingAlliance/seedingAlliancetest/supplemental/status_not_ok.json new file mode 100644 index 00000000000..78d99bc109d --- /dev/null +++ b/adapters/seedingAlliance/seedingAlliancetest/supplemental/status_not_ok.json @@ -0,0 +1,72 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "adUnitId": "example-tag-id" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://mockup.seeding-alliance.de/", + "body": { + "cur": [ + "EUR" + ], + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "example-tag-id", + "ext": { + "bidder": { + "adUnitId": "example-tag-id" + } + } + } + ] + } + }, + "mockResponse": { + "status": 404 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 404. Run with request.debug = 1 for more info.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/sharethrough/sharethrough.go b/adapters/sharethrough/sharethrough.go index 248859bbcf3..4f4bb070fe9 100644 --- a/adapters/sharethrough/sharethrough.go +++ b/adapters/sharethrough/sharethrough.go @@ -6,7 +6,7 @@ import ( "net/http" "strings" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -21,7 +21,7 @@ type adapter struct { } // Builder builds a new instance of the Sharethrough adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &adapter{ endpoint: config.Endpoint, } diff --git a/adapters/sharethrough/sharethrough_test.go b/adapters/sharethrough/sharethrough_test.go index d1ecadc423f..4aab8fc56cc 100644 --- a/adapters/sharethrough/sharethrough_test.go +++ b/adapters/sharethrough/sharethrough_test.go @@ -1,10 +1,11 @@ package sharethrough import ( + "testing" + "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) { @@ -12,7 +13,7 @@ func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderSharethrough, config.Adapter{ Endpoint: "http://whatever.url", - }) + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/silvermob/silvermob.go b/adapters/silvermob/silvermob.go index a2060d25fb5..9cb9172354a 100644 --- a/adapters/silvermob/silvermob.go +++ b/adapters/silvermob/silvermob.go @@ -6,7 +6,7 @@ import ( "net/http" "text/template" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -19,7 +19,7 @@ type SilverMobAdapter struct { } // Builder builds a new instance of the SilverMob adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (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) diff --git a/adapters/silvermob/silvermob_test.go b/adapters/silvermob/silvermob_test.go index 795d58fd834..5b7d60e2ead 100644 --- a/adapters/silvermob/silvermob_test.go +++ b/adapters/silvermob/silvermob_test.go @@ -11,7 +11,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderSilverMob, config.Adapter{ - Endpoint: "http://{{.Host}}.example.com/api/dsp/bid/{{.ZoneID}}"}) + Endpoint: "http://{{.Host}}.example.com/api/dsp/bid/{{.ZoneID}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -23,7 +23,7 @@ func TestJsonSamples(t *testing.T) { func TestEndpointTemplateMalformed(t *testing.T) { _, buildErr := Builder(openrtb_ext.BidderSilverMob, config.Adapter{ - Endpoint: "{{Malformed}}"}) + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Error(t, buildErr) } diff --git a/adapters/smaato/native.go b/adapters/smaato/native.go new file mode 100644 index 00000000000..d0d40d35c57 --- /dev/null +++ b/adapters/smaato/native.go @@ -0,0 +1,27 @@ +package smaato + +import ( + "encoding/json" + "fmt" + "github.com/prebid/prebid-server/errortypes" +) + +type nativeAd struct { + Native json.RawMessage `json:"native"` +} + +func extractAdmNative(adMarkup string) (string, error) { + var nativeAd nativeAd + if err := json.Unmarshal([]byte(adMarkup), &nativeAd); err != nil { + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Invalid ad markup %s.", adMarkup), + } + } + adm, err := json.Marshal(&nativeAd.Native) + if err != nil { + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Invalid ad markup %s.", adMarkup), + } + } + return string(adm), nil +} diff --git a/adapters/smaato/native_test.go b/adapters/smaato/native_test.go new file mode 100644 index 00000000000..7ccabe83df2 --- /dev/null +++ b/adapters/smaato/native_test.go @@ -0,0 +1,42 @@ +package smaato + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestExtractAdmNative(t *testing.T) { + tests := []struct { + testName string + adMarkup string + expectedAdMarkup string + expectedError string + }{ + { + testName: "extract native", + adMarkup: "{\"native\":{\"assets\":[]}}", + expectedAdMarkup: `{"assets":[]}`, + expectedError: "", + }, + { + testName: "invalid adMarkup", + adMarkup: "{", + expectedAdMarkup: "", + expectedError: "Invalid ad markup {.", + }, + } + + for _, tt := range tests { + t.Run(tt.testName, func(t *testing.T) { + adMarkup, err := extractAdmNative(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 c84dd356a59..7981dda20ca 100644 --- a/adapters/smaato/smaato.go +++ b/adapters/smaato/smaato.go @@ -8,7 +8,7 @@ import ( "strings" "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -17,7 +17,7 @@ import ( "github.com/prebid/prebid-server/util/timeutil" ) -const clientVersion = "prebid_server_0.4" +const clientVersion = "prebid_server_0.5" type adMarkupType string @@ -25,6 +25,7 @@ const ( smtAdTypeImg adMarkupType = "Img" smtAdTypeRichmedia adMarkupType = "Richmedia" smtAdTypeVideo adMarkupType = "Video" + smtAdTypeNative adMarkupType = "Native" ) // adapter describes a Smaato prebid server adapter. @@ -65,7 +66,7 @@ type videoExt struct { } // 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) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &adapter{ clock: &timeutil.RealTime{}, endpoint: config.Endpoint, @@ -187,20 +188,29 @@ func (adapter *adapter) makeIndividualRequests(request *openrtb2.BidRequest) ([] } func splitImpressionsByMediaType(imp *openrtb2.Imp) ([]openrtb2.Imp, error) { - if imp.Banner == nil && imp.Video == nil { - return nil, &errortypes.BadInput{Message: "Invalid MediaType. Smaato only supports Banner and Video."} + if imp.Banner == nil && imp.Video == nil && imp.Native == nil { + return nil, &errortypes.BadInput{Message: "Invalid MediaType. Smaato only supports Banner, Video and Native."} } - imps := make([]openrtb2.Imp, 0, 2) + imps := make([]openrtb2.Imp, 0, 3) if imp.Banner != nil { impCopy := *imp impCopy.Video = nil + impCopy.Native = nil imps = append(imps, impCopy) } if imp.Video != nil { + impCopy := *imp + impCopy.Banner = nil + impCopy.Native = nil + imps = append(imps, impCopy) + } + + if imp.Native != nil { imp.Banner = nil + imp.Video = nil imps = append(imps, *imp) } @@ -258,6 +268,8 @@ func getAdMarkupType(response *adapters.ResponseData, adMarkup string) (adMarkup return smtAdTypeRichmedia, nil } else if strings.HasPrefix(adMarkup, `
` + + bannerTemplate, err := template.New("banner").Parse(bannerHTML) + if err != nil { + return nil, err + } + + bidder := &adapter{ + endpoint: config.Endpoint, + bannerTemplate: bannerTemplate, + } + + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + formattedRequest, err := formatSspBcRequest(request) + if err != nil { + return nil, []error{err} + } + + requestJSON, err := json.Marshal(formattedRequest) + if err != nil { + return nil, []error{err} + } + + requestURL, err := url.Parse(a.endpoint) + if err != nil { + return nil, []error{err} + } + + // add query parameters to request + queryParams := requestURL.Query() + queryParams.Add("bdver", adapterVersion) + queryParams.Add("inver", prebidServerIntegrationType) + requestURL.RawQuery = queryParams.Encode() + + requestData := &adapters.RequestData{ + Method: http.MethodPost, + Uri: requestURL.String(), + Body: requestJSON, + } + + return []*adapters.RequestData{requestData}, nil +} + +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, externalResponse *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if externalResponse.StatusCode == http.StatusNoContent { + return nil, nil + } + + if externalResponse.StatusCode != http.StatusOK { + err := &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d.", externalResponse.StatusCode), + } + return nil, []error{err} + } + + var response openrtb2.BidResponse + if err := json.Unmarshal(externalResponse.Body, &response); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(internalRequest.Imp)) + bidResponse.Currency = response.Cur + + var errors []error + for _, seatBid := range response.SeatBid { + for _, bid := range seatBid.Bid { + if err := a.impToBid(internalRequest, seatBid, bid, bidResponse); err != nil { + errors = append(errors, err) + } + } + } + + return bidResponse, errors +} + +func (a *adapter) impToBid(internalRequest *openrtb2.BidRequest, seatBid openrtb2.SeatBid, bid openrtb2.Bid, + bidResponse *adapters.BidderResponse) error { + var bidType openrtb_ext.BidType + + /* + Determine bid type + At this moment we only check if bid contains Adm property + + Later updates will check for video & native data + */ + if bid.AdM != "" { + bidType = openrtb_ext.BidTypeBanner + } + + /* + Recover original ImpID + (stored on request in TagID) + */ + impID, err := getOriginalImpID(bid.ImpID, internalRequest.Imp) + if err != nil { + return err + } + bid.ImpID = impID + + // read additional data from proxy + var bidDataExt responseExt + if err := json.Unmarshal(bid.Ext, &bidDataExt); err != nil { + return err + } + /* + use correct ad creation method for a detected bid type + right now, we are only creating banner ads + if type is not detected / supported, throw error + */ + if bidType != openrtb_ext.BidTypeBanner { + return errNotSupportedFormat + } + + var adCreationError error + bid.AdM, adCreationError = a.createBannerAd(bid, bidDataExt, internalRequest, seatBid.Seat) + if adCreationError != nil { + return adCreationError + } + // append bid to responses + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: bidType, + }) + + return nil +} + +func getOriginalImpID(impID string, imps []openrtb2.Imp) (string, error) { + for _, imp := range imps { + if imp.ID == impID { + return imp.TagID, nil + } + } + + return "", errImpNotFound +} + +func (a *adapter) createBannerAd(bid openrtb2.Bid, ext responseExt, request *openrtb2.BidRequest, seat string) (string, error) { + if strings.Contains(bid.AdM, "") { + // Banner ad is already formatted + return bid.AdM, nil + } + + // create McAd payload + var mcad = mcAd{ + Id: request.ID, + Seat: seat, + SeatBid: []openrtb2.SeatBid{ + {Bid: []openrtb2.Bid{bid}}, + }, + } + + bannerData := &templatePayload{ + SiteId: ext.SiteId, + SlotId: ext.SlotId, + AdLabel: ext.AdLabel, + PubId: ext.PublisherId, + Page: request.Site.Page, + Referer: request.Site.Ref, + McAd: mcad, + Inver: prebidServerIntegrationType, + } + + var filledTemplate bytes.Buffer + if err := a.bannerTemplate.Execute(&filledTemplate, bannerData); err != nil { + return "", err + } + + return filledTemplate.String(), nil +} + +func getImpSize(imp openrtb2.Imp) string { + if imp.Banner == nil || len(imp.Banner.Format) == 0 { + return impFallbackSize + } + + var ( + areaMax int64 + impSize = impFallbackSize + ) + + for _, size := range imp.Banner.Format { + area := size.W * size.H + if area > areaMax { + areaMax = area + impSize = fmt.Sprintf("%dx%d", size.W, size.H) + } + } + + return impSize +} + +// getBidParameters reads additional data for this imp (site id , placement id, test) +// Errors in parameters do not break imp flow, and thus are not returned +func getBidParameters(imp openrtb2.Imp) openrtb_ext.ExtImpSspbc { + var extBidder adapters.ExtImpBidder + var extSSP openrtb_ext.ExtImpSspbc + + if err := json.Unmarshal(imp.Ext, &extBidder); err == nil { + _ = json.Unmarshal(extBidder.Bidder, &extSSP) + } + + return extSSP +} + +// getRequestType checks what kind of request we have. It can either be: +// - a standard request, where all Imps have complete site / placement data +// - a oneCodeRequest, where site / placement data has to be determined by server +// - a test request, where server returns fixed example ads +func getRequestType(request *openrtb2.BidRequest) int { + incompleteImps := 0 + + for _, imp := range request.Imp { + // Read data for this imp + extSSP := getBidParameters(imp) + + if extSSP.IsTest != 0 { + return requestTypeTest + } + + if extSSP.SiteId == "" || extSSP.Id == "" { + incompleteImps += 1 + } + } + + if incompleteImps > 0 { + return requestTypeOneCode + } + + return requestTypeStandard +} + +func formatSspBcRequest(request *openrtb2.BidRequest) (*openrtb2.BidRequest, error) { + if request.Site == nil { + return nil, errSiteNill + } + + var siteID string + + // determine what kind of request we are dealing with + requestType := getRequestType(request) + + for i, imp := range request.Imp { + // read ext data for the impression + extSSP := getBidParameters(imp) + + // store SiteID + if extSSP.SiteId != "" { + siteID = extSSP.SiteId + } + + // save current imp.id (adUnit name) as imp.tagid + // we will recover it in makeBids + imp.TagID = imp.ID + + // if there is a placement id, and this is not a oneCodeRequest, use it in imp.id + if extSSP.Id != "" && requestType != requestTypeOneCode { + imp.ID = extSSP.Id + } + + // check imp size and update e.ext - send pbslot, pbsize + // inability to set bid.ext will cause request to be invalid + impSize := getImpSize(imp) + impExt := requestImpExt{ + Data: adSlotData{ + PbSlot: imp.TagID, + PbSize: impSize, + }, + } + + impExtJSON, err := json.Marshal(impExt) + if err != nil { + return nil, err + } + imp.Ext = impExtJSON + // save updated imp + request.Imp[i] = imp + } + + siteCopy := *request.Site + request.Site = &siteCopy + + /* + update site ID + for oneCode request it has to be blank + for other requests it should be equal to + SiteId from one of the bids + */ + if requestType == requestTypeOneCode || siteID == "" { + request.Site.ID = "" + } else { + request.Site.ID = siteID + } + + // add domain info + if siteURL, err := url.Parse(request.Site.Page); err == nil { + request.Site.Domain = siteURL.Hostname() + } + + // set TEST Flag + if requestType == requestTypeTest { + request.Test = 1 + } + + return request, nil +} diff --git a/adapters/sspBC/sspbc_test.go b/adapters/sspBC/sspbc_test.go new file mode 100644 index 00000000000..47a8ae5d183 --- /dev/null +++ b/adapters/sspBC/sspbc_test.go @@ -0,0 +1,20 @@ +package sspBC + +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.BidderSspBC, config.Adapter{ + Endpoint: "http://ssp.wp.test/bidder/"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "sspbctest", bidder) +} diff --git a/adapters/sspBC/sspbctest/exemplary/banner-fromtemplate.json b/adapters/sspBC/sspbctest/exemplary/banner-fromtemplate.json new file mode 100644 index 00000000000..4d54443f2a9 --- /dev/null +++ b/adapters/sspBC/sspbctest/exemplary/banner-fromtemplate.json @@ -0,0 +1,152 @@ +{ + "mockBidRequest": { + "id": "test-request", + "imp": [ + { + "id": "slot", + "ext": { + "bidder": { + "siteId": "237503", + "id": "005" + } + }, + "secure": 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "site": { + "domain": "test.page", + "page": "https://test.page/", + "ref": "https://test.referer/" + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "test_consent" + }, + "buyeruid": "test_user" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ssp.wp.test/bidder/?bdver=5.8&inver=4", + "body": { + "id": "test-request", + "imp": [ + { + "id": "005", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "tagid": "slot", + "secure": 1, + "ext": { + "data": { + "pbslot": "slot", + "pbsize": "300x250" + } + } + } + ], + "site": { + "id": "237503", + "domain": "test.page", + "page": "https://test.page/", + "ref": "https://test.referer/" + }, + "user": { + "ext": { + "consent": "test_consent" + }, + "buyeruid": "test_user" + }, + "regs": { + "ext": { + "gdpr": 1 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "id": "test-request", + "seatbid": [ + { + "bid": [ + { + "adm": "some-test-ad", + "adomain": [ + "sspbc-test" + ], + "crid": "1234", + "ext": { + "adlabel": "Reklama", + "pubid": "431", + "siteid": "237503", + "slotid": "005", + "tagid": "slot" + }, + "w": 300, + "h": 250, + "id": "response-005", + "impid": "005", + "price": 20 + } + ], + "seat": "sspbc-test" + } + ], + "sn": "sspbc-test" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "response-005", + "impid": "slot", + "price": 20, + "adm": "<\/title><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><style> body { background-color: transparent; margin: 0; padding: 0; }<\/style><script> window.rekid = \"237503\"; window.slot = \"005\"; window.adlabel = 'Reklama'; window.pubid = '431'; window.wp_sn = 'sspbc_go'; window.page = 'https:\\\/\\\/test.page\\\/'; window.ref = 'https:\\\/\\\/test.referer\\\/'; window.mcad = {\"id\":\"test-request\",\"seat\":\"sspbc-test\",\"seatbid\":[{\"bid\":[{\"id\":\"response-005\",\"impid\":\"slot\",\"price\":20,\"adm\":\"some-test-ad\",\"adomain\":[\"sspbc-test\"],\"crid\":\"1234\",\"w\":300,\"h\":250,\"ext\":{\"adlabel\":\"Reklama\",\"pubid\":\"431\",\"siteid\":\"237503\",\"slotid\":\"005\",\"tagid\":\"slot\"}}]}]}; window.inver = '4'; <\/script><\/head><body><div id=\"c\"><\/div><script async crossorigin nomodule src=\"\/\/std.wpcdn.pl\/wpjslib\/wpjslib-inline.js\" id=\"wpjslib\"><\/script><script async crossorigin type=\"module\" src=\"\/\/std.wpcdn.pl\/wpjslib6\/wpjslib-inline.js\" id=\"wpjslib6\"><\/script><\/body><\/html>", + "adomain": [ + "sspbc-test" + ], + "crid": "1234", + "w": 300, + "h": 250, + "ext": { + "adlabel": "Reklama", + "pubid": "431", + "siteid": "237503", + "slotid": "005", + "tagid": "slot" + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/sspBC/sspbctest/exemplary/banner-preformatted-multiple-imps.json b/adapters/sspBC/sspbctest/exemplary/banner-preformatted-multiple-imps.json new file mode 100644 index 00000000000..ab958148abc --- /dev/null +++ b/adapters/sspBC/sspbctest/exemplary/banner-preformatted-multiple-imps.json @@ -0,0 +1,235 @@ +{ + "mockBidRequest": { + "id": "test-request", + "imp": [ + { + "id": "slot1", + "ext": { + "bidder": { + "siteId": "237503", + "id": "005" + } + }, + "secure": 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + }, + { + "id": "slot2", + "ext": { + "bidder": { + "siteId": "237503", + "id": "037" + } + }, + "secure": 1, + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + } + } + ], + "site": { + "domain": "test.page", + "page": "https://test.page/", + "ref": "https://test.referer/" + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "test_consent" + }, + "buyeruid": "test_user" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ssp.wp.test/bidder/?bdver=5.8&inver=4", + "body": { + "id": "test-request", + "imp": [ + { + "id": "005", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "tagid": "slot1", + "secure": 1, + "ext": { + "data": { + "pbslot": "slot1", + "pbsize": "300x250" + } + } + }, + { + "id": "037", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "slot2", + "secure": 1, + "ext": { + "data": { + "pbslot": "slot2", + "pbsize": "300x600" + } + } + } + ], + "site": { + "id": "237503", + "domain": "test.page", + "page": "https://test.page/", + "ref": "https://test.referer/" + }, + "user": { + "ext": { + "consent": "test_consent" + }, + "buyeruid": "test_user" + }, + "regs": { + "ext": { + "gdpr": 1 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "id": "test-request", + "seatbid": [ + { + "bid": [ + { + "adm": "<!--preformatted-->test_ad_1", + "adomain": [ + "sspbc-test" + ], + "crid": "1234", + "ext": { + "adlabel": "Reklama", + "pubid": "431", + "siteid": "237503", + "slotid": "005", + "tagid": "slot1" + }, + "w": 300, + "h": 250, + "id": "response-005", + "impid": "005", + "price": 20 + } + ], + "seat": "sspbc-test" + }, + { + "bid": [ + { + "adm": "<!--preformatted-->test_ad_2", + "adomain": [ + "sspbc-test" + ], + "crid": "5678", + "ext": { + "adlabel": "Reklama", + "pubid": "431", + "siteid": "237503", + "slotid": "037", + "tagid": "slot2" + }, + "w": 300, + "h": 600, + "id": "response-037", + "impid": "037", + "price": 10 + } + ], + "seat": "sspbc-test" + } + ], + "sn": "sspbc-test" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "response-005", + "impid": "slot1", + "price": 20, + "adm": "<!--preformatted-->test_ad_1", + "adomain": [ + "sspbc-test" + ], + "crid": "1234", + "w": 300, + "h": 250, + "ext": { + "adlabel": "Reklama", + "pubid": "431", + "siteid": "237503", + "slotid": "005", + "tagid": "slot1" + } + }, + "type": "banner" + }, + { + "bid": { + "id": "response-037", + "impid": "slot2", + "price": 10, + "adm": "<!--preformatted-->test_ad_2", + "adomain": [ + "sspbc-test" + ], + "crid": "5678", + "w": 300, + "h": 600, + "ext": { + "adlabel": "Reklama", + "pubid": "431", + "siteid": "237503", + "slotid": "037", + "tagid": "slot2" + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/sspBC/sspbctest/exemplary/banner-preformatted-onecode.json b/adapters/sspBC/sspbctest/exemplary/banner-preformatted-onecode.json new file mode 100644 index 00000000000..aaf9a7b4968 --- /dev/null +++ b/adapters/sspBC/sspbctest/exemplary/banner-preformatted-onecode.json @@ -0,0 +1,146 @@ +{ + "mockBidRequest": { + "id": "test-request", + "imp": [ + { + "id": "slot", + "ext": {}, + "secure": 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "site": { + "domain": "test.page", + "page": "https://test.page/", + "ref": "https://test.referer/" + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "test_consent" + }, + "buyeruid": "test_user" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ssp.wp.test/bidder/?bdver=5.8&inver=4", + "body": { + "id": "test-request", + "imp": [ + { + "id": "slot", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "tagid": "slot", + "secure": 1, + "ext": { + "data": { + "pbslot": "slot", + "pbsize": "300x250" + } + } + } + ], + "site": { + "domain": "test.page", + "page": "https://test.page/", + "ref": "https://test.referer/" + }, + "user": { + "ext": { + "consent": "test_consent" + }, + "buyeruid": "test_user" + }, + "regs": { + "ext": { + "gdpr": 1 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "id": "test-request", + "seatbid": [ + { + "bid": [ + { + "adm": "<!--preformatted-->test_ad", + "adomain": [ + "sspbc-test" + ], + "crid": "1234", + "ext": { + "adlabel": "Reklama", + "pubid": "431", + "siteid": "237503", + "slotid": "005", + "tagid": "slot" + }, + "w": 300, + "h": 250, + "id": "response-005", + "impid": "slot", + "price": 20 + } + ], + "seat": "sspbc-test" + } + ], + "sn": "sspbc-test" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "response-005", + "impid": "slot", + "price": 20, + "adm": "<!--preformatted-->test_ad", + "adomain": [ + "sspbc-test" + ], + "crid": "1234", + "w": 300, + "h": 250, + "ext": { + "adlabel": "Reklama", + "pubid": "431", + "siteid": "237503", + "slotid": "005", + "tagid": "slot" + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/sspBC/sspbctest/exemplary/banner-preformatted.json b/adapters/sspBC/sspbctest/exemplary/banner-preformatted.json new file mode 100644 index 00000000000..d5c3125e443 --- /dev/null +++ b/adapters/sspBC/sspbctest/exemplary/banner-preformatted.json @@ -0,0 +1,152 @@ +{ + "mockBidRequest": { + "id": "test-request", + "imp": [ + { + "id": "slot", + "ext": { + "bidder": { + "siteId": "237503", + "id": "005" + } + }, + "secure": 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "site": { + "domain": "test.page", + "page": "https://test.page/", + "ref": "https://test.referer/" + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "test_consent" + }, + "buyeruid": "test_user" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ssp.wp.test/bidder/?bdver=5.8&inver=4", + "body": { + "id": "test-request", + "imp": [ + { + "id": "005", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "tagid": "slot", + "secure": 1, + "ext": { + "data": { + "pbslot": "slot", + "pbsize": "300x250" + } + } + } + ], + "site": { + "id": "237503", + "domain": "test.page", + "page": "https://test.page/", + "ref": "https://test.referer/" + }, + "user": { + "ext": { + "consent": "test_consent" + }, + "buyeruid": "test_user" + }, + "regs": { + "ext": { + "gdpr": 1 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "id": "test-request", + "seatbid": [ + { + "bid": [ + { + "adm": "<!--preformatted-->test_ad", + "adomain": [ + "sspbc-test" + ], + "crid": "1234", + "ext": { + "adlabel": "Reklama", + "pubid": "431", + "siteid": "237503", + "slotid": "005", + "tagid": "slot" + }, + "w": 300, + "h": 250, + "id": "response-005", + "impid": "005", + "price": 20 + } + ], + "seat": "sspbc-test" + } + ], + "sn": "sspbc-test" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "response-005", + "impid": "slot", + "price": 20, + "adm": "<!--preformatted-->test_ad", + "adomain": [ + "sspbc-test" + ], + "crid": "1234", + "w": 300, + "h": 250, + "ext": { + "adlabel": "Reklama", + "pubid": "431", + "siteid": "237503", + "slotid": "005", + "tagid": "slot" + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/sspBC/sspbctest/supplemental/bad_request_without_site.json b/adapters/sspBC/sspbctest/supplemental/bad_request_without_site.json new file mode 100644 index 00000000000..c8acc0c82f7 --- /dev/null +++ b/adapters/sspBC/sspbctest/supplemental/bad_request_without_site.json @@ -0,0 +1,42 @@ +{ + "mockBidRequest": { + "id": "test-request", + "imp": [ + { + "id": "slot", + "ext": { + "bidder": { + "siteId": "237503", + "id": "005" + } + }, + "secure": 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "test_consent" + }, + "buyeruid": "test_user" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "site cannot be nill", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/sspBC/sspbctest/supplemental/bad_response.json b/adapters/sspBC/sspbctest/supplemental/bad_response.json new file mode 100644 index 00000000000..4e67b182730 --- /dev/null +++ b/adapters/sspBC/sspbctest/supplemental/bad_response.json @@ -0,0 +1,99 @@ +{ + "mockBidRequest": { + "id": "test-request", + "imp": [ + { + "id": "slot", + "ext": { + "bidder": { + "siteId": "237503", + "id": "005" + } + }, + "secure": 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "site": { + "domain": "test.page", + "page": "https://test.page/", + "ref": "https://test.referer/" + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "test_consent" + }, + "buyeruid": "test_user" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ssp.wp.test/bidder/?bdver=5.8&inver=4", + "body": { + "id": "test-request", + "imp": [ + { + "id": "005", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "tagid": "slot", + "secure": 1, + "ext": { + "data": { + "pbslot": "slot", + "pbsize": "300x250" + } + } + } + ], + "site": { + "id": "237503", + "domain": "test.page", + "page": "https://test.page/", + "ref": "https://test.referer/" + }, + "user": { + "ext": { + "consent": "test_consent" + }, + "buyeruid": "test_user" + }, + "regs": { + "ext": { + "gdpr": 1 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": "{\"id\"incorrect json" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/sspBC/sspbctest/supplemental/bad_response_with_incorrect_impid.json b/adapters/sspBC/sspbctest/supplemental/bad_response_with_incorrect_impid.json new file mode 100644 index 00000000000..bc5ffa205c2 --- /dev/null +++ b/adapters/sspBC/sspbctest/supplemental/bad_response_with_incorrect_impid.json @@ -0,0 +1,135 @@ +{ + "mockBidRequest": { + "id": "test-request", + "imp": [ + { + "id": "slot", + "ext": { + "bidder": { + "siteId": "237503", + "id": "005" + } + }, + "secure": 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "site": { + "domain": "test.page", + "page": "https://test.page/", + "ref": "https://test.referer/" + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "test_consent" + }, + "buyeruid": "test_user" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ssp.wp.test/bidder/?bdver=5.8&inver=4", + "body": { + "id": "test-request", + "imp": [ + { + "id": "005", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "tagid": "slot", + "secure": 1, + "ext": { + "data": { + "pbslot": "slot", + "pbsize": "300x250" + } + } + } + ], + "site": { + "id": "237503", + "domain": "test.page", + "page": "https://test.page/", + "ref": "https://test.referer/" + }, + "user": { + "ext": { + "consent": "test_consent" + }, + "buyeruid": "test_user" + }, + "regs": { + "ext": { + "gdpr": 1 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "id": "test-request", + "seatbid": [ + { + "bid": [ + { + "adm": "{\"bunch\":\"237503\",\"capping\":\"\",\"server\":\"s\",\"campaign\":\"c\",\"score\":\"1\",\"creations\":[{\"type\":\"image\",\"src\":\"https:\/\/bdr.wpcdn.pl\/tests\/test-300x250.png\",\"trackers\":{},\"width\":300,\"height\":250}],\"code\":\"1\",\"aabVendors\":false,\"slot\":\"005\",\"redir\":\"https:\/\/www.wp.pl\",\"wpclid\":\"1\"}", + "adomain": [ + "sspbc-test" + ], + "crid": "1234", + "ext": { + "adlabel": "Reklama", + "pubid": "431", + "siteid": "237503", + "slotid": "005", + "tagid": "slot" + }, + "w": 300, + "h": 250, + "id": "response-005", + "impid": "incorrect-imp-id", + "price": 20 + } + ], + "seat": "sspbc-test" + } + ], + "sn": "sspbc-test" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [] + } + ], + "expectedMakeBidsErrors": [ + { + "value": "imp not found", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/sspBC/sspbctest/supplemental/bad_response_without_adm.json b/adapters/sspBC/sspbctest/supplemental/bad_response_without_adm.json new file mode 100644 index 00000000000..46774f3051c --- /dev/null +++ b/adapters/sspBC/sspbctest/supplemental/bad_response_without_adm.json @@ -0,0 +1,129 @@ +{ + "mockBidRequest": { + "id": "test-request", + "imp": [ + { + "id": "slot", + "ext": { + "bidder": { + "siteId": "237503", + "id": "005" + } + }, + "secure": 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "site": { + "domain": "test.page", + "page": "https://test.page/", + "ref": "https://test.referer/" + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "test_consent" + }, + "buyeruid": "test_user" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ssp.wp.test/bidder/?bdver=5.8&inver=4", + "body": { + "id": "test-request", + "imp": [ + { + "id": "005", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "tagid": "slot", + "secure": 1, + "ext": { + "data": { + "pbslot": "slot", + "pbsize": "300x250" + } + } + } + ], + "site": { + "id": "237503", + "domain": "test.page", + "page": "https://test.page/", + "ref": "https://test.referer/" + }, + "user": { + "ext": { + "consent": "test_consent" + }, + "buyeruid": "test_user" + }, + "regs": { + "ext": { + "gdpr": 1 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "id": "test-request", + "seatbid": [ + { + "bid": [ + { + "adm": "", + "adomain": [ + "sspbc-test" + ], + "crid": "1234", + "ext": { + "adlabel": "Reklama", + "pubid": "431", + "siteid": "237503", + "slotid": "005", + "tagid": "slot" + }, + "w": 300, + "h": 250, + "id": "response-005", + "impid": "005", + "price": 20 + } + ], + "seat": "sspbc-test" + } + ], + "sn": "sspbc-test" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "bid format is not supported", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/sspBC/sspbctest/supplemental/request_with_diffrent_siteid.json b/adapters/sspBC/sspbctest/supplemental/request_with_diffrent_siteid.json new file mode 100644 index 00000000000..26a5043bbef --- /dev/null +++ b/adapters/sspBC/sspbctest/supplemental/request_with_diffrent_siteid.json @@ -0,0 +1,153 @@ +{ + "mockBidRequest": { + "id": "test-request", + "imp": [ + { + "id": "slot", + "ext": { + "bidder": { + "siteId": "237503", + "id": "005" + } + }, + "secure": 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "site": { + "id": "diffrent", + "domain": "test.page", + "page": "https://test.page/", + "ref": "https://test.referer/" + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "test_consent" + }, + "buyeruid": "test_user" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ssp.wp.test/bidder/?bdver=5.8&inver=4", + "body": { + "id": "test-request", + "imp": [ + { + "id": "005", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "tagid": "slot", + "secure": 1, + "ext": { + "data": { + "pbslot": "slot", + "pbsize": "300x250" + } + } + } + ], + "site": { + "id": "237503", + "domain": "test.page", + "page": "https://test.page/", + "ref": "https://test.referer/" + }, + "user": { + "ext": { + "consent": "test_consent" + }, + "buyeruid": "test_user" + }, + "regs": { + "ext": { + "gdpr": 1 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "id": "test-request", + "seatbid": [ + { + "bid": [ + { + "adm": "{\"bunch\":\"237503\",\"capping\":\"\",\"server\":\"s\",\"campaign\":\"c\",\"score\":\"1\",\"creations\":[{\"type\":\"image\",\"src\":\"https:\/\/bdr.wpcdn.pl\/tests\/test-300x250.png\",\"trackers\":{},\"width\":300,\"height\":250}],\"code\":\"1\",\"aabVendors\":false,\"slot\":\"005\",\"redir\":\"https:\/\/www.wp.pl\",\"wpclid\":\"1\"}", + "adomain": [ + "sspbc-test" + ], + "crid": "1234", + "ext": { + "adlabel": "Reklama", + "pubid": "431", + "siteid": "237503", + "slotid": "005", + "tagid": "slot" + }, + "w": 300, + "h": 250, + "id": "response-005", + "impid": "005", + "price": 20 + } + ], + "seat": "sspbc-test" + } + ], + "sn": "sspbc-test" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "response-005", + "impid": "slot", + "price": 20, + "adm": "<html><head><title><\/title><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><style> body { background-color: transparent; margin: 0; padding: 0; }<\/style><script> window.rekid = \"237503\"; window.slot = \"005\"; window.adlabel = 'Reklama'; window.pubid = '431'; window.wp_sn = 'sspbc_go'; window.page = 'https:\\\/\\\/test.page\\\/'; window.ref = 'https:\\\/\\\/test.referer\\\/'; window.mcad = {\"id\":\"test-request\",\"seat\":\"sspbc-test\",\"seatbid\":[{\"bid\":[{\"id\":\"response-005\",\"impid\":\"slot\",\"price\":20,\"adm\":\"{\\\"bunch\\\":\\\"237503\\\",\\\"capping\\\":\\\"\\\",\\\"server\\\":\\\"s\\\",\\\"campaign\\\":\\\"c\\\",\\\"score\\\":\\\"1\\\",\\\"creations\\\":[{\\\"type\\\":\\\"image\\\",\\\"src\\\":\\\"https://bdr.wpcdn.pl/tests/test-300x250.png\\\",\\\"trackers\\\":{},\\\"width\\\":300,\\\"height\\\":250}],\\\"code\\\":\\\"1\\\",\\\"aabVendors\\\":false,\\\"slot\\\":\\\"005\\\",\\\"redir\\\":\\\"https://www.wp.pl\\\",\\\"wpclid\\\":\\\"1\\\"}\",\"adomain\":[\"sspbc-test\"],\"crid\":\"1234\",\"w\":300,\"h\":250,\"ext\":{\"adlabel\":\"Reklama\",\"pubid\":\"431\",\"siteid\":\"237503\",\"slotid\":\"005\",\"tagid\":\"slot\"}}]}]}; window.inver = '4'; <\/script><\/head><body><div id=\"c\"><\/div><script async crossorigin nomodule src=\"\/\/std.wpcdn.pl\/wpjslib\/wpjslib-inline.js\" id=\"wpjslib\"><\/script><script async crossorigin type=\"module\" src=\"\/\/std.wpcdn.pl\/wpjslib6\/wpjslib-inline.js\" id=\"wpjslib6\"><\/script><\/body><\/html>", + "adomain": [ + "sspbc-test" + ], + "crid": "1234", + "w": 300, + "h": 250, + "ext": { + "adlabel": "Reklama", + "pubid": "431", + "siteid": "237503", + "slotid": "005", + "tagid": "slot" + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/sspBC/sspbctest/supplemental/request_with_incorrect_imp_bidder_ext.json b/adapters/sspBC/sspbctest/supplemental/request_with_incorrect_imp_bidder_ext.json new file mode 100644 index 00000000000..f9017c197ac --- /dev/null +++ b/adapters/sspBC/sspbctest/supplemental/request_with_incorrect_imp_bidder_ext.json @@ -0,0 +1,148 @@ +{ + "mockBidRequest": { + "id": "test-request", + "imp": [ + { + "id": "slot", + "ext": { + "bidder": "incorrect" + }, + "secure": 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "site": { + "domain": "test.page", + "page": "https://test.page/", + "ref": "https://test.referer/" + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "test_consent" + }, + "buyeruid": "test_user" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ssp.wp.test/bidder/?bdver=5.8&inver=4", + "body": { + "id": "test-request", + "imp": [ + { + "id": "slot", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "tagid": "slot", + "secure": 1, + "ext": { + "data": { + "pbslot": "slot", + "pbsize": "300x250" + } + } + } + ], + "site": { + "domain": "test.page", + "page": "https://test.page/", + "ref": "https://test.referer/" + }, + "user": { + "ext": { + "consent": "test_consent" + }, + "buyeruid": "test_user" + }, + "regs": { + "ext": { + "gdpr": 1 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "id": "test-request", + "seatbid": [ + { + "bid": [ + { + "adm": "{\"bunch\":\"237503\",\"capping\":\"\",\"server\":\"s\",\"campaign\":\"c\",\"score\":\"1\",\"creations\":[{\"type\":\"image\",\"src\":\"https:\/\/bdr.wpcdn.pl\/tests\/test-300x250.png\",\"trackers\":{},\"width\":300,\"height\":250}],\"code\":\"1\",\"aabVendors\":false,\"slot\":\"005\",\"redir\":\"https:\/\/www.wp.pl\",\"wpclid\":\"1\"}", + "adomain": [ + "sspbc-test" + ], + "crid": "1234", + "ext": { + "adlabel": "Reklama", + "pubid": "431", + "siteid": "237503", + "slotid": "005", + "tagid": "slot" + }, + "w": 300, + "h": 250, + "id": "response-005", + "impid": "slot", + "price": 20 + } + ], + "seat": "sspbc-test" + } + ], + "sn": "sspbc-test" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "response-005", + "impid": "slot", + "price": 20, + "adm": "<html><head><title><\/title><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><style> body { background-color: transparent; margin: 0; padding: 0; }<\/style><script> window.rekid = \"237503\"; window.slot = \"005\"; window.adlabel = 'Reklama'; window.pubid = '431'; window.wp_sn = 'sspbc_go'; window.page = 'https:\\\/\\\/test.page\\\/'; window.ref = 'https:\\\/\\\/test.referer\\\/'; window.mcad = {\"id\":\"test-request\",\"seat\":\"sspbc-test\",\"seatbid\":[{\"bid\":[{\"id\":\"response-005\",\"impid\":\"slot\",\"price\":20,\"adm\":\"{\\\"bunch\\\":\\\"237503\\\",\\\"capping\\\":\\\"\\\",\\\"server\\\":\\\"s\\\",\\\"campaign\\\":\\\"c\\\",\\\"score\\\":\\\"1\\\",\\\"creations\\\":[{\\\"type\\\":\\\"image\\\",\\\"src\\\":\\\"https://bdr.wpcdn.pl/tests/test-300x250.png\\\",\\\"trackers\\\":{},\\\"width\\\":300,\\\"height\\\":250}],\\\"code\\\":\\\"1\\\",\\\"aabVendors\\\":false,\\\"slot\\\":\\\"005\\\",\\\"redir\\\":\\\"https://www.wp.pl\\\",\\\"wpclid\\\":\\\"1\\\"}\",\"adomain\":[\"sspbc-test\"],\"crid\":\"1234\",\"w\":300,\"h\":250,\"ext\":{\"adlabel\":\"Reklama\",\"pubid\":\"431\",\"siteid\":\"237503\",\"slotid\":\"005\",\"tagid\":\"slot\"}}]}]}; window.inver = '4'; <\/script><\/head><body><div id=\"c\"><\/div><script async crossorigin nomodule src=\"\/\/std.wpcdn.pl\/wpjslib\/wpjslib-inline.js\" id=\"wpjslib\"><\/script><script async crossorigin type=\"module\" src=\"\/\/std.wpcdn.pl\/wpjslib6\/wpjslib-inline.js\" id=\"wpjslib6\"><\/script><\/body><\/html>", + "adomain": [ + "sspbc-test" + ], + "crid": "1234", + "w": 300, + "h": 250, + "ext": { + "adlabel": "Reklama", + "pubid": "431", + "siteid": "237503", + "slotid": "005", + "tagid": "slot" + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/sspBC/sspbctest/supplemental/request_with_incorrect_imp_ext.json b/adapters/sspBC/sspbctest/supplemental/request_with_incorrect_imp_ext.json new file mode 100644 index 00000000000..c26dfa35b6c --- /dev/null +++ b/adapters/sspBC/sspbctest/supplemental/request_with_incorrect_imp_ext.json @@ -0,0 +1,146 @@ +{ + "mockBidRequest": { + "id": "test-request", + "imp": [ + { + "id": "slot", + "ext": "incorrect", + "secure": 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "site": { + "domain": "test.page", + "page": "https://test.page/", + "ref": "https://test.referer/" + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "test_consent" + }, + "buyeruid": "test_user" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ssp.wp.test/bidder/?bdver=5.8&inver=4", + "body": { + "id": "test-request", + "imp": [ + { + "id": "slot", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "tagid": "slot", + "secure": 1, + "ext": { + "data": { + "pbslot": "slot", + "pbsize": "300x250" + } + } + } + ], + "site": { + "domain": "test.page", + "page": "https://test.page/", + "ref": "https://test.referer/" + }, + "user": { + "ext": { + "consent": "test_consent" + }, + "buyeruid": "test_user" + }, + "regs": { + "ext": { + "gdpr": 1 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "id": "test-request", + "seatbid": [ + { + "bid": [ + { + "adm": "{\"bunch\":\"237503\",\"capping\":\"\",\"server\":\"s\",\"campaign\":\"c\",\"score\":\"1\",\"creations\":[{\"type\":\"image\",\"src\":\"https:\/\/bdr.wpcdn.pl\/tests\/test-300x250.png\",\"trackers\":{},\"width\":300,\"height\":250}],\"code\":\"1\",\"aabVendors\":false,\"slot\":\"005\",\"redir\":\"https:\/\/www.wp.pl\",\"wpclid\":\"1\"}", + "adomain": [ + "sspbc-test" + ], + "crid": "1234", + "ext": { + "adlabel": "Reklama", + "pubid": "431", + "siteid": "237503", + "slotid": "005", + "tagid": "slot" + }, + "w": 300, + "h": 250, + "id": "response-005", + "impid": "slot", + "price": 20 + } + ], + "seat": "sspbc-test" + } + ], + "sn": "sspbc-test" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "response-005", + "impid": "slot", + "price": 20, + "adm": "<html><head><title><\/title><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><style> body { background-color: transparent; margin: 0; padding: 0; }<\/style><script> window.rekid = \"237503\"; window.slot = \"005\"; window.adlabel = 'Reklama'; window.pubid = '431'; window.wp_sn = 'sspbc_go'; window.page = 'https:\\\/\\\/test.page\\\/'; window.ref = 'https:\\\/\\\/test.referer\\\/'; window.mcad = {\"id\":\"test-request\",\"seat\":\"sspbc-test\",\"seatbid\":[{\"bid\":[{\"id\":\"response-005\",\"impid\":\"slot\",\"price\":20,\"adm\":\"{\\\"bunch\\\":\\\"237503\\\",\\\"capping\\\":\\\"\\\",\\\"server\\\":\\\"s\\\",\\\"campaign\\\":\\\"c\\\",\\\"score\\\":\\\"1\\\",\\\"creations\\\":[{\\\"type\\\":\\\"image\\\",\\\"src\\\":\\\"https://bdr.wpcdn.pl/tests/test-300x250.png\\\",\\\"trackers\\\":{},\\\"width\\\":300,\\\"height\\\":250}],\\\"code\\\":\\\"1\\\",\\\"aabVendors\\\":false,\\\"slot\\\":\\\"005\\\",\\\"redir\\\":\\\"https://www.wp.pl\\\",\\\"wpclid\\\":\\\"1\\\"}\",\"adomain\":[\"sspbc-test\"],\"crid\":\"1234\",\"w\":300,\"h\":250,\"ext\":{\"adlabel\":\"Reklama\",\"pubid\":\"431\",\"siteid\":\"237503\",\"slotid\":\"005\",\"tagid\":\"slot\"}}]}]}; window.inver = '4'; <\/script><\/head><body><div id=\"c\"><\/div><script async crossorigin nomodule src=\"\/\/std.wpcdn.pl\/wpjslib\/wpjslib-inline.js\" id=\"wpjslib\"><\/script><script async crossorigin type=\"module\" src=\"\/\/std.wpcdn.pl\/wpjslib6\/wpjslib-inline.js\" id=\"wpjslib6\"><\/script><\/body><\/html>", + "adomain": [ + "sspbc-test" + ], + "crid": "1234", + "w": 300, + "h": 250, + "ext": { + "adlabel": "Reklama", + "pubid": "431", + "siteid": "237503", + "slotid": "005", + "tagid": "slot" + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/sspBC/sspbctest/supplemental/request_with_standard_and_onecode_imp.json b/adapters/sspBC/sspbctest/supplemental/request_with_standard_and_onecode_imp.json new file mode 100644 index 00000000000..a304aabe768 --- /dev/null +++ b/adapters/sspBC/sspbctest/supplemental/request_with_standard_and_onecode_imp.json @@ -0,0 +1,229 @@ +{ + "mockBidRequest": { + "id": "test-request", + "imp": [ + { + "id": "slot-standard", + "ext": { + "bidder": { + "siteId": "237503", + "id": "005" + } + }, + "secure": 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + }, + { + "id": "slot-onecode", + "ext": {}, + "secure": 1, + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + } + } + ], + "site": { + "domain": "test.page", + "page": "https://test.page/", + "ref": "https://test.referer/" + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "test_consent" + }, + "buyeruid": "test_user" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ssp.wp.test/bidder/?bdver=5.8&inver=4", + "body": { + "id": "test-request", + "imp": [ + { + "id": "slot-standard", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "tagid": "slot-standard", + "secure": 1, + "ext": { + "data": { + "pbslot": "slot-standard", + "pbsize": "300x250" + } + } + }, + { + "id": "slot-onecode", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "slot-onecode", + "secure": 1, + "ext": { + "data": { + "pbslot": "slot-onecode", + "pbsize": "300x600" + } + } + } + ], + "site": { + "domain": "test.page", + "page": "https://test.page/", + "ref": "https://test.referer/" + }, + "user": { + "ext": { + "consent": "test_consent" + }, + "buyeruid": "test_user" + }, + "regs": { + "ext": { + "gdpr": 1 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "id": "test-request", + "seatbid": [ + { + "bid": [ + { + "adm": "<!--preformatted-->test_ad_1", + "adomain": [ + "sspbc-test" + ], + "crid": "1234", + "ext": { + "adlabel": "Reklama", + "pubid": "431", + "siteid": "237503", + "slotid": "005", + "tagid": "slot-standard" + }, + "w": 300, + "h": 250, + "id": "response-005", + "impid": "slot-standard", + "price": 20 + } + ], + "seat": "sspbc-test" + }, + { + "bid": [ + { + "adm": "<!--preformatted-->test_ad_2", + "adomain": [ + "sspbc-test" + ], + "crid": "5678", + "ext": { + "adlabel": "Reklama", + "pubid": "431", + "siteid": "237503", + "slotid": "037", + "tagid": "slot-onecode" + }, + "w": 300, + "h": 600, + "id": "response-037", + "impid": "slot-onecode", + "price": 10 + } + ], + "seat": "sspbc-test" + } + ], + "sn": "sspbc-test" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "response-005", + "impid": "slot-standard", + "price": 20, + "adm": "<!--preformatted-->test_ad_1", + "adomain": [ + "sspbc-test" + ], + "crid": "1234", + "w": 300, + "h": 250, + "ext": { + "adlabel": "Reklama", + "pubid": "431", + "siteid": "237503", + "slotid": "005", + "tagid": "slot-standard" + } + }, + "type": "banner" + }, + { + "bid": { + "id": "response-037", + "impid": "slot-onecode", + "price": 10, + "adm": "<!--preformatted-->test_ad_2", + "adomain": [ + "sspbc-test" + ], + "crid": "5678", + "w": 300, + "h": 600, + "ext": { + "adlabel": "Reklama", + "pubid": "431", + "siteid": "237503", + "slotid": "037", + "tagid": "slot-onecode" + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/sspBC/sspbctest/supplemental/request_with_test.json b/adapters/sspBC/sspbctest/supplemental/request_with_test.json new file mode 100644 index 00000000000..a30b77e70c9 --- /dev/null +++ b/adapters/sspBC/sspbctest/supplemental/request_with_test.json @@ -0,0 +1,155 @@ +{ + "mockBidRequest": { + "id": "test-request", + "imp": [ + { + "id": "slot", + "ext": { + "bidder": { + "siteId": "237503", + "id": "005", + "test": 1 + } + }, + "secure": 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "site": { + "id": "237503", + "domain": "test.page", + "page": "https://test.page/", + "ref": "https://test.referer/" + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "test_consent" + }, + "buyeruid": "test_user" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ssp.wp.test/bidder/?bdver=5.8&inver=4", + "body": { + "id": "test-request", + "imp": [ + { + "id": "005", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "tagid": "slot", + "secure": 1, + "ext": { + "data": { + "pbslot": "slot", + "pbsize": "300x250" + } + } + } + ], + "site": { + "id": "237503", + "domain": "test.page", + "page": "https://test.page/", + "ref": "https://test.referer/" + }, + "user": { + "ext": { + "consent": "test_consent" + }, + "buyeruid": "test_user" + }, + "test": 1, + "regs": { + "ext": { + "gdpr": 1 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "id": "test-request", + "seatbid": [ + { + "bid": [ + { + "adm": "{\"bunch\":\"237503\",\"capping\":\"\",\"server\":\"s\",\"campaign\":\"c\",\"score\":\"1\",\"creations\":[{\"type\":\"image\",\"src\":\"https:\/\/bdr.wpcdn.pl\/tests\/test-300x250.png\",\"trackers\":{},\"width\":300,\"height\":250}],\"code\":\"1\",\"aabVendors\":false,\"slot\":\"005\",\"redir\":\"https:\/\/www.wp.pl\",\"wpclid\":\"1\"}", + "adomain": [ + "sspbc-test" + ], + "crid": "1234", + "ext": { + "adlabel": "Reklama", + "pubid": "431", + "siteid": "237503", + "slotid": "005", + "tagid": "slot" + }, + "w": 300, + "h": 250, + "id": "response-005", + "impid": "005", + "price": 20 + } + ], + "seat": "sspbc-test" + } + ], + "sn": "sspbc-test" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "response-005", + "impid": "slot", + "price": 20, + "adm": "<html><head><title><\/title><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><style> body { background-color: transparent; margin: 0; padding: 0; }<\/style><script> window.rekid = \"237503\"; window.slot = \"005\"; window.adlabel = 'Reklama'; window.pubid = '431'; window.wp_sn = 'sspbc_go'; window.page = 'https:\\\/\\\/test.page\\\/'; window.ref = 'https:\\\/\\\/test.referer\\\/'; window.mcad = {\"id\":\"test-request\",\"seat\":\"sspbc-test\",\"seatbid\":[{\"bid\":[{\"id\":\"response-005\",\"impid\":\"slot\",\"price\":20,\"adm\":\"{\\\"bunch\\\":\\\"237503\\\",\\\"capping\\\":\\\"\\\",\\\"server\\\":\\\"s\\\",\\\"campaign\\\":\\\"c\\\",\\\"score\\\":\\\"1\\\",\\\"creations\\\":[{\\\"type\\\":\\\"image\\\",\\\"src\\\":\\\"https://bdr.wpcdn.pl/tests/test-300x250.png\\\",\\\"trackers\\\":{},\\\"width\\\":300,\\\"height\\\":250}],\\\"code\\\":\\\"1\\\",\\\"aabVendors\\\":false,\\\"slot\\\":\\\"005\\\",\\\"redir\\\":\\\"https://www.wp.pl\\\",\\\"wpclid\\\":\\\"1\\\"}\",\"adomain\":[\"sspbc-test\"],\"crid\":\"1234\",\"w\":300,\"h\":250,\"ext\":{\"adlabel\":\"Reklama\",\"pubid\":\"431\",\"siteid\":\"237503\",\"slotid\":\"005\",\"tagid\":\"slot\"}}]}]}; window.inver = '4'; <\/script><\/head><body><div id=\"c\"><\/div><script async crossorigin nomodule src=\"\/\/std.wpcdn.pl\/wpjslib\/wpjslib-inline.js\" id=\"wpjslib\"><\/script><script async crossorigin type=\"module\" src=\"\/\/std.wpcdn.pl\/wpjslib6\/wpjslib-inline.js\" id=\"wpjslib6\"><\/script><\/body><\/html>", + "adomain": [ + "sspbc-test" + ], + "crid": "1234", + "w": 300, + "h": 250, + "ext": { + "adlabel": "Reklama", + "pubid": "431", + "siteid": "237503", + "slotid": "005", + "tagid": "slot" + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/sspBC/sspbctest/supplemental/request_without_banner_format.json b/adapters/sspBC/sspbctest/supplemental/request_without_banner_format.json new file mode 100644 index 00000000000..39322ae4b06 --- /dev/null +++ b/adapters/sspBC/sspbctest/supplemental/request_without_banner_format.json @@ -0,0 +1,141 @@ +{ + "mockBidRequest": { + "id": "test-request", + "imp": [ + { + "id": "slot", + "ext": { + "bidder": { + "siteId": "237503", + "id": "005" + } + }, + "secure": 1, + "banner": { + "format": [] + } + } + ], + "site": { + "id": "237503", + "domain": "test.page", + "page": "https://test.page/", + "ref": "https://test.referer/" + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "test_consent" + }, + "buyeruid": "test_user" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ssp.wp.test/bidder/?bdver=5.8&inver=4", + "body": { + "id": "test-request", + "imp": [ + { + "id": "005", + "banner": {}, + "tagid": "slot", + "secure": 1, + "ext": { + "data": { + "pbslot": "slot", + "pbsize": "1x1" + } + } + } + ], + "site": { + "id": "237503", + "domain": "test.page", + "page": "https://test.page/", + "ref": "https://test.referer/" + }, + "user": { + "ext": { + "consent": "test_consent" + }, + "buyeruid": "test_user" + }, + "regs": { + "ext": { + "gdpr": 1 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "id": "test-request", + "seatbid": [ + { + "bid": [ + { + "adm": "{\"bunch\":\"237503\",\"capping\":\"\",\"server\":\"s\",\"campaign\":\"c\",\"score\":\"1\",\"creations\":[{\"type\":\"image\",\"src\":\"https:\/\/bdr.wpcdn.pl\/tests\/test-300x250.png\",\"trackers\":{},\"width\":300,\"height\":250}],\"code\":\"1\",\"aabVendors\":false,\"slot\":\"005\",\"redir\":\"https:\/\/www.wp.pl\",\"wpclid\":\"1\"}", + "adomain": [ + "sspbc-test" + ], + "crid": "1234", + "ext": { + "adlabel": "Reklama", + "pubid": "431", + "siteid": "237503", + "slotid": "005", + "tagid": "slot" + }, + "w": 300, + "h": 250, + "id": "response-005", + "impid": "005", + "price": 20 + } + ], + "seat": "sspbc-test" + } + ], + "sn": "sspbc-test" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "response-005", + "impid": "slot", + "price": 20, + "adm": "<html><head><title><\/title><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><style> body { background-color: transparent; margin: 0; padding: 0; }<\/style><script> window.rekid = \"237503\"; window.slot = \"005\"; window.adlabel = 'Reklama'; window.pubid = '431'; window.wp_sn = 'sspbc_go'; window.page = 'https:\\\/\\\/test.page\\\/'; window.ref = 'https:\\\/\\\/test.referer\\\/'; window.mcad = {\"id\":\"test-request\",\"seat\":\"sspbc-test\",\"seatbid\":[{\"bid\":[{\"id\":\"response-005\",\"impid\":\"slot\",\"price\":20,\"adm\":\"{\\\"bunch\\\":\\\"237503\\\",\\\"capping\\\":\\\"\\\",\\\"server\\\":\\\"s\\\",\\\"campaign\\\":\\\"c\\\",\\\"score\\\":\\\"1\\\",\\\"creations\\\":[{\\\"type\\\":\\\"image\\\",\\\"src\\\":\\\"https://bdr.wpcdn.pl/tests/test-300x250.png\\\",\\\"trackers\\\":{},\\\"width\\\":300,\\\"height\\\":250}],\\\"code\\\":\\\"1\\\",\\\"aabVendors\\\":false,\\\"slot\\\":\\\"005\\\",\\\"redir\\\":\\\"https://www.wp.pl\\\",\\\"wpclid\\\":\\\"1\\\"}\",\"adomain\":[\"sspbc-test\"],\"crid\":\"1234\",\"w\":300,\"h\":250,\"ext\":{\"adlabel\":\"Reklama\",\"pubid\":\"431\",\"siteid\":\"237503\",\"slotid\":\"005\",\"tagid\":\"slot\"}}]}]}; window.inver = '4'; <\/script><\/head><body><div id=\"c\"><\/div><script async crossorigin nomodule src=\"\/\/std.wpcdn.pl\/wpjslib\/wpjslib-inline.js\" id=\"wpjslib\"><\/script><script async crossorigin type=\"module\" src=\"\/\/std.wpcdn.pl\/wpjslib6\/wpjslib-inline.js\" id=\"wpjslib6\"><\/script><\/body><\/html>", + "adomain": [ + "sspbc-test" + ], + "crid": "1234", + "w": 300, + "h": 250, + "ext": { + "adlabel": "Reklama", + "pubid": "431", + "siteid": "237503", + "slotid": "005", + "tagid": "slot" + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/sspBC/sspbctest/supplemental/request_without_ext_id.json b/adapters/sspBC/sspbctest/supplemental/request_without_ext_id.json new file mode 100644 index 00000000000..d6d92952a95 --- /dev/null +++ b/adapters/sspBC/sspbctest/supplemental/request_without_ext_id.json @@ -0,0 +1,150 @@ +{ + "mockBidRequest": { + "id": "test-request", + "imp": [ + { + "id": "slot", + "ext": { + "bidder": { + "siteId": "237503" + } + }, + "secure": 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "site": { + "domain": "test.page", + "page": "https://test.page/", + "ref": "https://test.referer/" + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "test_consent" + }, + "buyeruid": "test_user" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ssp.wp.test/bidder/?bdver=5.8&inver=4", + "body": { + "id": "test-request", + "imp": [ + { + "id": "slot", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "tagid": "slot", + "secure": 1, + "ext": { + "data": { + "pbslot": "slot", + "pbsize": "300x250" + } + } + } + ], + "site": { + "domain": "test.page", + "page": "https://test.page/", + "ref": "https://test.referer/" + }, + "user": { + "ext": { + "consent": "test_consent" + }, + "buyeruid": "test_user" + }, + "regs": { + "ext": { + "gdpr": 1 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "id": "test-request", + "seatbid": [ + { + "bid": [ + { + "adm": "{\"bunch\":\"237503\",\"capping\":\"\",\"server\":\"s\",\"campaign\":\"c\",\"score\":\"1\",\"creations\":[{\"type\":\"image\",\"src\":\"https:\/\/bdr.wpcdn.pl\/tests\/test-300x250.png\",\"trackers\":{},\"width\":300,\"height\":250}],\"code\":\"1\",\"aabVendors\":false,\"slot\":\"005\",\"redir\":\"https:\/\/www.wp.pl\",\"wpclid\":\"1\"}", + "adomain": [ + "sspbc-test" + ], + "crid": "1234", + "ext": { + "adlabel": "Reklama", + "pubid": "431", + "siteid": "237503", + "slotid": "005", + "tagid": "slot" + }, + "w": 300, + "h": 250, + "id": "response-005", + "impid": "slot", + "price": 20 + } + ], + "seat": "sspbc-test" + } + ], + "sn": "sspbc-test" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "response-005", + "impid": "slot", + "price": 20, + "adm": "<html><head><title><\/title><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><style> body { background-color: transparent; margin: 0; padding: 0; }<\/style><script> window.rekid = \"237503\"; window.slot = \"005\"; window.adlabel = 'Reklama'; window.pubid = '431'; window.wp_sn = 'sspbc_go'; window.page = 'https:\\\/\\\/test.page\\\/'; window.ref = 'https:\\\/\\\/test.referer\\\/'; window.mcad = {\"id\":\"test-request\",\"seat\":\"sspbc-test\",\"seatbid\":[{\"bid\":[{\"id\":\"response-005\",\"impid\":\"slot\",\"price\":20,\"adm\":\"{\\\"bunch\\\":\\\"237503\\\",\\\"capping\\\":\\\"\\\",\\\"server\\\":\\\"s\\\",\\\"campaign\\\":\\\"c\\\",\\\"score\\\":\\\"1\\\",\\\"creations\\\":[{\\\"type\\\":\\\"image\\\",\\\"src\\\":\\\"https://bdr.wpcdn.pl/tests/test-300x250.png\\\",\\\"trackers\\\":{},\\\"width\\\":300,\\\"height\\\":250}],\\\"code\\\":\\\"1\\\",\\\"aabVendors\\\":false,\\\"slot\\\":\\\"005\\\",\\\"redir\\\":\\\"https://www.wp.pl\\\",\\\"wpclid\\\":\\\"1\\\"}\",\"adomain\":[\"sspbc-test\"],\"crid\":\"1234\",\"w\":300,\"h\":250,\"ext\":{\"adlabel\":\"Reklama\",\"pubid\":\"431\",\"siteid\":\"237503\",\"slotid\":\"005\",\"tagid\":\"slot\"}}]}]}; window.inver = '4'; <\/script><\/head><body><div id=\"c\"><\/div><script async crossorigin nomodule src=\"\/\/std.wpcdn.pl\/wpjslib\/wpjslib-inline.js\" id=\"wpjslib\"><\/script><script async crossorigin type=\"module\" src=\"\/\/std.wpcdn.pl\/wpjslib6\/wpjslib-inline.js\" id=\"wpjslib6\"><\/script><\/body><\/html>", + "adomain": [ + "sspbc-test" + ], + "crid": "1234", + "w": 300, + "h": 250, + "ext": { + "adlabel": "Reklama", + "pubid": "431", + "siteid": "237503", + "slotid": "005", + "tagid": "slot" + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/sspBC/sspbctest/supplemental/request_without_ext_site_id.json b/adapters/sspBC/sspbctest/supplemental/request_without_ext_site_id.json new file mode 100644 index 00000000000..6d5858345df --- /dev/null +++ b/adapters/sspBC/sspbctest/supplemental/request_without_ext_site_id.json @@ -0,0 +1,151 @@ +{ + "mockBidRequest": { + "id": "test-request", + "imp": [ + { + "id": "slot", + "ext": { + "bidder": { + "siteId": "", + "id": "005" + } + }, + "secure": 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "site": { + "domain": "test.page", + "page": "https://test.page/", + "ref": "https://test.referer/" + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "test_consent" + }, + "buyeruid": "test_user" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ssp.wp.test/bidder/?bdver=5.8&inver=4", + "body": { + "id": "test-request", + "imp": [ + { + "id": "slot", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "tagid": "slot", + "secure": 1, + "ext": { + "data": { + "pbslot": "slot", + "pbsize": "300x250" + } + } + } + ], + "site": { + "domain": "test.page", + "page": "https://test.page/", + "ref": "https://test.referer/" + }, + "user": { + "ext": { + "consent": "test_consent" + }, + "buyeruid": "test_user" + }, + "regs": { + "ext": { + "gdpr": 1 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "id": "test-request", + "seatbid": [ + { + "bid": [ + { + "adm": "{\"bunch\":\"237503\",\"capping\":\"\",\"server\":\"s\",\"campaign\":\"c\",\"score\":\"1\",\"creations\":[{\"type\":\"image\",\"src\":\"https:\/\/bdr.wpcdn.pl\/tests\/test-300x250.png\",\"trackers\":{},\"width\":300,\"height\":250}],\"code\":\"1\",\"aabVendors\":false,\"slot\":\"005\",\"redir\":\"https:\/\/www.wp.pl\",\"wpclid\":\"1\"}", + "adomain": [ + "sspbc-test" + ], + "crid": "1234", + "ext": { + "adlabel": "Reklama", + "pubid": "431", + "siteid": "237503", + "slotid": "005", + "tagid": "slot" + }, + "w": 300, + "h": 250, + "id": "response-005", + "impid": "slot", + "price": 20 + } + ], + "seat": "sspbc-test" + } + ], + "sn": "sspbc-test" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "response-005", + "impid": "slot", + "price": 20, + "adm": "<html><head><title><\/title><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><style> body { background-color: transparent; margin: 0; padding: 0; }<\/style><script> window.rekid = \"237503\"; window.slot = \"005\"; window.adlabel = 'Reklama'; window.pubid = '431'; window.wp_sn = 'sspbc_go'; window.page = 'https:\\\/\\\/test.page\\\/'; window.ref = 'https:\\\/\\\/test.referer\\\/'; window.mcad = {\"id\":\"test-request\",\"seat\":\"sspbc-test\",\"seatbid\":[{\"bid\":[{\"id\":\"response-005\",\"impid\":\"slot\",\"price\":20,\"adm\":\"{\\\"bunch\\\":\\\"237503\\\",\\\"capping\\\":\\\"\\\",\\\"server\\\":\\\"s\\\",\\\"campaign\\\":\\\"c\\\",\\\"score\\\":\\\"1\\\",\\\"creations\\\":[{\\\"type\\\":\\\"image\\\",\\\"src\\\":\\\"https://bdr.wpcdn.pl/tests/test-300x250.png\\\",\\\"trackers\\\":{},\\\"width\\\":300,\\\"height\\\":250}],\\\"code\\\":\\\"1\\\",\\\"aabVendors\\\":false,\\\"slot\\\":\\\"005\\\",\\\"redir\\\":\\\"https://www.wp.pl\\\",\\\"wpclid\\\":\\\"1\\\"}\",\"adomain\":[\"sspbc-test\"],\"crid\":\"1234\",\"w\":300,\"h\":250,\"ext\":{\"adlabel\":\"Reklama\",\"pubid\":\"431\",\"siteid\":\"237503\",\"slotid\":\"005\",\"tagid\":\"slot\"}}]}]}; window.inver = '4'; <\/script><\/head><body><div id=\"c\"><\/div><script async crossorigin nomodule src=\"\/\/std.wpcdn.pl\/wpjslib\/wpjslib-inline.js\" id=\"wpjslib\"><\/script><script async crossorigin type=\"module\" src=\"\/\/std.wpcdn.pl\/wpjslib6\/wpjslib-inline.js\" id=\"wpjslib6\"><\/script><\/body><\/html>", + "adomain": [ + "sspbc-test" + ], + "crid": "1234", + "w": 300, + "h": 250, + "ext": { + "adlabel": "Reklama", + "pubid": "431", + "siteid": "237503", + "slotid": "005", + "tagid": "slot" + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/sspBC/sspbctest/supplemental/status_204.json b/adapters/sspBC/sspbctest/supplemental/status_204.json new file mode 100644 index 00000000000..931f59978bc --- /dev/null +++ b/adapters/sspBC/sspbctest/supplemental/status_204.json @@ -0,0 +1,95 @@ +{ + "mockBidRequest": { + "id": "test-request", + "imp": [ + { + "id": "slot", + "ext": { + "bidder": { + "siteId": "237503", + "id": "005" + } + }, + "secure": 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "site": { + "id": "237503", + "domain": "test.page", + "page": "https://test.page/", + "ref": "https://test.referer/" + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "test_consent" + }, + "buyeruid": "test_user" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ssp.wp.test/bidder/?bdver=5.8&inver=4", + "body": { + "id": "test-request", + "imp": [ + { + "id": "005", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "tagid": "slot", + "secure": 1, + "ext": { + "data": { + "pbslot": "slot", + "pbsize": "300x250" + } + } + } + ], + "site": { + "id": "237503", + "domain": "test.page", + "page": "https://test.page/", + "ref": "https://test.referer/" + }, + "user": { + "ext": { + "consent": "test_consent" + }, + "buyeruid": "test_user" + }, + "regs": { + "ext": { + "gdpr": 1 + } + } + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/sspBC/sspbctest/supplemental/status_400.json b/adapters/sspBC/sspbctest/supplemental/status_400.json new file mode 100644 index 00000000000..75a763b3a8e --- /dev/null +++ b/adapters/sspBC/sspbctest/supplemental/status_400.json @@ -0,0 +1,100 @@ +{ + "mockBidRequest": { + "id": "test-request", + "imp": [ + { + "id": "slot", + "ext": { + "bidder": { + "siteId": "237503", + "id": "005" + } + }, + "secure": 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "site": { + "id": "237503", + "domain": "test.page", + "page": "https://test.page/", + "ref": "https://test.referer/" + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "test_consent" + }, + "buyeruid": "test_user" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ssp.wp.test/bidder/?bdver=5.8&inver=4", + "body": { + "id": "test-request", + "imp": [ + { + "id": "005", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "tagid": "slot", + "secure": 1, + "ext": { + "data": { + "pbslot": "slot", + "pbsize": "300x250" + } + } + } + ], + "site": { + "id": "237503", + "domain": "test.page", + "page": "https://test.page/", + "ref": "https://test.referer/" + }, + "user": { + "ext": { + "consent": "test_consent" + }, + "buyeruid": "test_user" + }, + "regs": { + "ext": { + "gdpr": 1 + } + } + } + }, + "mockResponse": { + "status": 400, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/stroeerCore/params_test.go b/adapters/stroeerCore/params_test.go new file mode 100644 index 00000000000..92586189b6f --- /dev/null +++ b/adapters/stroeerCore/params_test.go @@ -0,0 +1,49 @@ +package stroeerCore + +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.BidderStroeerCore, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected stroeerCore 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.BidderStroeerCore, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"sid":"slot id"}`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{"something":"else"}`, + `{"sid":false}`, +} diff --git a/adapters/stroeerCore/stroeercore.go b/adapters/stroeerCore/stroeercore.go new file mode 100644 index 00000000000..72830b1d117 --- /dev/null +++ b/adapters/stroeerCore/stroeercore.go @@ -0,0 +1,117 @@ +package stroeerCore + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/prebid/openrtb/v17/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 { + URL string `json:"url"` + Server config.Server +} + +type response struct { + Bids []bidResponse `json:"bids"` +} + +type bidResponse struct { + ID string `json:"id"` + BidID string `json:"bidId"` + CPM float64 `json:"cpm"` + Width int64 `json:"width"` + Height int64 `json:"height"` + Ad string `json:"ad"` + CrID string `json:"crid"` +} + +func (a *adapter) MakeBids(bidRequest *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if responseData.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected http status code: %d.", responseData.StatusCode), + }} + } + + var errors []error + stroeerResponse := response{} + + if err := json.Unmarshal(responseData.Body, &stroeerResponse); err != nil { + errors = append(errors, err) + return nil, errors + } + + bidderResponse := adapters.NewBidderResponseWithBidsCapacity(len(stroeerResponse.Bids)) + bidderResponse.Currency = "EUR" + + for _, bid := range stroeerResponse.Bids { + openRtbBid := openrtb2.Bid{ + ID: bid.ID, + ImpID: bid.BidID, + W: bid.Width, + H: bid.Height, + Price: bid.CPM, + AdM: bid.Ad, + CrID: bid.CrID, + } + + bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{ + Bid: &openRtbBid, + BidType: openrtb_ext.BidTypeBanner, + }) + } + + return bidderResponse, errors +} + +func (a *adapter) MakeRequests(bidRequest *openrtb2.BidRequest, extraRequestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var errors []error + + for idx := range bidRequest.Imp { + imp := &bidRequest.Imp[idx] + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + errors = append(errors, err) + continue + } + + var stroeerExt openrtb_ext.ExtImpStroeerCore + if err := json.Unmarshal(bidderExt.Bidder, &stroeerExt); err != nil { + errors = append(errors, err) + continue + } + + imp.TagID = stroeerExt.Sid + } + + reqJSON, err := json.Marshal(bidRequest) + if err != nil { + errors = append(errors, err) + return nil, errors + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + + return []*adapters.RequestData{{ + Method: "POST", + Uri: a.URL, + Body: reqJSON, + Headers: headers, + }}, errors +} + +// Builder builds a new instance of the StroeerCore adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + bidder := &adapter{ + URL: config.Endpoint, + } + return bidder, nil +} diff --git a/adapters/stroeerCore/stroeercore_test.go b/adapters/stroeerCore/stroeercore_test.go new file mode 100644 index 00000000000..fc7a680add0 --- /dev/null +++ b/adapters/stroeerCore/stroeercore_test.go @@ -0,0 +1,20 @@ +package stroeerCore + +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.BidderStroeerCore, config.Adapter{ + Endpoint: "http://localhost/s2sdsh"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "stroeercoretest", bidder) +} diff --git a/adapters/stroeerCore/stroeercoretest/exemplary/mobile-banner-single.json b/adapters/stroeerCore/stroeercoretest/exemplary/mobile-banner-single.json new file mode 100644 index 00000000000..0abdbc8b499 --- /dev/null +++ b/adapters/stroeerCore/stroeercoretest/exemplary/mobile-banner-single.json @@ -0,0 +1,188 @@ +{ + "mockBidRequest": { + "id": "auction-id", + "cur": ["EUR"], + "imp": [ + { + "id": "3", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "sid": "123456" + } + } + } + ], + "device": { + "geo":{ + "city":"Paris", + "country":"FRA", + "lat": 48.860757, + "lon": 2.337562, + "region":"IDF", + "zip":"75001" + }, + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0, + "devicetype": 4, + "ifa": "e785543f-0be8-48f1-2c53-b4ae46a942d3", + "hwv": "6S", + "make": "Apple", + "model": "iPhone", + "os": "iOS", + "osv": "8.1" + }, + "app": { + "id": "test-app-id", + "name": "test-app-name", + "bundle": "test-app-bundle", + "domain": "test-app-domain", + "storeurl": "test-app-storeurl", + "cat": [ + "IAB9-30", + "IAB9-5" + ], + "ver": "1.0", + "privacypolicy": 1, + "paid": 2, + "publisher": { + "domain": "topleveldomain.com", + "id": "04241e0b1cc98976858ce16377c7eef4", + "name": "App Publisher" + }, + "keywords": "game,puzzle" + }, + "user": { + "buyeruid": "test-buyer-user-id" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"] + }, + "uri": "http://localhost/s2sdsh", + "body": { + "id": "auction-id", + "cur": ["EUR"], + "imp": [ + { + "id": "3", + "tagid": "123456", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "sid": "123456" + } + } + } + ], + "device": { + "geo":{ + "city":"Paris", + "country":"FRA", + "lat": 48.860757, + "lon": 2.337562, + "region":"IDF", + "zip":"75001" + }, + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0, + "devicetype": 4, + "ifa": "e785543f-0be8-48f1-2c53-b4ae46a942d3", + "hwv": "6S", + "make": "Apple", + "model": "iPhone", + "os": "iOS", + "osv": "8.1" + }, + "app": { + "id": "test-app-id", + "name": "test-app-name", + "bundle": "test-app-bundle", + "domain": "test-app-domain", + "storeurl": "test-app-storeurl", + "cat": [ + "IAB9-30", + "IAB9-5" + ], + "ver": "1.0", + "privacypolicy": 1, + "paid": 2, + "publisher": { + "domain": "topleveldomain.com", + "id": "04241e0b1cc98976858ce16377c7eef4", + "name": "App Publisher" + }, + "keywords": "game,puzzle" + }, + "user": { + "buyeruid": "test-buyer-user-id" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "bids": [ + { + "id": "4985939039393-04", + "bidId": "3", + "cpm": 0.24, + "width": 468, + "height": 60, + "ad": "advert", + "crid": "XYZijk" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids" : [{ + "bid": { + "id": "4985939039393-04", + "impid": "3", + "price": 0.24, + "adm": "advert", + "w": 468, + "h": 60, + "crid": "XYZijk" + }, + "type": "banner" + }] + } + ] +} diff --git a/adapters/stroeerCore/stroeercoretest/exemplary/site-banner-multi.json b/adapters/stroeerCore/stroeercoretest/exemplary/site-banner-multi.json new file mode 100644 index 00000000000..091f244c6ef --- /dev/null +++ b/adapters/stroeerCore/stroeercoretest/exemplary/site-banner-multi.json @@ -0,0 +1,188 @@ +{ + "mockBidRequest": { + "id": "auction-id", + "cur": ["EUR"], + "imp": [ + { + "id": "3", + "banner": { + "format": [ + { + "w": 468, + "h": 60 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "sid": "123456" + } + } + }, + { + "id": "9", + "banner": { + "format": [ + { + "w": 770, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "sid": "85310" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/some/path", + "ext": { + "amp": 1 + } + }, + "user": { + "buyeruid": "test-buyer-user-id" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"] + }, + "uri": "http://localhost/s2sdsh", + "body": { + "id": "auction-id", + "cur": ["EUR"], + "imp": [ + { + "id": "3", + "tagid": "123456", + "banner": { + "format": [ + { + "w": 468, + "h": 60 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "sid": "123456" + } + } + }, + { + "id": "9", + "tagid": "85310", + "banner": { + "format": [ + { + "w": 770, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "sid": "85310" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/some/path", + "ext": { + "amp": 1 + } + }, + "user": { + "buyeruid": "test-buyer-user-id" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "bids": [ + { + "id": "3929239282-01", + "bidId": "3", + "cpm": 2, + "width": 468, + "height": 60, + "ad": "advert", + "crid": "qwert" + }, + { + "id": "3929239282-02", + "bidId": "9", + "cpm": 7.21, + "width": 770, + "height": 250, + "ad": "another advert", + "crid": "wasd" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "id": "3929239282-01", + "impid": "3", + "price": 2, + "adm": "advert", + "w": 468, + "h": 60, + "crid": "qwert" + }, + "type": "banner" + }, + { + "bid": { + "id": "3929239282-02", + "impid": "9", + "price": 7.21, + "adm": "another advert", + "w": 770, + "h": 250, + "crid": "wasd" + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/stroeerCore/stroeercoretest/exemplary/site-banner-single.json b/adapters/stroeerCore/stroeercoretest/exemplary/site-banner-single.json new file mode 100644 index 00000000000..ca6ea0c26ea --- /dev/null +++ b/adapters/stroeerCore/stroeercoretest/exemplary/site-banner-single.json @@ -0,0 +1,132 @@ +{ + "mockBidRequest": { + "id": "auction-id", + "cur": ["EUR"], + "imp": [ + { + "id": "3", + "banner": { + "format": [ + { + "w": 468, + "h": 60 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "sid": "123456" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/some/path", + "ext": { + "amp": 0 + } + }, + "user": { + "buyeruid": "test-buyer-user-id" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"] + }, + "uri": "http://localhost/s2sdsh", + "body": { + "id": "auction-id", + "cur": ["EUR"], + "imp": [ + { + "id": "3", + "tagid": "123456", + "banner": { + "format": [ + { + "w": 468, + "h": 60 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "sid": "123456" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/some/path", + "ext": { + "amp": 0 + } + }, + "user": { + "buyeruid": "test-buyer-user-id" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "bids": [ + { + "id": "8923356982838-09", + "bidId": "3", + "cpm": 2, + "width": 468, + "height": 60, + "ad": "advert", + "crid": "wasd" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids" : [{ + "bid": { + "id": "8923356982838-09", + "impid": "3", + "price": 2, + "adm": "advert", + "w": 468, + "h": 60, + "crid": "wasd" + }, + "type": "banner" + }] + } + ] +} diff --git a/adapters/stroeerCore/stroeercoretest/supplemental/bad-server-response.json b/adapters/stroeerCore/stroeercoretest/supplemental/bad-server-response.json new file mode 100644 index 00000000000..48275916b2e --- /dev/null +++ b/adapters/stroeerCore/stroeercoretest/supplemental/bad-server-response.json @@ -0,0 +1,59 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "banner": { + "w": 300, + "h": 200 + }, + "ext": { + "bidder": { + "sid": "tagid" + } + } + } + ], + "user": { + "buyeruid": "test-buyer-user-id" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/s2sdsh", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "tagid": "tagid", + "banner": { + "w": 300, + "h": 200 + }, + "ext": { + "bidder": { + "sid": "tagid" + } + } + } + ], + "user": { + "buyeruid": "test-buyer-user-id" + } + } + }, + "mockResponse": { + "status": 500 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected http status code: 500.", + "comparison": "literal" + } + ] +} diff --git a/adapters/suntContent/params_test.go b/adapters/suntContent/params_test.go new file mode 100644 index 00000000000..653ed948d46 --- /dev/null +++ b/adapters/suntContent/params_test.go @@ -0,0 +1,43 @@ +package suntContent + +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.BidderSuntContent, 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.BidderSuntContent, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} + +var validParams = []string{ + `{"adUnitId": "1234"}`, + `{"adUnitId": "AB12"}`, +} + +var invalidParams = []string{ + `{"adUnitId": 42}`, +} diff --git a/adapters/suntContent/suntContent.go b/adapters/suntContent/suntContent.go new file mode 100644 index 00000000000..03fa416fb93 --- /dev/null +++ b/adapters/suntContent/suntContent.go @@ -0,0 +1,145 @@ +package suntContent + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" + "strings" + + "github.com/prebid/openrtb/v17/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(_ openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, extraRequestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + for i := range request.Imp { + if err := addTagID(&request.Imp[i]); err != nil { + return nil, []error{err} + } + } + + if !curExists(request.Cur, "EUR") { + request.Cur = append(request.Cur, "EUR") + } + + requestJSON, err := json.Marshal(request) + if err != nil { + return nil, []error{err} + } + + requestData := &adapters.RequestData{ + Method: http.MethodPost, + Uri: a.endpoint, + Body: requestJSON, + } + + return []*adapters.RequestData{requestData}, nil +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if responseData.StatusCode == http.StatusNoContent { + return nil, nil + } + + if responseData.StatusCode == http.StatusBadRequest { + err := &errortypes.BadInput{ + Message: "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.", + } + return nil, []error{err} + } + + if responseData.StatusCode != http.StatusOK { + err := &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info.", responseData.StatusCode), + } + return nil, []error{err} + } + + var response openrtb2.BidResponse + if err := json.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + bidResponse.Currency = response.Cur + + var errs []error + + for _, seatBid := range response.SeatBid { + for i := range seatBid.Bid { + resolvePriceMacro(&seatBid.Bid[i]) + + bidType, err := getMediaTypeForBid(seatBid.Bid[i].Ext) + if err != nil { + errs = append(errs, err) + continue + } + + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &seatBid.Bid[i], + BidType: bidType, + }) + } + } + + return bidResponse, errs +} + +func resolvePriceMacro(bid *openrtb2.Bid) { + price := strconv.FormatFloat(bid.Price, 'f', -1, 64) + bid.AdM = strings.Replace(bid.AdM, "${AUCTION_PRICE}", price, -1) +} + +func getMediaTypeForBid(ext json.RawMessage) (openrtb_ext.BidType, error) { + var bidExt openrtb_ext.ExtBid + + if err := json.Unmarshal(ext, &bidExt); err != nil { + return "", fmt.Errorf("could not unmarshal openrtb_ext.ExtBid: %w", err) + } + + if bidExt.Prebid == nil { + return "", fmt.Errorf("bid.Ext.Prebid is empty") + } + + return openrtb_ext.ParseBidType(string(bidExt.Prebid.Type)) +} + +func curExists(allowedCurrencies []string, newCurrency string) bool { + for i := range allowedCurrencies { + if allowedCurrencies[i] == newCurrency { + return true + } + } + return false +} + +func addTagID(imp *openrtb2.Imp) error { + var ext adapters.ExtImpBidder + var extSA openrtb_ext.ImpExtSuntContent + + if err := json.Unmarshal(imp.Ext, &ext); err != nil { + return fmt.Errorf("could not unmarshal adapters.ExtImpBidder: %w", err) + } + + if err := json.Unmarshal(ext.Bidder, &extSA); err != nil { + return fmt.Errorf("could not unmarshal openrtb_ext.ImpExtSuntContent: %w", err) + } + + imp.TagID = extSA.AdUnitID + + return nil +} diff --git a/adapters/suntContent/suntContent_test.go b/adapters/suntContent/suntContent_test.go new file mode 100644 index 00000000000..e0915125381 --- /dev/null +++ b/adapters/suntContent/suntContent_test.go @@ -0,0 +1,194 @@ +package suntContent + +import ( + "encoding/json" + "testing" + + "github.com/prebid/openrtb/v17/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" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderSuntContent, config.Adapter{ + Endpoint: "https://mockup.suntcontent.com/", + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "suntContenttest", bidder) +} + +func TestResolvePriceMacro(t *testing.T) { + adm := `{"link":{"url":"https://some_url.com/abc123?wp=${AUCTION_PRICE}"}` + want := `{"link":{"url":"https://some_url.com/abc123?wp=12.34"}` + + bid := openrtb2.Bid{AdM: adm, Price: 12.34} + resolvePriceMacro(&bid) + + if bid.AdM != want { + t.Fatalf("want: %v, got: %v", want, bid.AdM) + } +} + +func TestGetMediaTypeForBid(t *testing.T) { + tests := []struct { + name string + want openrtb_ext.BidType + invalidJSON bool + wantErr bool + wantErrContain string + bidType openrtb_ext.BidType + }{ + { + name: "native", + want: openrtb_ext.BidTypeNative, + invalidJSON: false, + wantErr: false, + wantErrContain: "", + bidType: "native", + }, + { + name: "banner", + want: openrtb_ext.BidTypeBanner, + invalidJSON: false, + wantErr: false, + wantErrContain: "", + bidType: "banner", + }, + { + name: "video", + want: openrtb_ext.BidTypeVideo, + invalidJSON: false, + wantErr: false, + wantErrContain: "", + bidType: "video", + }, + { + name: "audio", + want: openrtb_ext.BidTypeAudio, + invalidJSON: false, + wantErr: false, + wantErrContain: "", + bidType: "audio", + }, + { + name: "empty type", + want: "", + invalidJSON: false, + wantErr: true, + wantErrContain: "invalid BidType", + bidType: "", + }, + { + name: "invalid type", + want: "", + invalidJSON: false, + wantErr: true, + wantErrContain: "invalid BidType", + bidType: "invalid", + }, + { + name: "invalid json", + want: "", + invalidJSON: true, + wantErr: true, + wantErrContain: "bid.Ext.Prebid is empty", + bidType: "", + }, + } + + for _, test := range tests { + var bid openrtb2.SeatBid + var extBid openrtb_ext.ExtBid + + var bidExtJsonString string + if test.invalidJSON { + bidExtJsonString = `{"x_prebid": {"type":""}}` + } else { + bidExtJsonString = `{"prebid": {"type":"` + string(test.bidType) + `"}}` + } + + if err := bid.Ext.UnmarshalJSON([]byte(bidExtJsonString)); err != nil { + t.Fatalf("unexpected error %v", err) + } + + if err := json.Unmarshal(bid.Ext, &extBid); err != nil { + t.Fatalf("could not unmarshal extBid: %v", err) + } + + got, gotErr := getMediaTypeForBid(bid.Ext) + assert.Equal(t, test.want, got) + + if test.wantErr { + if gotErr != nil { + assert.Contains(t, gotErr.Error(), test.wantErrContain) + continue + } + t.Fatalf("wantErr: %v, gotErr: %v", test.wantErr, gotErr) + } + } +} + +func TestAddTagID(t *testing.T) { + tests := []struct { + name string + want string + data string + wantErr bool + }{ + {"regular case", "abc123", "abc123", false}, + {"nil case", "", "", false}, + {"unmarshal err case", "", "", true}, + } + + for _, test := range tests { + extSA, err := json.Marshal(openrtb_ext.ImpExtSuntContent{AdUnitID: test.data}) + if err != nil { + t.Fatalf("unexpected error %v", err) + } + + extBidder, err := json.Marshal(adapters.ExtImpBidder{Bidder: extSA}) + if err != nil { + t.Fatalf("unexpected error %v", err) + } + + if test.wantErr { + extBidder = []byte{} + } + + ortbImp := openrtb2.Imp{Ext: extBidder} + + if err := addTagID(&ortbImp); err != nil { + if test.wantErr { + continue + } + t.Fatalf("unexpected error %v", err) + } + + if test.want != ortbImp.TagID { + t.Fatalf("want: %v, got: %v", test.want, ortbImp.TagID) + } + } +} + +func TestCurExists(t *testing.T) { + tests := []struct { + name string + cur string + data []string + want bool + }{ + {"no eur", "EUR", []string{"USD"}, false}, + {"eur exists", "EUR", []string{"USD", "EUR"}, true}, + } + + for _, test := range tests { + got := curExists(test.data, test.cur) + assert.Equal(t, test.want, got) + } +} diff --git a/adapters/suntContent/suntContenttest/exemplary/banner.json b/adapters/suntContent/suntContenttest/exemplary/banner.json new file mode 100644 index 00000000000..153ba0dacde --- /dev/null +++ b/adapters/suntContent/suntContenttest/exemplary/banner.json @@ -0,0 +1,140 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "publisher": { + "id": "foo", + "name": "foo" + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "adUnitId": "example-tag-id" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://mockup.suntcontent.com/", + "body": { + "cur": [ + "EUR" + ], + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "example-tag-id", + "ext": { + "bidder": { + "adUnitId": "example-tag-id" + } + } + } + ], + "site": { + "publisher": { + "id": "foo", + "name": "foo" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "123", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "12341234", + "adm": "some-test-ad", + "adomain": [ + "domain.com" + ], + "iurl": "http://abc.com/cr?id=12341234", + "cid": "123", + "crid": "12341234", + "h": 250, + "w": 300, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "EUR" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "12341234", + "adomain": [ + "domain.com" + ], + "iurl": "http://abc.com/cr?id=12341234", + "cid": "123", + "crid": "12341234", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/suntContent/suntContenttest/exemplary/native.json b/adapters/suntContent/suntContenttest/exemplary/native.json new file mode 100644 index 00000000000..89ef4e07dfb --- /dev/null +++ b/adapters/suntContent/suntContenttest/exemplary/native.json @@ -0,0 +1,109 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "publisher": { + "id": "foo", + "name": "foo" + } + }, + "cur": [ + "EUR" + ], + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" + }, + "ext": { + "bidder": { + "adUnitId": "example-tag-id" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://mockup.suntcontent.com/", + "body": { + "cur": [ + "EUR" + ], + "id": "test-request-id", + "imp": [ + { + "tagid": "example-tag-id", + "id": "test-imp-id", + "native": { + "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" + }, + "ext": { + "bidder": { + "adUnitId": "example-tag-id" + } + } + } + ], + "site": { + "publisher": { + "id": "foo", + "name": "foo" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "some-id", + "impid": "test-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\": 3000,\"h\": 2250}},{\"id\": 1,\"title\":{\"text\":\"This is an example Prebid Native creative\"}},{\"id\": 3,\"data\":{\"value\":\"Prebid.org\"}},{\"id\": 4,\"data\":{\"value\":\"This is a Prebid Native Creative. There are many like it, but this one is mine.\"}}],\"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}\"],\"jstracker\":\"<script src=\\\"http://www.dummyurl.js\\\"></script>\"}", + "ext": { + "prebid": { + "type": "native" + } + } + } + ], + "seat": "123" + } + ], + "bidid": "8141327771600527856", + "cur": "EUR" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "id": "some-id", + "impid": "test-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\": 3000,\"h\": 2250}},{\"id\": 1,\"title\":{\"text\":\"This is an example Prebid Native creative\"}},{\"id\": 3,\"data\":{\"value\":\"Prebid.org\"}},{\"id\": 4,\"data\":{\"value\":\"This is a Prebid Native Creative. There are many like it, but this one is mine.\"}}],\"link\":{\"url\":\"http://example.com/click?AAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwhdYz3ZyNFNG3fXpZUyLXNZ0o5aAAAAACrElgC-AwAAvgMAAAIAAAC98iUEeP4QAAAAAABVU0QAVVNEAAEAAQARIAAAAAABAgQCAAAAAAEAhBaSXgAAAAA./pp=1/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=1\"],\"jstracker\":\"<script src=\\\"http://www.dummyurl.js\\\"></script>\"}", + "ext": { + "prebid": { + "type": "native" + } + } + }, + "type": "native" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/suntContent/suntContenttest/supplemental/invalid_tag_id.json b/adapters/suntContent/suntContenttest/supplemental/invalid_tag_id.json new file mode 100644 index 00000000000..1493d336f9d --- /dev/null +++ b/adapters/suntContent/suntContenttest/supplemental/invalid_tag_id.json @@ -0,0 +1,30 @@ +{ + "expectedMakeRequestsErrors": [ + { + "value": "could not unmarshal openrtb_ext.ImpExtSuntContent: json: cannot unmarshal number into Go struct field ImpExtSuntContent.adUnitID of type string", + "comparison": "literal" + } + ], + "mockBidRequest": { + "id": "test-request-id", + "site": { + "publisher": { + "id": "foo", + "name": "foo" + } + }, + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" + }, + "ext": { + "bidder": { + "adUnitId": 1234 + } + } + } + ] + } +} \ No newline at end of file diff --git a/adapters/suntContent/suntContenttest/supplemental/status_bad_request.json b/adapters/suntContent/suntContenttest/supplemental/status_bad_request.json new file mode 100644 index 00000000000..d7e62bb90cb --- /dev/null +++ b/adapters/suntContent/suntContenttest/supplemental/status_bad_request.json @@ -0,0 +1,72 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "adUnitId": "example-tag-id" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://mockup.suntcontent.com/", + "body": { + "cur": [ + "EUR" + ], + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "example-tag-id", + "ext": { + "bidder": { + "adUnitId": "example-tag-id" + } + } + } + ] + } + }, + "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/suntContent/suntContenttest/supplemental/status_no_content.json b/adapters/suntContent/suntContenttest/supplemental/status_no_content.json new file mode 100644 index 00000000000..8eda774c657 --- /dev/null +++ b/adapters/suntContent/suntContenttest/supplemental/status_no_content.json @@ -0,0 +1,66 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "adUnitId": "example-tag-id" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://mockup.suntcontent.com/", + "body": { + "cur": [ + "EUR" + ], + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "example-tag-id", + "ext": { + "bidder": { + "adUnitId": "example-tag-id" + } + } + } + ] + } + }, + "mockResponse": { + "status": 204 + } + } + ] +} \ No newline at end of file diff --git a/adapters/suntContent/suntContenttest/supplemental/status_not_ok.json b/adapters/suntContent/suntContenttest/supplemental/status_not_ok.json new file mode 100644 index 00000000000..07618aed7fa --- /dev/null +++ b/adapters/suntContent/suntContenttest/supplemental/status_not_ok.json @@ -0,0 +1,72 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "adUnitId": "example-tag-id" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://mockup.suntcontent.com/", + "body": { + "cur": [ + "EUR" + ], + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "example-tag-id", + "ext": { + "bidder": { + "adUnitId": "example-tag-id" + } + } + } + ] + } + }, + "mockResponse": { + "status": 404 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 404. Run with request.debug = 1 for more info.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/synacormedia/params_test.go b/adapters/synacormedia/params_test.go index a216818e382..edb9a2a44b6 100644 --- a/adapters/synacormedia/params_test.go +++ b/adapters/synacormedia/params_test.go @@ -9,7 +9,7 @@ import ( // This file actually intends to test static/bidder-params/synacormedia.json // -// These also validate the format of the external API: request.imp[i].ext.synacormedia +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.synacormedia // TestValidParams makes sure that the synacormedia schema accepts all imp.ext fields which we intend to support. func TestValidParams(t *testing.T) { diff --git a/adapters/synacormedia/synacormedia.go b/adapters/synacormedia/synacormedia.go index c6a19bd0005..3a882b71223 100644 --- a/adapters/synacormedia/synacormedia.go +++ b/adapters/synacormedia/synacormedia.go @@ -6,7 +6,7 @@ import ( "net/http" "text/template" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -28,7 +28,7 @@ type ReqExt struct { } // Builder builds a new instance of the SynacorMedia adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (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) diff --git a/adapters/synacormedia/synacormedia_test.go b/adapters/synacormedia/synacormedia_test.go index 6a018dc3287..d4e5e70f568 100644 --- a/adapters/synacormedia/synacormedia_test.go +++ b/adapters/synacormedia/synacormedia_test.go @@ -11,7 +11,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderSynacormedia, config.Adapter{ - Endpoint: "http://{{.Host}}.technoratimedia.com/openrtb/bids/{{.Host}}"}) + Endpoint: "http://{{.Host}}.technoratimedia.com/openrtb/bids/{{.Host}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -22,7 +22,7 @@ func TestJsonSamples(t *testing.T) { func TestEndpointTemplateMalformed(t *testing.T) { _, buildErr := Builder(openrtb_ext.BidderSynacormedia, config.Adapter{ - Endpoint: "{{Malformed}}"}) + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Error(t, buildErr) } diff --git a/adapters/taboola/params_test.go b/adapters/taboola/params_test.go new file mode 100644 index 00000000000..0ac740a860b --- /dev/null +++ b/adapters/taboola/params_test.go @@ -0,0 +1,51 @@ +package taboola + +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 schema. %v", err) + } + + for _, p := range validParams { + if err := validator.Validate(openrtb_ext.BidderTaboola, 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.BidderTaboola, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} + +var validParams = []string{ + `{"publisherId" : "1", "tagid": "tag-id-for-example"}`, + `{"publisherId" : "1", "tagid": "tag-id-for-example", "bcat": ["excluded-category"], "badv": ["excluded-advertiser"], "bidfloor": 1.2, "publisherDomain": "http://domain.com"}`, +} + +var invalidParams = []string{ + `{}`, + `{"publisherId" : "1"}`, + `{"publisherId" : 1, "tagid": "tag-id-for-example"}`, + `{"publisherId" : "1"", "tagid": 2}`, + `{"publisherId" : "1", "tagid": "tag-id-for-example", "bcat":"incorrect-type"}`, + `{"publisherId" : "1", "tagid": "tag-id-for-example", "badv":"incorrect-type"}`, + `{"publisherId" : "1", "tagid": "tag-id-for-example", "bidfloor":"incorrect-type"}`, + `{"publisherId" : "1", "tagid": "tag-id-for-example", "publisherDomain":1}`, + `{"publisherId" : "1", "bcat": ["excluded-category"], "badv": ["excluded-advertiser"], "bidfloor": 1.2, "publisherDomain": "http://domain.com"}`, + `{"tagid": "tag-id-for-example", "bcat": ["excluded-category"], "badv": ["excluded-advertiser"], "bidfloor": 1.2, "publisherDomain": "http://domain.com"}`, +} diff --git a/adapters/taboola/taboola.go b/adapters/taboola/taboola.go new file mode 100644 index 00000000000..ecd6fcbf609 --- /dev/null +++ b/adapters/taboola/taboola.go @@ -0,0 +1,173 @@ +package taboola + +import ( + "encoding/json" + "fmt" + "github.com/prebid/openrtb/v17/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 +} + +// Builder builds a new instance of the Foo adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + + taboolaRequest, errs := createTaboolaRequest(request) + if len(errs) > 0 { + return nil, errs + } + + requestJSON, err := json.Marshal(taboolaRequest) + if err != nil { + return nil, []error{err} + } + + requestData := &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint + "/" + taboolaRequest.Site.ID, + Body: requestJSON, + } + + return []*adapters.RequestData{requestData}, errs +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + var errs []error + + if responseData.StatusCode == http.StatusNoContent { + return nil, nil + } + + if responseData.StatusCode == http.StatusBadRequest { + err := &errortypes.BadInput{ + Message: "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.", + } + return nil, []error{err} + } + + if responseData.StatusCode != http.StatusOK { + err := &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info.", responseData.StatusCode), + } + return nil, []error{err} + } + + var response openrtb2.BidResponse + if err := json.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + bidResponse.Currency = response.Cur + for _, seatBid := range response.SeatBid { + for i := range seatBid.Bid { + bidType, err := getMediaType(seatBid.Bid[i].ImpID, request.Imp) + if err != nil { + errs = append(errs, err) + continue + } + b := &adapters.TypedBid{ + Bid: &seatBid.Bid[i], + BidType: bidType, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + return bidResponse, errs +} + +func createTaboolaRequest(request *openrtb2.BidRequest) (taboolaRequest *openrtb2.BidRequest, errors []error) { + modifiedRequest := *request + var errs []error + + var taboolaExt openrtb_ext.ImpExtTaboola + for i := 0; i < len(modifiedRequest.Imp); i++ { + imp := modifiedRequest.Imp[i] + + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + errs = append(errs, err) + continue + } + if err := json.Unmarshal(bidderExt.Bidder, &taboolaExt); err != nil { + errs = append(errs, err) + continue + } + if taboolaExt.TagId != "" { + imp.TagID = taboolaExt.TagId + modifiedRequest.Imp[i] = imp + } + if taboolaExt.BidFloor != 0 { + imp.BidFloor = taboolaExt.BidFloor + modifiedRequest.Imp[i] = imp + } + } + + publisher := &openrtb2.Publisher{ + ID: taboolaExt.PublisherId, + } + + if modifiedRequest.Site == nil { + newSite := &openrtb2.Site{ + ID: taboolaExt.PublisherId, + Name: taboolaExt.PublisherId, + Domain: evaluateDomain(taboolaExt.PublisherDomain, request), + Publisher: publisher, + } + modifiedRequest.Site = newSite + } else { + modifiedSite := *modifiedRequest.Site + modifiedSite.Publisher = publisher + modifiedSite.ID = taboolaExt.PublisherId + modifiedSite.Name = taboolaExt.PublisherId + modifiedSite.Domain = evaluateDomain(taboolaExt.PublisherDomain, request) + modifiedRequest.Site = &modifiedSite + } + + if taboolaExt.BCat != nil { + modifiedRequest.BCat = taboolaExt.BCat + } + + if taboolaExt.BAdv != nil { + modifiedRequest.BAdv = taboolaExt.BAdv + } + + return &modifiedRequest, errs +} + +func getMediaType(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 + } + } + } + + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Failed to find banner impression \"%s\" ", impID), + } +} + +func evaluateDomain(publisherDomain string, request *openrtb2.BidRequest) (result string) { + if publisherDomain != "" { + return publisherDomain + } + if request.Site != nil { + return request.Site.Domain + } + return "" +} diff --git a/adapters/taboola/taboola_test.go b/adapters/taboola/taboola_test.go new file mode 100644 index 00000000000..9233a2cfcb2 --- /dev/null +++ b/adapters/taboola/taboola_test.go @@ -0,0 +1,22 @@ +package taboola + +import ( + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "testing" +) + +import ( + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderOutbrain, config.Adapter{ + Endpoint: "http://whatever.com"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "taboolatest", bidder) +} diff --git a/adapters/taboola/taboolatest/exemplary/banner.json b/adapters/taboola/taboolatest/exemplary/banner.json new file mode 100644 index 00000000000..13309f7a7dd --- /dev/null +++ b/adapters/taboola/taboolatest/exemplary/banner.json @@ -0,0 +1,152 @@ +{ + "mockBidRequest": { + "id": "request-id", + "imp": [ + { + "id": "impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + }, + { + "w": 160, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "publisher-id", + "tagid": "tag-id" + } + } + } + ], + "site": { + "domain": "http://domain.com", + "page": "http://page-domain.com", + "ref": "http://page-domain.com" + }, + "device": { + "ua": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.62 Mobile Safari/537.36", + "h": 300, + "w": 300 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.com/publisher-id", + "body": { + "id": "request-id", + "imp": [ + { + "id": "impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + }, + { + "w": 160, + "h": 600 + } + ] + }, + "tagid" : "tag-id", + "ext": { + "bidder": { + "publisherId": "publisher-id", + "tagid": "tag-id" + } + } + } + ], + "site": { + "id": "publisher-id", + "name": "publisher-id", + "domain": "http://domain.com", + "page": "http://page-domain.com", + "ref": "http://page-domain.com", + "publisher": { + "id": "publisher-id" + } + }, + "device": { + "ua": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.62 Mobile Safari/537.36", + "h": 300, + "w": 300 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "123", + "seatbid": [ + { + "bid": [ + { + "id": "request-id", + "impid": "impression-id", + "price": 2.1, + "adid": "1", + "adm": "<hrml></html>", + "adomain": [ + "adomain.com" + ], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "exp": 60, + "lurl": "https://lurl.domain.com/" + } + ], + "seat": "1111" + } + ], + "bidid": "123", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "request-id", + "impid": "impression-id", + "price": 2.1, + "adid": "1", + "adm": "<hrml></html>", + "adomain": [ + "adomain.com" + ], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "exp": 60, + "lurl": "https://lurl.domain.com/" + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/taboola/taboolatest/supplemental/bidParamsOverrideRequestFields.json b/adapters/taboola/taboolatest/supplemental/bidParamsOverrideRequestFields.json new file mode 100644 index 00000000000..267b5173150 --- /dev/null +++ b/adapters/taboola/taboolatest/supplemental/bidParamsOverrideRequestFields.json @@ -0,0 +1,156 @@ +{ + "mockBidRequest": { + "id": "request-id", + "imp": [ + { + "id": "impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + }, + { + "w": 160, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "publisher-id", + "tagid": "tag-id", + "publisherDomain": "http://domain.com" + } + } + } + ], + "site": { + "id": "dummy-site-id", + "name": "dummy-site-id", + "domain": "http://dummy-domain.com", + "page": "http://page-domain.com", + "ref": "http://page-domain.com" + }, + "device": { + "ua": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.62 Mobile Safari/537.36", + "h": 300, + "w": 300 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.com/publisher-id", + "body": { + "id": "request-id", + "imp": [ + { + "id": "impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + }, + { + "w": 160, + "h": 600 + } + ] + }, + "tagid" : "tag-id", + "ext": { + "bidder": { + "publisherId": "publisher-id", + "tagid": "tag-id", + "publisherDomain": "http://domain.com" + } + } + } + ], + "site": { + "id": "publisher-id", + "name": "publisher-id", + "domain": "http://domain.com", + "page": "http://page-domain.com", + "ref": "http://page-domain.com", + "publisher": { + "id": "publisher-id" + } + }, + "device": { + "ua": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.62 Mobile Safari/537.36", + "h": 300, + "w": 300 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "123", + "seatbid": [ + { + "bid": [ + { + "id": "request-id", + "impid": "impression-id", + "price": 2.1, + "adid": "1", + "adm": "<hrml></html>", + "adomain": [ + "adomain.com" + ], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "exp": 60, + "lurl": "https://lurl.domain.com/" + } + ], + "seat": "1111" + } + ], + "bidid": "123", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "request-id", + "impid": "impression-id", + "price": 2.1, + "adid": "1", + "adm": "<hrml></html>", + "adomain": [ + "adomain.com" + ], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "exp": 60, + "lurl": "https://lurl.domain.com/" + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/taboola/taboolatest/supplemental/bidderServerError.json b/adapters/taboola/taboolatest/supplemental/bidderServerError.json new file mode 100644 index 00000000000..1e27aed7f23 --- /dev/null +++ b/adapters/taboola/taboolatest/supplemental/bidderServerError.json @@ -0,0 +1,133 @@ +{ + "mockBidRequest": { + "id": "request-id", + "imp": [ + { + "id": "impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + }, + { + "w": 160, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "publisher-id", + "tagid": "tag-id" + } + } + } + ], + "site": { + "domain": "http://domain.com", + "page": "http://page-domain.com", + "ref": "http://page-domain.com" + }, + "device": { + "ua": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.62 Mobile Safari/537.36", + "h": 300, + "w": 300 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.com/publisher-id", + "body": { + "id": "request-id", + "imp": [ + { + "id": "impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + }, + { + "w": 160, + "h": 600 + } + ] + }, + "tagid" : "tag-id", + "ext": { + "bidder": { + "publisherId": "publisher-id", + "tagid": "tag-id" + } + } + } + ], + "site": { + "id": "publisher-id", + "name": "publisher-id", + "domain": "http://domain.com", + "page": "http://page-domain.com", + "ref": "http://page-domain.com", + "publisher": { + "id": "publisher-id" + } + }, + "device": { + "ua": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.62 Mobile Safari/537.36", + "h": 300, + "w": 300 + } + } + }, + "mockResponse": { + "status": 500, + "body": { + "id": "123", + "seatbid": [ + { + "bid": [ + { + "id": "request-id", + "impid": "impression-id", + "price": 2.1, + "adid": "1", + "adm": "<hrml></html>", + "adomain": [ + "adomain.com" + ], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "exp": 60, + "lurl": "https://lurl.domain.com/" + } + ], + "seat": "1111" + } + ], + "bidid": "123", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500. Run with request.debug = 1 for more info.", + "comparison": "literal" + } + ] +} diff --git a/adapters/taboola/taboolatest/supplemental/emptyReponseFromBidder.json b/adapters/taboola/taboolatest/supplemental/emptyReponseFromBidder.json new file mode 100644 index 00000000000..31241873ade --- /dev/null +++ b/adapters/taboola/taboolatest/supplemental/emptyReponseFromBidder.json @@ -0,0 +1,101 @@ +{ + "mockBidRequest": { + "id": "request-id", + "imp": [ + { + "id": "impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + }, + { + "w": 160, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "publisher-id", + "tagid": "tag-id" + } + } + } + ], + "site": { + "domain": "http://domain.com", + "page": "http://page-domain.com", + "ref": "http://page-domain.com" + }, + "device": { + "ua": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.62 Mobile Safari/537.36", + "h": 300, + "w": 300 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.com/publisher-id", + "body": { + "id": "request-id", + "imp": [ + { + "id": "impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + }, + { + "w": 160, + "h": 600 + } + ] + }, + "tagid": "tag-id", + "ext": { + "bidder": { + "publisherId": "publisher-id", + "tagid": "tag-id" + } + } + } + ], + "site": { + "id": "publisher-id", + "name": "publisher-id", + "domain": "http://domain.com", + "page": "http://page-domain.com", + "ref": "http://page-domain.com", + "publisher": { + "id": "publisher-id" + } + }, + "device": { + "ua": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.62 Mobile Safari/537.36", + "h": 300, + "w": 300 + } + } + }, + "mockResponse": { + "status": 204, + "body": { + } + } + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/taboola/taboolatest/supplemental/emptySiteInRequest.json b/adapters/taboola/taboolatest/supplemental/emptySiteInRequest.json new file mode 100644 index 00000000000..76d663bfc4f --- /dev/null +++ b/adapters/taboola/taboolatest/supplemental/emptySiteInRequest.json @@ -0,0 +1,150 @@ +{ + "mockBidRequest": { + "id": "request-id", + "imp": [ + { + "id": "impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + }, + { + "w": 160, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "publisher-id", + "tagid": "tag-id" + } + } + } + ], + "app": { + "domain": "http://domain.com" + }, + "device": { + "ua": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.62 Mobile Safari/537.36", + "h": 300, + "w": 300 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.com/publisher-id", + "body": { + "id": "request-id", + "imp": [ + { + "id": "impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + }, + { + "w": 160, + "h": 600 + } + ] + }, + "tagid" : "tag-id", + "ext": { + "bidder": { + "publisherId": "publisher-id", + "tagid": "tag-id" + } + } + } + ], + "app": { + "domain": "http://domain.com" + }, + "site": { + "id": "publisher-id", + "name": "publisher-id", + "publisher": { + "id": "publisher-id" + } + }, + "device": { + "ua": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.62 Mobile Safari/537.36", + "h": 300, + "w": 300 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "123", + "seatbid": [ + { + "bid": [ + { + "id": "request-id", + "impid": "impression-id", + "price": 2.1, + "adid": "1", + "adm": "<hrml></html>", + "adomain": [ + "adomain.com" + ], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "exp": 60, + "lurl": "https://lurl.domain.com/" + } + ], + "seat": "1111" + } + ], + "bidid": "123", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "request-id", + "impid": "impression-id", + "price": 2.1, + "adid": "1", + "adm": "<hrml></html>", + "adomain": [ + "adomain.com" + ], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "exp": 60, + "lurl": "https://lurl.domain.com/" + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/taboola/taboolatest/supplemental/incorrectResponseImpMapping.json b/adapters/taboola/taboolatest/supplemental/incorrectResponseImpMapping.json new file mode 100644 index 00000000000..e32ae471177 --- /dev/null +++ b/adapters/taboola/taboolatest/supplemental/incorrectResponseImpMapping.json @@ -0,0 +1,132 @@ +{ + "mockBidRequest": { + "id": "request-id", + "imp": [ + { + "id": "impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + }, + { + "w": 160, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "publisher-id", + "tagid": "tag-id" + } + } + } + ], + "site": { + "domain": "http://domain.com", + "page": "http://page-domain.com", + "ref": "http://page-domain.com" + }, + "device": { + "ua": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.62 Mobile Safari/537.36", + "h": 300, + "w": 300 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.com/publisher-id", + "body": { + "id": "request-id", + "imp": [ + { + "id": "impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + }, + { + "w": 160, + "h": 600 + } + ] + }, + "tagid" : "tag-id", + "ext": { + "bidder": { + "publisherId": "publisher-id", + "tagid": "tag-id" + } + } + } + ], + "site": { + "id": "publisher-id", + "name": "publisher-id", + "domain": "http://domain.com", + "page": "http://page-domain.com", + "ref": "http://page-domain.com", + "publisher": { + "id": "publisher-id" + } + }, + "device": { + "ua": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.62 Mobile Safari/537.36", + "h": 300, + "w": 300 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "123", + "seatbid": [ + { + "bid": [ + { + "id": "request-id", + "impid": "incorrect-impression-id", + "price": 2.1, + "adid": "1", + "adm": "<hrml></html>", + "adomain": [ + "adomain.com" + ], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "exp": 60, + "lurl": "https://lurl.domain.com/" + } + ], + "seat": "1111" + } + ], + "bidid": "123", + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Failed to find banner impression \"incorrect-impression-id\" ", + "comparison": "literal" + } + ] +} diff --git a/adapters/taboola/taboolatest/supplemental/multiImpressionsRequest.json b/adapters/taboola/taboolatest/supplemental/multiImpressionsRequest.json new file mode 100644 index 00000000000..03c3532ea09 --- /dev/null +++ b/adapters/taboola/taboolatest/supplemental/multiImpressionsRequest.json @@ -0,0 +1,206 @@ +{ + "mockBidRequest": { + "id": "request-id", + "imp": [ + { + "id": "impression-id-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "publisher-id", + "tagid": "tag-id-1" + } + } + }, + { + "id": "impression-id-2", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "publisher-id", + "tagid": "tag-id-2" + } + } + } + ], + "site": { + "domain": "http://domain.com", + "page": "http://page-domain.com", + "ref": "http://page-domain.com" + }, + "device": { + "ua": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.62 Mobile Safari/537.36", + "h": 300, + "w": 300 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.com/publisher-id", + "body": { + "id": "request-id", + "imp": [ + { + "id": "impression-id-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "tagid" : "tag-id-1", + "ext": { + "bidder": { + "publisherId": "publisher-id", + "tagid": "tag-id-1" + } + } + }, + { + "id": "impression-id-2", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "tagid" : "tag-id-2", + "ext": { + "bidder": { + "publisherId": "publisher-id", + "tagid": "tag-id-2" + } + } + } + ], + "site": { + "id": "publisher-id", + "name": "publisher-id", + "domain": "http://domain.com", + "page": "http://page-domain.com", + "ref": "http://page-domain.com", + "publisher": { + "id": "publisher-id" + } + }, + "device": { + "ua": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.62 Mobile Safari/537.36", + "h": 300, + "w": 300 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "123", + "seatbid": [ + { + "bid": [ + { + "id": "request-id", + "impid": "impression-id-1", + "price": 2.1, + "adid": "1", + "adm": "<hrml></html>", + "adomain": [ + "adomain.com" + ], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "exp": 60, + "lurl": "https://lurl.domain.com/" + }, + { + "id": "request-id", + "impid": "impression-id-2", + "price": 1.2, + "adid": "1", + "adm": "<hrml></html>", + "adomain": [ + "adomain.com" + ], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "exp": 60, + "lurl": "https://lurl2.domain.com/" + } + ], + "seat": "1111" + } + ], + "bidid": "123", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "request-id", + "impid": "impression-id-1", + "price": 2.1, + "adid": "1", + "adm": "<hrml></html>", + "adomain": [ + "adomain.com" + ], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "exp": 60, + "lurl": "https://lurl.domain.com/" + }, + "type": "banner" + }, + { + "bid": { + "id": "request-id", + "impid": "impression-id-2", + "price": 1.2, + "adid": "1", + "adm": "<hrml></html>", + "adomain": [ + "adomain.com" + ], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "exp": 60, + "lurl": "https://lurl2.domain.com/" + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/taboola/taboolatest/supplemental/noValidImpression.json b/adapters/taboola/taboolatest/supplemental/noValidImpression.json new file mode 100644 index 00000000000..2ecf5594817 --- /dev/null +++ b/adapters/taboola/taboolatest/supplemental/noValidImpression.json @@ -0,0 +1,47 @@ +{ + "mockBidRequest": { + "id": "request-id", + "imp": [ + { + "id": "impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + }, + { + "w": 160, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "publisherId": 1 + } + } + } + ], + "site": { + "domain": "http://domain.com", + "page": "http://page-domain.com", + "ref": "http://page-domain.com" + }, + "device": { + "ua": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.62 Mobile Safari/537.36", + "h": 300, + "w": 300 + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "json: cannot unmarshal number into Go struct field ImpExtTaboola.publisherId of type string", + "comparison": "literal" + } + ] +} diff --git a/adapters/taboola/taboolatest/supplemental/optionalParamsProvided.json b/adapters/taboola/taboolatest/supplemental/optionalParamsProvided.json new file mode 100644 index 00000000000..17103882cea --- /dev/null +++ b/adapters/taboola/taboolatest/supplemental/optionalParamsProvided.json @@ -0,0 +1,112 @@ +{ + "mockBidRequest": { + "id": "request-id", + "imp": [ + { + "id": "impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + }, + { + "w": 160, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "publisher-id", + "tagid": "tag-id", + "bcat": ["excluded-category"], + "badv": ["excluded-advertiser"], + "bidfloor": 1.2, + "publisherDomain": "http://domain.com" + } + } + } + ], + "site": { + "domain": "http://domain.com", + "page": "http://page-domain.com", + "ref": "http://page-domain.com" + }, + "device": { + "ua": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.62 Mobile Safari/537.36", + "h": 300, + "w": 300 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.com/publisher-id", + "body": { + "id": "request-id", + "imp": [ + { + "id": "impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + }, + { + "w": 160, + "h": 600 + } + ] + }, + "tagid": "tag-id", + "bidfloor": 1.2, + "ext": { + "bidder": { + "publisherId": "publisher-id", + "tagid": "tag-id", + "bcat": ["excluded-category"], + "badv": ["excluded-advertiser"], + "bidfloor": 1.2, + "publisherDomain": "http://domain.com" + } + } + } + ], + "site": { + "id": "publisher-id", + "name": "publisher-id", + "domain": "http://domain.com", + "page": "http://page-domain.com", + "ref": "http://page-domain.com", + "publisher": { + "id": "publisher-id" + } + }, + "device": { + "ua": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.62 Mobile Safari/537.36", + "h": 300, + "w": 300 + }, + "bcat": ["excluded-category"], + "badv": ["excluded-advertiser"] + } + }, + "mockResponse": { + "status": 204, + "body": { + } + } + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/taboola/taboolatest/supplemental/unexpectedStatusCode.json b/adapters/taboola/taboolatest/supplemental/unexpectedStatusCode.json new file mode 100644 index 00000000000..3e4cd70f22f --- /dev/null +++ b/adapters/taboola/taboolatest/supplemental/unexpectedStatusCode.json @@ -0,0 +1,133 @@ +{ + "mockBidRequest": { + "id": "request-id", + "imp": [ + { + "id": "impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + }, + { + "w": 160, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "publisher-id", + "tagid": "tag-id" + } + } + } + ], + "site": { + "domain": "http://domain.com", + "page": "http://page-domain.com", + "ref": "http://page-domain.com" + }, + "device": { + "ua": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.62 Mobile Safari/537.36", + "h": 300, + "w": 300 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.com/publisher-id", + "body": { + "id": "request-id", + "imp": [ + { + "id": "impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + }, + { + "w": 160, + "h": 600 + } + ] + }, + "tagid" : "tag-id", + "ext": { + "bidder": { + "publisherId": "publisher-id", + "tagid": "tag-id" + } + } + } + ], + "site": { + "id": "publisher-id", + "name": "publisher-id", + "domain": "http://domain.com", + "page": "http://page-domain.com", + "ref": "http://page-domain.com", + "publisher": { + "id": "publisher-id" + } + }, + "device": { + "ua": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.62 Mobile Safari/537.36", + "h": 300, + "w": 300 + } + } + }, + "mockResponse": { + "status": 302, + "body": { + "id": "123", + "seatbid": [ + { + "bid": [ + { + "id": "request-id", + "impid": "impression-id", + "price": 2.1, + "adid": "1", + "adm": "<hrml></html>", + "adomain": [ + "adomain.com" + ], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "exp": 60, + "lurl": "https://lurl.domain.com/" + } + ], + "seat": "1111" + } + ], + "bidid": "123", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 302. Run with request.debug = 1 for more info.", + "comparison": "literal" + } + ] +} diff --git a/adapters/tappx/tappx.go b/adapters/tappx/tappx.go index 0f80e5ab050..06059267869 100644 --- a/adapters/tappx/tappx.go +++ b/adapters/tappx/tappx.go @@ -10,7 +10,7 @@ import ( "text/template" "time" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -18,7 +18,7 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" ) -const TAPPX_BIDDER_VERSION = "1.4" +const TAPPX_BIDDER_VERSION = "1.5" const TYPE_CNN = "prebid" type TappxAdapter struct { @@ -37,7 +37,7 @@ type Ext struct { } // 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) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (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) @@ -132,7 +132,7 @@ func (a *TappxAdapter) buildEndpointURL(params *openrtb_ext.ExtImpTappx, test in } tappxHost := "tappx.com" - isNewEndpoint, err := regexp.Match(`^(zz|vz)[0-9]{3,}([a-z]{2}|test)$`, []byte(params.Endpoint)) + isNewEndpoint, err := regexp.Match(`^(zz|vz)[0-9]{3,}([a-z]{2,3}|test)$`, []byte(params.Endpoint)) if isNewEndpoint { tappxHost = params.Endpoint + ".pub." + tappxHost + "/rtb/" } else { diff --git a/adapters/tappx/tappx_test.go b/adapters/tappx/tappx_test.go index 796968972e3..c1b711426fb 100644 --- a/adapters/tappx/tappx_test.go +++ b/adapters/tappx/tappx_test.go @@ -12,7 +12,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderTappx, config.Adapter{ - Endpoint: "http://{{.Host}}"}) + Endpoint: "http://{{.Host}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -23,14 +23,14 @@ func TestJsonSamples(t *testing.T) { func TestEndpointTemplateMalformed(t *testing.T) { _, buildErr := Builder(openrtb_ext.BidderTappx, config.Adapter{ - Endpoint: "{{Malformed}}"}) + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Error(t, buildErr) } func TestTsValue(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderTappx, config.Adapter{ - Endpoint: "http://{{.Host}}"}) + Endpoint: "http://{{.Host}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -46,7 +46,7 @@ func TestTsValue(t *testing.T) { url, err := bidderTappx.buildEndpointURL(&tappxExt, test) - match, err := regexp.MatchString(`http://ssp\.api\.tappx\.com/rtb/v2/DUMMYENDPOINT\?tappxkey=dummy-tappx-key&ts=[0-9]{13}&type_cnn=prebid&v=1\.4`, url) + match, err := regexp.MatchString(`http://ssp\.api\.tappx\.com/rtb/v2/DUMMYENDPOINT\?tappxkey=dummy-tappx-key&ts=[0-9]{13}&type_cnn=prebid&v=1\.5`, 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 index 15d19f94ef0..ef8e5e394a9 100644 --- a/adapters/tappx/tappxtest/exemplary/single-banner-impression-extra.json +++ b/adapters/tappx/tappxtest/exemplary/single-banner-impression-extra.json @@ -35,7 +35,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://ssp.api.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.4", + "uri": "http://ssp.api.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.5", "body": { "id": "0000000000001", "test": 1, 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 8a78493ee4c..5af06ad55a8 100644 --- a/adapters/tappx/tappxtest/exemplary/single-banner-impression-future-feature.json +++ b/adapters/tappx/tappxtest/exemplary/single-banner-impression-future-feature.json @@ -32,7 +32,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://zz123456ps.pub.tappx.com/rtb/?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.4", + "uri": "http://zz123456ps.pub.tappx.com/rtb/?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.5", "body": { "id": "0000000000001", "test": 1, diff --git a/adapters/tappx/tappxtest/exemplary/single-banner-impression.json b/adapters/tappx/tappxtest/exemplary/single-banner-impression.json index da27cd97977..dbd342b3c1e 100644 --- a/adapters/tappx/tappxtest/exemplary/single-banner-impression.json +++ b/adapters/tappx/tappxtest/exemplary/single-banner-impression.json @@ -32,7 +32,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://ssp.api.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.4", + "uri": "http://ssp.api.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.5", "body": { "id": "0000000000001", "test": 1, diff --git a/adapters/tappx/tappxtest/exemplary/single-banner-site.json b/adapters/tappx/tappxtest/exemplary/single-banner-site.json index 2a75af67870..dde889dd5bc 100644 --- a/adapters/tappx/tappxtest/exemplary/single-banner-site.json +++ b/adapters/tappx/tappxtest/exemplary/single-banner-site.json @@ -36,7 +36,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://ssp.api.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-site-9876&type_cnn=prebid&v=1.4", + "uri": "http://ssp.api.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-site-9876&type_cnn=prebid&v=1.5", "body": { "id": "0000000000001", "test": 1, diff --git a/adapters/tappx/tappxtest/exemplary/single-video-impression.json b/adapters/tappx/tappxtest/exemplary/single-video-impression.json index 407a056558b..a9507df56a7 100644 --- a/adapters/tappx/tappxtest/exemplary/single-video-impression.json +++ b/adapters/tappx/tappxtest/exemplary/single-video-impression.json @@ -34,7 +34,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://ssp.api.tappx.com/rtb/v2/VZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.4", + "uri": "http://ssp.api.tappx.com/rtb/v2/VZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.5", "body": { "id": "0000000000001", "test": 1, diff --git a/adapters/tappx/tappxtest/exemplary/single-video-site.json b/adapters/tappx/tappxtest/exemplary/single-video-site.json index 4dec5931216..5e2ba58b03e 100644 --- a/adapters/tappx/tappxtest/exemplary/single-video-site.json +++ b/adapters/tappx/tappxtest/exemplary/single-video-site.json @@ -38,7 +38,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://ssp.api.tappx.com/rtb/v2/VZ123456PS?tappxkey=pub-12345-site-9876&type_cnn=prebid&v=1.4", + "uri": "http://ssp.api.tappx.com/rtb/v2/VZ123456PS?tappxkey=pub-12345-site-9876&type_cnn=prebid&v=1.5", "body": { "id": "0000000000001", "test": 1, diff --git a/adapters/tappx/tappxtest/supplemental/204status.json b/adapters/tappx/tappxtest/supplemental/204status.json index a0141616c38..8e3bb4b5ed7 100644 --- a/adapters/tappx/tappxtest/supplemental/204status.json +++ b/adapters/tappx/tappxtest/supplemental/204status.json @@ -29,7 +29,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://ssp.api.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.4", + "uri": "http://ssp.api.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.5", "body": { "id": "0000000000001", "test": 1, diff --git a/adapters/tappx/tappxtest/supplemental/bidfloor.json b/adapters/tappx/tappxtest/supplemental/bidfloor.json index cc451d65c53..7c2c53063fd 100644 --- a/adapters/tappx/tappxtest/supplemental/bidfloor.json +++ b/adapters/tappx/tappxtest/supplemental/bidfloor.json @@ -33,7 +33,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://ssp.api.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.4", + "uri": "http://ssp.api.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.5", "body": { "id": "0000000000001", "test": 1, diff --git a/adapters/tappx/tappxtest/supplemental/http-err-status.json b/adapters/tappx/tappxtest/supplemental/http-err-status.json index 1798868b37c..22d0afe3180 100644 --- a/adapters/tappx/tappxtest/supplemental/http-err-status.json +++ b/adapters/tappx/tappxtest/supplemental/http-err-status.json @@ -29,7 +29,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://ssp.api.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.4", + "uri": "http://ssp.api.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.5", "body": { "id": "0000000000001", "test": 1, diff --git a/adapters/tappx/tappxtest/supplemental/http-err-status2.json b/adapters/tappx/tappxtest/supplemental/http-err-status2.json index 2d64cfe0a2d..011f80a5ff8 100644 --- a/adapters/tappx/tappxtest/supplemental/http-err-status2.json +++ b/adapters/tappx/tappxtest/supplemental/http-err-status2.json @@ -29,7 +29,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://ssp.api.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.4", + "uri": "http://ssp.api.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.5", "body": { "id": "0000000000001", "test": 1, diff --git a/adapters/telaria/telaria.go b/adapters/telaria/telaria.go index d36312e53dc..dbbcbc5d2ab 100644 --- a/adapters/telaria/telaria.go +++ b/adapters/telaria/telaria.go @@ -6,7 +6,7 @@ import ( "net/http" "strconv" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -19,7 +19,7 @@ type TelariaAdapter struct { URI string } -// This will be part of Imp[i].Ext when this adapter calls out the Telaria Ad Server +// This will be part of imp[i].ext when this adapter calls out the Telaria Ad Server type ImpressionExtOut struct { OriginalTagID string `json:"originalTagid"` OriginalPublisherID string `json:"originalPublisherid"` @@ -186,7 +186,7 @@ func (a *TelariaAdapter) MakeRequests(requestIn *openrtb2.BidRequest, reqInfo *a var err error var imp = request.Imp[0] - // fetch adCode & seatCode from Imp[i].Ext + // fetch adCode & seatCode from imp[i].ext telariaImpExt, err = a.FetchTelariaExtImpParams(&imp) if err != nil { return nil, []error{err} @@ -194,7 +194,7 @@ func (a *TelariaAdapter) MakeRequests(requestIn *openrtb2.BidRequest, reqInfo *a seatCode = telariaImpExt.SeatCode - // move the original tagId and the original publisher.id into the Imp[i].Ext object + // move the original tagId and the original publisher.id into the imp[i].ext object imp.Ext, err = json.Marshal(&ImpressionExtOut{imp.TagID, originalPublisherID}) if err != nil { return nil, []error{err} @@ -291,7 +291,7 @@ func (a *TelariaAdapter) MakeBids(internalRequest *openrtb2.BidRequest, external } // Builder builds a new instance of the Telaria adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { endpoint := config.Endpoint if endpoint == "" { endpoint = Endpoint // Hardcoded default diff --git a/adapters/telaria/telaria_test.go b/adapters/telaria/telaria_test.go index be29ed40f03..f8008835ac3 100644 --- a/adapters/telaria/telaria_test.go +++ b/adapters/telaria/telaria_test.go @@ -12,7 +12,7 @@ import ( func TestEndpointFromConfig(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderTelaria, config.Adapter{ Endpoint: "providedurl.com", - }) + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -24,7 +24,7 @@ func TestEndpointFromConfig(t *testing.T) { } func TestJsonSamples(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderTelaria, config.Adapter{}) + bidder, buildErr := Builder(openrtb_ext.BidderTelaria, config.Adapter{}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/trafficgate/trafficgate.go b/adapters/trafficgate/trafficgate.go index 30ec08b0bcc..3adfea48047 100644 --- a/adapters/trafficgate/trafficgate.go +++ b/adapters/trafficgate/trafficgate.go @@ -6,7 +6,7 @@ import ( "net/http" "text/template" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -168,7 +168,7 @@ func getMediaTypeForImp(bidType string) openrtb_ext.BidType { } // Builder builds a new instance of the TrafficGate adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (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) diff --git a/adapters/trafficgate/trafficgate_test.go b/adapters/trafficgate/trafficgate_test.go index 37cea770d1c..326c50523fe 100644 --- a/adapters/trafficgate/trafficgate_test.go +++ b/adapters/trafficgate/trafficgate_test.go @@ -11,7 +11,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderTrafficGate, config.Adapter{ - Endpoint: "http://{{.Host}}.bc-plugin.com/?c=o&m=rtb"}) + Endpoint: "http://{{.Host}}.bc-plugin.com/?c=o&m=rtb"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -22,7 +22,7 @@ func TestJsonSamples(t *testing.T) { func TestEndpointTemplateMalformed(t *testing.T) { _, buildErr := Builder(openrtb_ext.BidderTrafficGate, config.Adapter{ - Endpoint: "{{Malformed}}"}) + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Error(t, buildErr) } diff --git a/adapters/triplelift/triplelift.go b/adapters/triplelift/triplelift.go index 3cd651cee5c..d02f45eedfc 100644 --- a/adapters/triplelift/triplelift.go +++ b/adapters/triplelift/triplelift.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -26,7 +26,7 @@ type TripleliftRespExt struct { func getBidType(ext TripleliftRespExt) openrtb_ext.BidType { t := ext.Triplelift.Format - if t == 11 { + if t == 11 || t == 12 || t == 17 { return openrtb_ext.BidTypeVideo } return openrtb_ext.BidTypeBanner @@ -143,7 +143,7 @@ func (a *TripleliftAdapter) MakeBids(internalRequest *openrtb2.BidRequest, exter } // Builder builds a new instance of the Triplelift adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &TripleliftAdapter{ endpoint: config.Endpoint, } diff --git a/adapters/triplelift/triplelift_test.go b/adapters/triplelift/triplelift_test.go index 5107d7cc997..add71b05788 100644 --- a/adapters/triplelift/triplelift_test.go +++ b/adapters/triplelift/triplelift_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderTriplelift, config.Adapter{ - Endpoint: "http://tlx.3lift.net/s2s/auction?sra=1&supplier_id=20"}) + Endpoint: "http://tlx.3lift.net/s2s/auction?sra=1&supplier_id=20"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/triplelift/triplelifttest/supplemental/video-format-11.json b/adapters/triplelift/triplelifttest/supplemental/video-format-11.json new file mode 100644 index 00000000000..f7a88ec5adc --- /dev/null +++ b/adapters/triplelift/triplelifttest/supplemental/video-format-11.json @@ -0,0 +1,118 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "minduration": 15, + "maxduration": 30, + "protocols": [2, 3, 5, 6, 7, 8], + "w": 940, + "h": 560 + }, + "ext": { + "bidder": { + "inventoryCode": "aaw", + "floor": 0.10 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://tlx.3lift.net/s2s/auction?sra=1&supplier_id=20", + "body": { + "id": "test-request-id", + "imp": [ + { + "tagid": "aaw", + "bidfloor": 0.10, + "id": "test-imp-id", + "video": { + "mimes": [ "video/mp4"], + "minduration": 15, + "maxduration": 30, + "protocols": [2, 3, 5, 6, 7, 8], + "w": 940, + "h": 560 + }, + "ext": { + "bidder": { + "inventoryCode": "aaw", + "floor": 0.10 + } + } + } + ] + } + }, + "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": ["foo.com"], + "iurl": "http://tlx.3lift.net/s2s/auction?sra=1&supplier_id=20", + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300, + "cat": ["IAB9-1"], + "ext": { + "triplelift_pb": { + "format": 11 + } + } + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": ["foo.com"], + "iurl": "http://tlx.3lift.net/s2s/auction?sra=1&supplier_id=20", + "cid": "958", + "crid": "29681110", + "w": 300, + "h": 250, + "cat": ["IAB9-1"], + "ext": { + "triplelift_pb": { + "format": 11 + } + } + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/triplelift/triplelifttest/supplemental/video-format-12.json b/adapters/triplelift/triplelifttest/supplemental/video-format-12.json new file mode 100644 index 00000000000..7d5987afc10 --- /dev/null +++ b/adapters/triplelift/triplelifttest/supplemental/video-format-12.json @@ -0,0 +1,118 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "minduration": 15, + "maxduration": 30, + "protocols": [2, 3, 5, 6, 7, 8], + "w": 940, + "h": 560 + }, + "ext": { + "bidder": { + "inventoryCode": "aaw", + "floor": 0.10 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://tlx.3lift.net/s2s/auction?sra=1&supplier_id=20", + "body": { + "id": "test-request-id", + "imp": [ + { + "tagid": "aaw", + "bidfloor": 0.10, + "id": "test-imp-id", + "video": { + "mimes": [ "video/mp4"], + "minduration": 15, + "maxduration": 30, + "protocols": [2, 3, 5, 6, 7, 8], + "w": 940, + "h": 560 + }, + "ext": { + "bidder": { + "inventoryCode": "aaw", + "floor": 0.10 + } + } + } + ] + } + }, + "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": ["foo.com"], + "iurl": "http://tlx.3lift.net/s2s/auction?sra=1&supplier_id=20", + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300, + "cat": ["IAB9-1"], + "ext": { + "triplelift_pb": { + "format": 12 + } + } + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": ["foo.com"], + "iurl": "http://tlx.3lift.net/s2s/auction?sra=1&supplier_id=20", + "cid": "958", + "crid": "29681110", + "w": 300, + "h": 250, + "cat": ["IAB9-1"], + "ext": { + "triplelift_pb": { + "format": 12 + } + } + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/triplelift/triplelifttest/supplemental/video-format-17.json b/adapters/triplelift/triplelifttest/supplemental/video-format-17.json new file mode 100644 index 00000000000..1fb44507e6c --- /dev/null +++ b/adapters/triplelift/triplelifttest/supplemental/video-format-17.json @@ -0,0 +1,118 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "minduration": 15, + "maxduration": 30, + "protocols": [2, 3, 5, 6, 7, 8], + "w": 940, + "h": 560 + }, + "ext": { + "bidder": { + "inventoryCode": "aaw", + "floor": 0.10 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://tlx.3lift.net/s2s/auction?sra=1&supplier_id=20", + "body": { + "id": "test-request-id", + "imp": [ + { + "tagid": "aaw", + "bidfloor": 0.10, + "id": "test-imp-id", + "video": { + "mimes": [ "video/mp4"], + "minduration": 15, + "maxduration": 30, + "protocols": [2, 3, 5, 6, 7, 8], + "w": 940, + "h": 560 + }, + "ext": { + "bidder": { + "inventoryCode": "aaw", + "floor": 0.10 + } + } + } + ] + } + }, + "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": ["foo.com"], + "iurl": "http://tlx.3lift.net/s2s/auction?sra=1&supplier_id=20", + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300, + "cat": ["IAB9-1"], + "ext": { + "triplelift_pb": { + "format": 17 + } + } + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": ["foo.com"], + "iurl": "http://tlx.3lift.net/s2s/auction?sra=1&supplier_id=20", + "cid": "958", + "crid": "29681110", + "w": 300, + "h": 250, + "cat": ["IAB9-1"], + "ext": { + "triplelift_pb": { + "format": 17 + } + } + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/triplelift_native/triplelift_native.go b/adapters/triplelift_native/triplelift_native.go index d412def437f..3f74614afa1 100644 --- a/adapters/triplelift_native/triplelift_native.go +++ b/adapters/triplelift_native/triplelift_native.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -176,7 +176,7 @@ func (a *TripleliftNativeAdapter) MakeBids(internalRequest *openrtb2.BidRequest, } // Builder builds a new instance of the TripleliftNative adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { extraInfo, err := getExtraInfo(config.ExtraAdapterInfo) if err != nil { return nil, err diff --git a/adapters/triplelift_native/triplelift_native_test.go b/adapters/triplelift_native/triplelift_native_test.go index fef24949e79..18e157a41cd 100644 --- a/adapters/triplelift_native/triplelift_native_test.go +++ b/adapters/triplelift_native/triplelift_native_test.go @@ -13,7 +13,7 @@ func TestBadConfig(t *testing.T) { _, buildErr := Builder(openrtb_ext.BidderTripleliftNative, config.Adapter{ Endpoint: `http://tlx.3lift.net/s2sn/auction?supplier_id=20`, ExtraAdapterInfo: `{foo:2}`, - }) + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Error(t, buildErr) } @@ -22,7 +22,7 @@ func TestEmptyConfig(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderTripleliftNative, config.Adapter{ Endpoint: `http://tlx.3lift.net/s2sn/auction?supplier_id=20`, ExtraAdapterInfo: ``, - }) + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) bidderTripleliftNative := bidder.(*TripleliftNativeAdapter) @@ -35,7 +35,7 @@ func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderTripleliftNative, config.Adapter{ Endpoint: `http://tlx.3lift.net/s2sn/auction?supplier_id=20`, ExtraAdapterInfo: `{"publisher_whitelist":["foo","bar","baz"]}`, - }) + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/ucfunnel/params_test.go b/adapters/ucfunnel/params_test.go index 4faec8739da..b721925e72a 100644 --- a/adapters/ucfunnel/params_test.go +++ b/adapters/ucfunnel/params_test.go @@ -2,13 +2,14 @@ package ucfunnel import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/openrtb_ext" ) // This file actually intends to test static/bidder-params/ucfunnel.json // -// These also validate the format of the external API: request.imp[i].ext.ucfunnel +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.ucfunnel // TestValidParams makes sure that the ucfunnel schema accepts all imp.ext fields which we intend to support. func TestValidParams(t *testing.T) { diff --git a/adapters/ucfunnel/ucfunnel.go b/adapters/ucfunnel/ucfunnel.go index 1d3efc04451..05245cac96a 100644 --- a/adapters/ucfunnel/ucfunnel.go +++ b/adapters/ucfunnel/ucfunnel.go @@ -6,7 +6,7 @@ import ( "net/http" "net/url" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -18,7 +18,7 @@ type UcfunnelAdapter struct { } // Builder builds a new instance of the Ucfunnel adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &UcfunnelAdapter{ URI: config.Endpoint, } diff --git a/adapters/ucfunnel/ucfunnel_test.go b/adapters/ucfunnel/ucfunnel_test.go index 95ac5985f56..194aa715d80 100644 --- a/adapters/ucfunnel/ucfunnel_test.go +++ b/adapters/ucfunnel/ucfunnel_test.go @@ -5,7 +5,7 @@ import ( "fmt" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" @@ -47,7 +47,7 @@ func TestMakeRequests(t *testing.T) { internalRequest03.Imp[4].Ext = []byte(`{"bidder": {"adunitid": "aa","partnerid": ""}}`) bidder, buildErr := Builder(openrtb_ext.BidderUcfunnel, config.Adapter{ - Endpoint: "http://localhost/bid"}) + Endpoint: "http://localhost/bid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -119,7 +119,7 @@ func TestMakeBids(t *testing.T) { RequestData02 := adapters.RequestData{Method: "POST", Body: []byte(`{"imp":[{"id":"1234","banne"1235","video":{}},{"id":"1236","audio":{}},{"id":"1237","native":{}}]}`)} bidder, buildErr := Builder(openrtb_ext.BidderUcfunnel, config.Adapter{ - Endpoint: "http://localhost/bid"}) + Endpoint: "http://localhost/bid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/unicorn/params_test.go b/adapters/unicorn/params_test.go index 4a534208e84..9313183fbfa 100644 --- a/adapters/unicorn/params_test.go +++ b/adapters/unicorn/params_test.go @@ -35,24 +35,45 @@ func TestInvalidParams(t *testing.T) { var validParams = []string{ `{ - "accountId": 199578, - "publisherId": 123456, + "accountId": 12345, + "publisherId": "123456", + "mediaId": "test_media", + "placementId": "test_placement" + }`, + `{ + "accountId": 12345, + "publisherId": "123456-pub", + "mediaId": "test_media", + "placementId": "test_placement" + }`, + `{ + "accountId": 12345, + "publisherId": "12341234123412341234", "mediaId": "test_media", "placementId": "test_placement" }`, `{ - "accountId": 199578, + "accountId": 12345, "mediaId": "test_media" + }`, + `{ + "accountId": 12345 }`, } var invalidParams = []string{ `{}`, `{ - "accountId": "199578", + "accountId": "12345", "publisherId": "123456", "mediaId": 12345, "placementId": 12345 + }`, + `{ + "accountId": 12345, + "publisherId": 12341234123412341234, + "mediaId": "test_media", + "placementId": "test_placement" }`, `{ "publisherId": 123456, diff --git a/adapters/unicorn/unicorn.go b/adapters/unicorn/unicorn.go index 8d1413f43dd..48c1e68ee98 100644 --- a/adapters/unicorn/unicorn.go +++ b/adapters/unicorn/unicorn.go @@ -2,11 +2,12 @@ package unicorn import ( "encoding/json" + "errors" "fmt" "net/http" "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -34,7 +35,7 @@ type unicornExt struct { } // Builder builds a new instance of the UNICORN adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &adapter{ endpoint: config.Endpoint, } @@ -69,6 +70,10 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte return nil, []error{err} } + if err := modifyApp(request); err != nil { + return nil, []error{err} + } + var modifiableSource openrtb2.Source if request.Source != nil { modifiableSource = *request.Source @@ -171,11 +176,42 @@ func setSourceExt() json.RawMessage { return json.RawMessage(`{"stype": "prebid_server_uncn", "bidder": "unicorn"}`) } +func modifyApp(request *openrtb2.BidRequest) error { + if request.App == nil { + return errors.New("request app is required") + } + + modifiableApp := *request.App + + mediaId, err := jsonparser.GetString(request.Imp[0].Ext, "bidder", "mediaId") + if err == nil { + modifiableApp.ID = mediaId + } + + publisherId, err := jsonparser.GetString(request.Imp[0].Ext, "bidder", "publisherId") + if err == nil { + var publisher openrtb2.Publisher + if modifiableApp.Publisher != nil { + publisher = *modifiableApp.Publisher + } else { + publisher = openrtb2.Publisher{} + } + + publisher.ID = publisherId + + modifiableApp.Publisher = &publisher + } + + request.App = &modifiableApp + return nil +} + func setExt(request *openrtb2.BidRequest) (json.RawMessage, error) { accountID, err := jsonparser.GetInt(request.Imp[0].Ext, "bidder", "accountId") if err != nil { - accountID = 0 + return nil, fmt.Errorf("accountId field is required") } + var decodedExt *unicornExt err = json.Unmarshal(request.Ext, &decodedExt) if err != nil { diff --git a/adapters/unicorn/unicorn_test.go b/adapters/unicorn/unicorn_test.go index 1a0d67d29f7..084be78498a 100644 --- a/adapters/unicorn/unicorn_test.go +++ b/adapters/unicorn/unicorn_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderUnicorn, config.Adapter{ - Endpoint: "https://ds.uncn.jp"}) + Endpoint: "https://ds.uncn.jp"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app-no-app-publisher.json b/adapters/unicorn/unicorntest/exemplary/banner-app-no-app-publisher.json new file mode 100644 index 00000000000..3ddb63d0cc2 --- /dev/null +++ b/adapters/unicorn/unicorntest/exemplary/banner-app-no-app-publisher.json @@ -0,0 +1,228 @@ +{ + "mockBidRequest": { + "at": 1, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "prebid": { + "storedrequest": { + "id": "test_unicorn" + } + }, + "bidder": { + "accountId": 12345, + "publisherId": "123456", + "mediaId": "test_media", + "placementId": "test_placement" + } + } + } + ], + "cur": ["JPY"], + "app": { + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + }, + "ext": { + "prebid": { + "targeting": {}, + "cache": { + "bids": {} + }, + "storedrequest": { + "id": "test" + } + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ds.uncn.jp", + "body": { + "app": { + "bundle": "net.ada.test", + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "id": "test_media", + "publisher": { + "id": "123456" + }, + "ver": "1.9.0" + }, + "at": 1, + "cur": ["JPY"], + "device": { + "connectiontype": 1, + "ext": { + "atts": 0 + }, + "h": 568, + "ifa": "00000000-0000-0000-0000-000000000000", + "make": "Apple", + "model": "Simulator", + "os": "iOS", + "osv": "14.4", + "pxratio": 2, + "w": 320 + }, + "ext": { + "prebid": { + "storedrequest": { + "id": "test" + } + }, + "accountId": 12345 + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "imp": [ + { + "banner": { + "api": [5], + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "accountId": 12345, + "publisherId": "123456", + "mediaId": "test_media", + "placementId": "test_placement" + } + }, + "tagid": "test_placement", + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1 + } + ], + "source": { + "ext": { + "bidder": "unicorn", + "stype": "prebid_server_uncn" + }, + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "user": { + "gender": "O" + } + } + }, + "mockResponse": { + "url": "https://ds.uncn.jp", + "status": 200, + "headers": { + "Content-Type": ["application/json"], + "Date": ["Thu, 04 Feb 2021 06:36:31 GMT"], + "Vary": ["Accept-Encoding"] + }, + "body": { + "bidid": "f1248280-24ec-4fbc-bdf5-6a866e7dcea4", + "cur": "JPY", + "ext": {}, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "seatbid": [ + { + "bid": [ + { + "adid": "uoNYbq1L_-123456", + "adm": "awesome domain", + "adomain": ["example.com"], + "attr": [], + "bundle": "example.com", + "cid": "7314", + "crid": "-123456", + "ext": {}, + "h": 250, + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "iurl": "https://example.com/banner.png", + "price": 73.283, + "w": 300 + } + ], + "group": 0, + "seat": "274" + } + ], + "units": 0 + } + } + } + ], + "expectedBidResponses": [ + { + "Currency": "JPY", + "bids": [ + { + "Bid": { + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "price": 73.283, + "adm": "awesome domain", + "adid": "uoNYbq1L_-123456", + "adomain": ["example.com"], + "bundle": "example.com", + "iurl": "https://example.com/banner.png", + "cid": "7314", + "crid": "-123456", + "w": 300, + "h": 250, + "ext": {} + }, + "type": "banner", + "BidType": "banner", + "BidVideo": null, + "DealPriority": 0 + } + ] + } + ] +} diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app-no-mediaid.json b/adapters/unicorn/unicorntest/exemplary/banner-app-no-mediaid.json new file mode 100644 index 00000000000..8e526449fc4 --- /dev/null +++ b/adapters/unicorn/unicorntest/exemplary/banner-app-no-mediaid.json @@ -0,0 +1,228 @@ +{ + "mockBidRequest": { + "at": 1, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "prebid": { + "storedrequest": { + "id": "test_unicorn" + } + }, + "bidder": { + "accountId": 12345, + "publisherId": "123456", + "placementId": "test_placement" + } + } + } + ], + "cur": ["JPY"], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + }, + "ext": { + "prebid": { + "targeting": {}, + "cache": { + "bids": {} + }, + "storedrequest": { + "id": "test" + } + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ds.uncn.jp", + "body": { + "app": { + "bundle": "net.ada.test", + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "publisher": { + "id": "123456" + }, + "ver": "1.9.0" + }, + "at": 1, + "cur": ["JPY"], + "device": { + "connectiontype": 1, + "ext": { + "atts": 0 + }, + "h": 568, + "ifa": "00000000-0000-0000-0000-000000000000", + "make": "Apple", + "model": "Simulator", + "os": "iOS", + "osv": "14.4", + "pxratio": 2, + "w": 320 + }, + "ext": { + "prebid": { + "storedrequest": { + "id": "test" + } + }, + "accountId": 12345 + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "imp": [ + { + "banner": { + "api": [5], + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "accountId": 12345, + "publisherId": "123456", + "placementId": "test_placement" + } + }, + "tagid": "test_placement", + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1 + } + ], + "source": { + "ext": { + "bidder": "unicorn", + "stype": "prebid_server_uncn" + }, + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "user": { + "gender": "O" + } + } + }, + "mockResponse": { + "url": "https://ds.uncn.jp", + "status": 200, + "headers": { + "Content-Type": ["application/json"], + "Date": ["Thu, 04 Feb 2021 06:36:31 GMT"], + "Vary": ["Accept-Encoding"] + }, + "body": { + "bidid": "f1248280-24ec-4fbc-bdf5-6a866e7dcea4", + "cur": "JPY", + "ext": {}, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "seatbid": [ + { + "bid": [ + { + "adid": "uoNYbq1L_-123456", + "adm": "awesome domain", + "adomain": ["example.com"], + "attr": [], + "bundle": "example.com", + "cid": "7314", + "crid": "-123456", + "ext": {}, + "h": 250, + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "iurl": "https://example.com/banner.png", + "price": 73.283, + "w": 300 + } + ], + "group": 0, + "seat": "274" + } + ], + "units": 0 + } + } + } + ], + "expectedBidResponses": [ + { + "Currency": "JPY", + "bids": [ + { + "Bid": { + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "price": 73.283, + "adm": "awesome domain", + "adid": "uoNYbq1L_-123456", + "adomain": ["example.com"], + "bundle": "example.com", + "iurl": "https://example.com/banner.png", + "cid": "7314", + "crid": "-123456", + "w": 300, + "h": 250, + "ext": {} + }, + "type": "banner", + "BidType": "banner", + "BidVideo": null, + "DealPriority": 0 + } + ] + } + ] +} diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app-no-publisherid.json b/adapters/unicorn/unicorntest/exemplary/banner-app-no-publisherid.json new file mode 100644 index 00000000000..924c2e4026b --- /dev/null +++ b/adapters/unicorn/unicorntest/exemplary/banner-app-no-publisherid.json @@ -0,0 +1,229 @@ +{ + "mockBidRequest": { + "at": 1, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "prebid": { + "storedrequest": { + "id": "test_unicorn" + } + }, + "bidder": { + "accountId": 12345, + "mediaId": "test_media", + "placementId": "test_placement" + } + } + } + ], + "cur": ["JPY"], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + }, + "ext": { + "prebid": { + "targeting": {}, + "cache": { + "bids": {} + }, + "storedrequest": { + "id": "test" + } + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ds.uncn.jp", + "body": { + "app": { + "bundle": "net.ada.test", + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "id": "test_media", + "publisher": { + "id": "test" + }, + "ver": "1.9.0" + }, + "at": 1, + "cur": ["JPY"], + "device": { + "connectiontype": 1, + "ext": { + "atts": 0 + }, + "h": 568, + "ifa": "00000000-0000-0000-0000-000000000000", + "make": "Apple", + "model": "Simulator", + "os": "iOS", + "osv": "14.4", + "pxratio": 2, + "w": 320 + }, + "ext": { + "prebid": { + "storedrequest": { + "id": "test" + } + }, + "accountId": 12345 + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "imp": [ + { + "banner": { + "api": [5], + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "accountId": 12345, + "mediaId": "test_media", + "placementId": "test_placement" + } + }, + "tagid": "test_placement", + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1 + } + ], + "source": { + "ext": { + "bidder": "unicorn", + "stype": "prebid_server_uncn" + }, + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "user": { + "gender": "O" + } + } + }, + "mockResponse": { + "url": "https://ds.uncn.jp", + "status": 200, + "headers": { + "Content-Type": ["application/json"], + "Date": ["Thu, 04 Feb 2021 06:36:31 GMT"], + "Vary": ["Accept-Encoding"] + }, + "body": { + "bidid": "f1248280-24ec-4fbc-bdf5-6a866e7dcea4", + "cur": "JPY", + "ext": {}, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "seatbid": [ + { + "bid": [ + { + "adid": "uoNYbq1L_-123456", + "adm": "awesome domain", + "adomain": ["example.com"], + "attr": [], + "bundle": "example.com", + "cid": "7314", + "crid": "-123456", + "ext": {}, + "h": 250, + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "iurl": "https://example.com/banner.png", + "price": 73.283, + "w": 300 + } + ], + "group": 0, + "seat": "274" + } + ], + "units": 0 + } + } + } + ], + "expectedBidResponses": [ + { + "Currency": "JPY", + "bids": [ + { + "Bid": { + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "price": 73.283, + "adm": "awesome domain", + "adid": "uoNYbq1L_-123456", + "adomain": ["example.com"], + "bundle": "example.com", + "iurl": "https://example.com/banner.png", + "cid": "7314", + "crid": "-123456", + "w": 300, + "h": 250, + "ext": {} + }, + "type": "banner", + "BidType": "banner", + "BidVideo": null, + "DealPriority": 0 + } + ] + } + ] +} diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app-no-source.json b/adapters/unicorn/unicorntest/exemplary/banner-app-no-source.json index c37c2095d48..938c9aa1f13 100644 --- a/adapters/unicorn/unicorntest/exemplary/banner-app-no-source.json +++ b/adapters/unicorn/unicorntest/exemplary/banner-app-no-source.json @@ -21,8 +21,8 @@ } }, "bidder": { - "accountId": 199578, - "publisherId": 123456, + "accountId": 12345, + "publisherId": "123456", "mediaId": "test_media", "placementId": "test_placement" } @@ -88,8 +88,9 @@ "version": "1.9.0" } }, + "id": "test_media", "publisher": { - "id": "test" + "id": "123456" }, "ver": "1.9.0" }, @@ -111,13 +112,11 @@ }, "ext": { "prebid": { - "bidder": null, - "is_rewarded_inventory": 0, "storedrequest": { "id": "test" } }, - "accountId": 199578 + "accountId": 12345 }, "id": "CFD24FB1-916F-467D-8825-34892B315DB7", "imp": [ @@ -133,8 +132,8 @@ }, "ext": { "bidder": { - "accountId": 199578, - "publisherId": 123456, + "accountId": 12345, + "publisherId": "123456", "mediaId": "test_media", "placementId": "test_placement" } diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app-with-ip.json b/adapters/unicorn/unicorntest/exemplary/banner-app-with-ip.json index 2d38a990db3..74539fce60d 100644 --- a/adapters/unicorn/unicorntest/exemplary/banner-app-with-ip.json +++ b/adapters/unicorn/unicorntest/exemplary/banner-app-with-ip.json @@ -21,8 +21,8 @@ } }, "bidder": { - "accountId": 199578, - "publisherId": 123456, + "accountId": 12345, + "publisherId": "123456", "mediaId": "test_media", "placementId": "test_placement" } @@ -93,8 +93,9 @@ "version": "1.9.0" } }, + "id": "test_media", "publisher": { - "id": "test" + "id": "123456" }, "ver": "1.9.0" }, @@ -118,13 +119,11 @@ }, "ext": { "prebid": { - "bidder": null, - "is_rewarded_inventory": 0, "storedrequest": { "id": "test" } }, - "accountId": 199578 + "accountId": 12345 }, "id": "CFD24FB1-916F-467D-8825-34892B315DB7", "imp": [ @@ -140,8 +139,8 @@ }, "ext": { "bidder": { - "accountId": 199578, - "publisherId": 123456, + "accountId": 12345, + "publisherId": "123456", "mediaId": "test_media", "placementId": "test_placement" } diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app-with-ipv6.json b/adapters/unicorn/unicorntest/exemplary/banner-app-with-ipv6.json index 595741a0aa1..8c3292447b7 100644 --- a/adapters/unicorn/unicorntest/exemplary/banner-app-with-ipv6.json +++ b/adapters/unicorn/unicorntest/exemplary/banner-app-with-ipv6.json @@ -21,8 +21,8 @@ } }, "bidder": { - "accountId": 199578, - "publisherId": 123456, + "accountId": 12345, + "publisherId": "123456", "mediaId": "test_media", "placementId": "test_placement" } @@ -93,8 +93,9 @@ "version": "1.9.0" } }, + "id": "test_media", "publisher": { - "id": "test" + "id": "123456" }, "ver": "1.9.0" }, @@ -118,13 +119,11 @@ }, "ext": { "prebid": { - "bidder": null, - "is_rewarded_inventory": 0, "storedrequest": { "id": "test" } }, - "accountId": 199578 + "accountId": 12345 }, "id": "CFD24FB1-916F-467D-8825-34892B315DB7", "imp": [ @@ -140,8 +139,8 @@ }, "ext": { "bidder": { - "accountId": 199578, - "publisherId": 123456, + "accountId": 12345, + "publisherId": "123456", "mediaId": "test_media", "placementId": "test_placement" } diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app-without-ext.json b/adapters/unicorn/unicorntest/exemplary/banner-app-without-ext.json index 8b5423a5556..bc8ec8a6d6e 100644 --- a/adapters/unicorn/unicorntest/exemplary/banner-app-without-ext.json +++ b/adapters/unicorn/unicorntest/exemplary/banner-app-without-ext.json @@ -21,8 +21,8 @@ } }, "bidder": { - "accountId": 199578, - "publisherId": 123456, + "accountId": 12345, + "publisherId": "123456", "mediaId": "test_media", "placementId": "test_placement" } @@ -79,8 +79,9 @@ "version": "1.9.0" } }, + "id": "test_media", "publisher": { - "id": "test" + "id": "123456" }, "ver": "1.9.0" }, @@ -100,7 +101,7 @@ "w": 320 }, "ext": { - "accountId": 199578 + "accountId": 12345 }, "id": "CFD24FB1-916F-467D-8825-34892B315DB7", "imp": [ @@ -116,8 +117,8 @@ }, "ext": { "bidder": { - "accountId": 199578, - "publisherId": 123456, + "accountId": 12345, + "publisherId": "123456", "mediaId": "test_media", "placementId": "test_placement" } diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app-without-placementid.json b/adapters/unicorn/unicorntest/exemplary/banner-app-without-placementid.json index c05d3b6f536..795afe20084 100644 --- a/adapters/unicorn/unicorntest/exemplary/banner-app-without-placementid.json +++ b/adapters/unicorn/unicorntest/exemplary/banner-app-without-placementid.json @@ -21,8 +21,8 @@ } }, "bidder": { - "accountId": 199578, - "publisherId": 123456, + "accountId": 12345, + "publisherId": "12341234123412341234", "mediaId": "test_media" } } @@ -90,8 +90,9 @@ "version": "1.9.0" } }, + "id": "test_media", "publisher": { - "id": "test" + "id": "12341234123412341234" }, "ver": "1.9.0" }, @@ -113,13 +114,11 @@ }, "ext": { "prebid": { - "bidder": null, - "is_rewarded_inventory": 0, "storedrequest": { "id": "test" } }, - "accountId": 199578 + "accountId": 12345 }, "id": "CFD24FB1-916F-467D-8825-34892B315DB7", "imp": [ @@ -135,8 +134,8 @@ }, "ext": { "bidder": { - "accountId": 199578, - "publisherId": 123456, + "accountId": 12345, + "publisherId": "12341234123412341234", "mediaId": "test_media", "placementId": "test_unicorn" } diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app.json b/adapters/unicorn/unicorntest/exemplary/banner-app.json index b29c1e1d625..ca604895aa3 100644 --- a/adapters/unicorn/unicorntest/exemplary/banner-app.json +++ b/adapters/unicorn/unicorntest/exemplary/banner-app.json @@ -21,8 +21,8 @@ } }, "bidder": { - "accountId": 199578, - "publisherId": 123456, + "accountId": 12345, + "publisherId": "123456", "mediaId": "test_media", "placementId": "test_placement" } @@ -91,8 +91,9 @@ "version": "1.9.0" } }, + "id": "test_media", "publisher": { - "id": "test" + "id": "123456" }, "ver": "1.9.0" }, @@ -114,13 +115,11 @@ }, "ext": { "prebid": { - "bidder": null, - "is_rewarded_inventory": 0, "storedrequest": { "id": "test" } }, - "accountId": 199578 + "accountId": 12345 }, "id": "CFD24FB1-916F-467D-8825-34892B315DB7", "imp": [ @@ -136,8 +135,8 @@ }, "ext": { "bidder": { - "accountId": 199578, - "publisherId": 123456, + "accountId": 12345, + "publisherId": "123456", "mediaId": "test_media", "placementId": "test_placement" } diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app_with_fpd.json b/adapters/unicorn/unicorntest/exemplary/banner-app_with_fpd.json index 0a6fb420adc..668e566490d 100644 --- a/adapters/unicorn/unicorntest/exemplary/banner-app_with_fpd.json +++ b/adapters/unicorn/unicorntest/exemplary/banner-app_with_fpd.json @@ -21,8 +21,8 @@ } }, "bidder": { - "accountId": 199578, - "publisherId": 123456, + "accountId": 12345, + "publisherId": "123456", "mediaId": "test_media", "placementId": "test_placement" }, @@ -97,8 +97,9 @@ "version": "1.9.0" } }, + "id": "test_media", "publisher": { - "id": "test" + "id": "123456" }, "ver": "1.9.0" }, @@ -120,13 +121,11 @@ }, "ext": { "prebid": { - "bidder": null, - "is_rewarded_inventory": 0, "storedrequest": { "id": "test" } }, - "accountId": 199578 + "accountId": 12345 }, "id": "CFD24FB1-916F-467D-8825-34892B315DB7", "imp": [ @@ -142,8 +141,8 @@ }, "ext": { "bidder": { - "accountId": 199578, - "publisherId": 123456, + "accountId": 12345, + "publisherId": "123456", "mediaId": "test_media", "placementId": "test_placement" }, diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app_with_no_fpd.json b/adapters/unicorn/unicorntest/exemplary/banner-app_with_no_fpd.json index 022246382f8..c86c8f4fc98 100644 --- a/adapters/unicorn/unicorntest/exemplary/banner-app_with_no_fpd.json +++ b/adapters/unicorn/unicorntest/exemplary/banner-app_with_no_fpd.json @@ -21,8 +21,8 @@ } }, "bidder": { - "accountId": 199578, - "publisherId": 123456, + "accountId": 12345, + "publisherId": "123456", "mediaId": "test_media", "placementId": "test_placement" }, @@ -94,8 +94,9 @@ "version": "1.9.0" } }, + "id": "test_media", "publisher": { - "id": "test" + "id": "123456" }, "ver": "1.9.0" }, @@ -117,13 +118,11 @@ }, "ext": { "prebid": { - "bidder": null, - "is_rewarded_inventory": 0, "storedrequest": { "id": "test" } }, - "accountId": 199578 + "accountId": 12345 }, "id": "CFD24FB1-916F-467D-8825-34892B315DB7", "imp": [ @@ -139,8 +138,8 @@ }, "ext": { "bidder": { - "accountId": 199578, - "publisherId": 123456, + "accountId": 12345, + "publisherId": "123456", "mediaId": "test_media", "placementId": "test_placement" }, diff --git a/adapters/unicorn/unicorntest/supplemental/204.json b/adapters/unicorn/unicorntest/supplemental/204.json index a05864090ea..1c168a9defc 100644 --- a/adapters/unicorn/unicorntest/supplemental/204.json +++ b/adapters/unicorn/unicorntest/supplemental/204.json @@ -21,8 +21,8 @@ } }, "bidder": { - "accountId": 199578, - "publisherId": 123456, + "accountId": 12345, + "publisherId": "123456", "mediaId": "test_media", "placementId": "test_placement" } @@ -91,8 +91,9 @@ "version": "1.9.0" } }, + "id": "test_media", "publisher": { - "id": "test" + "id": "123456" }, "ver": "1.9.0" }, @@ -114,13 +115,11 @@ }, "ext": { "prebid": { - "bidder": null, - "is_rewarded_inventory": 0, "storedrequest": { "id": "test" } }, - "accountId": 199578 + "accountId": 12345 }, "id": "CFD24FB1-916F-467D-8825-34892B315DB7", "imp": [ @@ -136,8 +135,8 @@ }, "ext": { "bidder": { - "accountId": 199578, - "publisherId": 123456, + "accountId": 12345, + "publisherId": "123456", "mediaId": "test_media", "placementId": "test_placement" } diff --git a/adapters/unicorn/unicorntest/supplemental/400.json b/adapters/unicorn/unicorntest/supplemental/400.json index 6578082ca19..f41a117f15c 100644 --- a/adapters/unicorn/unicorntest/supplemental/400.json +++ b/adapters/unicorn/unicorntest/supplemental/400.json @@ -21,8 +21,8 @@ } }, "bidder": { - "accountId": 199578, - "publisherId": 123456, + "accountId": 12345, + "publisherId": "123456", "mediaId": "test_media", "placementId": "test_placement" } @@ -91,8 +91,9 @@ "version": "1.9.0" } }, + "id": "test_media", "publisher": { - "id": "test" + "id": "123456" }, "ver": "1.9.0" }, @@ -114,13 +115,11 @@ }, "ext": { "prebid": { - "bidder": null, - "is_rewarded_inventory": 0, "storedrequest": { "id": "test" } }, - "accountId": 199578 + "accountId": 12345 }, "id": "CFD24FB1-916F-467D-8825-34892B315DB7", "imp": [ @@ -136,8 +135,8 @@ }, "ext": { "bidder": { - "accountId": 199578, - "publisherId": 123456, + "accountId": 12345, + "publisherId": "123456", "mediaId": "test_media", "placementId": "test_placement" } diff --git a/adapters/unicorn/unicorntest/supplemental/500.json b/adapters/unicorn/unicorntest/supplemental/500.json index c0811be4b24..31b977e4256 100644 --- a/adapters/unicorn/unicorntest/supplemental/500.json +++ b/adapters/unicorn/unicorntest/supplemental/500.json @@ -21,8 +21,8 @@ } }, "bidder": { - "accountId": 199578, - "publisherId": 123456, + "accountId": 12345, + "publisherId": "123456", "mediaId": "test_media", "placementId": "test_placement" } @@ -91,8 +91,9 @@ "version": "1.9.0" } }, + "id": "test_media", "publisher": { - "id": "test" + "id": "123456" }, "ver": "1.9.0" }, @@ -114,13 +115,11 @@ }, "ext": { "prebid": { - "bidder": null, - "is_rewarded_inventory": 0, "storedrequest": { "id": "test" } }, - "accountId": 199578 + "accountId": 12345 }, "id": "CFD24FB1-916F-467D-8825-34892B315DB7", "imp": [ @@ -136,8 +135,8 @@ }, "ext": { "bidder": { - "accountId": 199578, - "publisherId": 123456, + "accountId": 12345, + "publisherId": "123456", "mediaId": "test_media", "placementId": "test_placement" } diff --git a/adapters/unicorn/unicorntest/supplemental/cannot-parse-accountid.json b/adapters/unicorn/unicorntest/supplemental/cannot-parse-accountid.json new file mode 100644 index 00000000000..9ece314b485 --- /dev/null +++ b/adapters/unicorn/unicorntest/supplemental/cannot-parse-accountid.json @@ -0,0 +1,75 @@ +{ + "mockBidRequest": { + "at": 1, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "prebid": { + "storedrequest": { + "id": "test_unicorn" + } + }, + "bidder": { + "accountId": "hogeohge", + "publisherId": "123456", + "mediaId": "test_media", + "placementId": "test_placement" + } + } + } + ], + "cur": ["JPY"], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Error while decoding imp[0].ext: json: cannot unmarshal string into Go struct field ExtImpUnicorn.bidder.accountId of type int", + "comparison": "literal" + } + ] +} diff --git a/adapters/unicorn/unicorntest/supplemental/ccpa-is-enabled.json b/adapters/unicorn/unicorntest/supplemental/ccpa-is-enabled.json index 6bc396c67f1..a06b82c6227 100644 --- a/adapters/unicorn/unicorntest/supplemental/ccpa-is-enabled.json +++ b/adapters/unicorn/unicorntest/supplemental/ccpa-is-enabled.json @@ -26,8 +26,8 @@ } }, "bidder": { - "accountId": 199578, - "publisherId": 123456, + "accountId": 12345, + "publisherId": "123456", "mediaId": "test_media", "placementId": "test_placement" } diff --git a/adapters/unicorn/unicorntest/supplemental/coppa-is-enabled.json b/adapters/unicorn/unicorntest/supplemental/coppa-is-enabled.json index 1c33ce2e805..fec11c14f7a 100644 --- a/adapters/unicorn/unicorntest/supplemental/coppa-is-enabled.json +++ b/adapters/unicorn/unicorntest/supplemental/coppa-is-enabled.json @@ -24,8 +24,8 @@ } }, "bidder": { - "accountId": 199578, - "publisherId": 123456, + "accountId": 12345, + "publisherId": "123456", "mediaId": "test_media", "placementId": "test_placement" } diff --git a/adapters/unicorn/unicorntest/supplemental/gdpr-is-enabled.json b/adapters/unicorn/unicorntest/supplemental/gdpr-is-enabled.json index 3c9222d8cc2..881a0fa8545 100644 --- a/adapters/unicorn/unicorntest/supplemental/gdpr-is-enabled.json +++ b/adapters/unicorn/unicorntest/supplemental/gdpr-is-enabled.json @@ -26,8 +26,8 @@ } }, "bidder": { - "accountId": 199578, - "publisherId": 123456, + "accountId": 12345, + "publisherId": "123456", "mediaId": "test_media", "placementId": "test_placement" } diff --git a/adapters/unicorn/unicorntest/supplemental/no-accountid.json b/adapters/unicorn/unicorntest/supplemental/no-accountid.json new file mode 100644 index 00000000000..3d26f0b74af --- /dev/null +++ b/adapters/unicorn/unicorntest/supplemental/no-accountid.json @@ -0,0 +1,74 @@ +{ + "mockBidRequest": { + "at": 1, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "prebid": { + "storedrequest": { + "id": "test_unicorn" + } + }, + "bidder": { + "publisherId": "123456", + "mediaId": "test_media", + "placementId": "test_placement" + } + } + } + ], + "cur": ["JPY"], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "accountId field is required", + "comparison": "literal" + } + ] +} diff --git a/adapters/unicorn/unicorntest/supplemental/no-app.json b/adapters/unicorn/unicorntest/supplemental/no-app.json new file mode 100644 index 00000000000..a591864983a --- /dev/null +++ b/adapters/unicorn/unicorntest/supplemental/no-app.json @@ -0,0 +1,71 @@ +{ + "mockBidRequest": { + "at": 1, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "prebid": { + "storedrequest": { + "id": "test_unicorn" + } + }, + "bidder": { + "accountId": 12345, + "publisherId": "123456", + "mediaId": "test_media", + "placementId": "test_placement" + } + } + } + ], + "cur": ["JPY"], + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + }, + "ext": { + "prebid": { + "targeting": {}, + "cache": { + "bids": {} + }, + "storedrequest": { + "id": "test" + } + } + } + }, + "expectedMakeRequestsErrors" : [{ + "value": "request app is required", + "comparison": "literal" + }] +} diff --git a/adapters/unicorn/unicorntest/supplemental/no-imp-ext-prebid.json b/adapters/unicorn/unicorntest/supplemental/no-imp-ext-prebid.json index 2e6ce79a176..a75e2bb31ac 100644 --- a/adapters/unicorn/unicorntest/supplemental/no-imp-ext-prebid.json +++ b/adapters/unicorn/unicorntest/supplemental/no-imp-ext-prebid.json @@ -16,8 +16,8 @@ "secure": 1, "ext": { "bidder": { - "accountId": 199578, - "publisherId": 123456, + "accountId": 12345, + "publisherId": "123456", "mediaId": "test_media" } } diff --git a/adapters/unicorn/unicorntest/supplemental/no-storedrequest-imp.json b/adapters/unicorn/unicorntest/supplemental/no-storedrequest-imp.json index d903effd466..5372f96f726 100644 --- a/adapters/unicorn/unicorntest/supplemental/no-storedrequest-imp.json +++ b/adapters/unicorn/unicorntest/supplemental/no-storedrequest-imp.json @@ -18,8 +18,8 @@ "prebid": { }, "bidder": { - "accountId": 199578, - "publisherId": 123456, + "accountId": 12345, + "publisherId": "123456", "mediaId": "test_media" } } diff --git a/adapters/unruly/unruly.go b/adapters/unruly/unruly.go index 40b8fe66bd6..fe0540593b3 100644 --- a/adapters/unruly/unruly.go +++ b/adapters/unruly/unruly.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -17,7 +17,7 @@ type adapter struct { } // 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) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &adapter{ endPoint: config.Endpoint, } diff --git a/adapters/unruly/unruly_test.go b/adapters/unruly/unruly_test.go index c43e509941e..b5d837abea5 100644 --- a/adapters/unruly/unruly_test.go +++ b/adapters/unruly/unruly_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderUnruly, config.Adapter{ - Endpoint: "http://targeting.unrulymedia.com/unruly_prebid_server"}) + Endpoint: "http://targeting.unrulymedia.com/unruly_prebid_server"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/videobyte/params_test.go b/adapters/videobyte/params_test.go index fa38836b03a..b638d4585c6 100644 --- a/adapters/videobyte/params_test.go +++ b/adapters/videobyte/params_test.go @@ -9,7 +9,7 @@ import ( // 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 +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.videobyte // TestValidParams makes sure that the videobyte schema accepts all imp.ext fields which we intend to support. func TestValidParams(t *testing.T) { diff --git a/adapters/videobyte/videobyte.go b/adapters/videobyte/videobyte.go index a4c0df615b8..969d1a9a3c0 100644 --- a/adapters/videobyte/videobyte.go +++ b/adapters/videobyte/videobyte.go @@ -11,14 +11,14 @@ import ( "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" ) type adapter struct { endpoint string } -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &adapter{ endpoint: config.Endpoint, } diff --git a/adapters/videobyte/videobyte_test.go b/adapters/videobyte/videobyte_test.go index 9357a533997..d4dda0606f8 100644 --- a/adapters/videobyte/videobyte_test.go +++ b/adapters/videobyte/videobyte_test.go @@ -9,7 +9,7 @@ import ( ) func TestJsonSamples(t *testing.T) { - bidder, buildErr := Builder("videobyte", config.Adapter{Endpoint: "https://mock.videobyte.com"}) + bidder, buildErr := Builder("videobyte", config.Adapter{Endpoint: "https://mock.videobyte.com"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) } diff --git a/adapters/vidoomy/params_test.go b/adapters/vidoomy/params_test.go index d910b654a3e..63ffb462c19 100644 --- a/adapters/vidoomy/params_test.go +++ b/adapters/vidoomy/params_test.go @@ -9,7 +9,7 @@ import ( // This file actually intends to test static/bidder-params/vidoomy.json // -// These also validate the format of the external API: request.imp[i].ext.vidoomy +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.vidoomy // TestValidParams makes sure that the vidoomy schema accepts all imp.ext fields which we intend to support. func TestValidParams(t *testing.T) { diff --git a/adapters/vidoomy/vidoomy.go b/adapters/vidoomy/vidoomy.go index 57fbb09d35d..584b3b2b18a 100644 --- a/adapters/vidoomy/vidoomy.go +++ b/adapters/vidoomy/vidoomy.go @@ -3,12 +3,14 @@ package vidoomy import ( "encoding/json" "fmt" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "net/http" + + "github.com/prebid/openrtb/v17/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 { @@ -165,7 +167,7 @@ func getImpInfo(impId string, imps []openrtb2.Imp) (bool, openrtb_ext.BidType) { } // Builder builds a new instance of the Vidoomy adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &adapter{ endpoint: config.Endpoint, } diff --git a/adapters/vidoomy/vidoomy_test.go b/adapters/vidoomy/vidoomy_test.go index 11654a0cb5a..60cd2c9d967 100644 --- a/adapters/vidoomy/vidoomy_test.go +++ b/adapters/vidoomy/vidoomy_test.go @@ -13,7 +13,7 @@ import ( func TestVidoomyBidderEndpointConfig(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderVidoomy, config.Adapter{ Endpoint: "http://localhost/bid", - }) + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -25,7 +25,7 @@ func TestVidoomyBidderEndpointConfig(t *testing.T) { } func TestJsonSamples(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderVidoomy, config.Adapter{}) + bidder, buildErr := Builder(openrtb_ext.BidderVidoomy, config.Adapter{}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/visx/visx.go b/adapters/visx/visx.go index 53277ff8fe4..af61a3c60b6 100644 --- a/adapters/visx/visx.go +++ b/adapters/visx/visx.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -69,6 +69,16 @@ func (a *VisxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapte headers := http.Header{} headers.Add("Content-Type", "application/json;charset=utf-8") + if request.Device != nil { + if request.Device.IP != "" { + headers.Add("X-Forwarded-For", request.Device.IP) + } + + if request.Device.IPv6 != "" { + headers.Add("X-Forwarded-For", request.Device.IPv6) + } + } + return []*adapters.RequestData{{ Method: "POST", Uri: a.endpoint, @@ -165,7 +175,7 @@ func getMediaTypeForImp(impID string, imps []openrtb2.Imp, bid visxBid) (openrtb } // 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) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &VisxAdapter{ endpoint: config.Endpoint, } diff --git a/adapters/visx/visx_test.go b/adapters/visx/visx_test.go index 2f51af76597..5fc58a1f83d 100644 --- a/adapters/visx/visx_test.go +++ b/adapters/visx/visx_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderVisx, config.Adapter{ - Endpoint: "http://localhost/prebid"}) + Endpoint: "http://localhost/prebid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/visx/visxtest/exemplary/headers_ipv4.json b/adapters/visx/visxtest/exemplary/headers_ipv4.json new file mode 100644 index 00000000000..4a4da7b5673 --- /dev/null +++ b/adapters/visx/visxtest/exemplary/headers_ipv4.json @@ -0,0 +1,177 @@ +{ + "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": { + "size": [ + 300, + 250 + ], + "uid": 7 + } + } + }], + "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", + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "X-Forwarded-For": ["123.123.123.123"] + }, + "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", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "secure": 1, + "ext": { + "bidder": { + "size": [ + 300, + 250 + ], + "uid": 7 + } + } + }], + "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": 46, + "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", + "impid": "test-imp-id", + "id": "test-request-id", + "h": 250, + "adomain": [ + "goodadvertiser.com" + ], + "dealid": "test_deal_id", + "crid": "2_260", + "w": 300 + }, + "type": "banner" + }] + }] +} diff --git a/adapters/visx/visxtest/exemplary/headers_ipv6.json b/adapters/visx/visxtest/exemplary/headers_ipv6.json new file mode 100644 index 00000000000..a3774c5e771 --- /dev/null +++ b/adapters/visx/visxtest/exemplary/headers_ipv6.json @@ -0,0 +1,177 @@ +{ + "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": { + "size": [ + 300, + 250 + ], + "uid": 7 + } + } + }], + "device": { + "ua": "test-user-agent", + "ipv6": "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "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", + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "X-Forwarded-For": ["2001:0db8:85a3:0000:0000:8a2e:0370:7334"] + }, + "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", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "secure": 1, + "ext": { + "bidder": { + "size": [ + 300, + 250 + ], + "uid": 7 + } + } + }], + "device": { + "ua": "test-user-agent", + "ipv6": "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "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": 46, + "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", + "impid": "test-imp-id", + "id": "test-request-id", + "h": 250, + "adomain": [ + "goodadvertiser.com" + ], + "dealid": "test_deal_id", + "crid": "2_260", + "w": 300 + }, + "type": "banner" + }] + }] +} diff --git a/adapters/vrtcal/params_test.go b/adapters/vrtcal/params_test.go index ba57b6d82f8..d45d3b39013 100644 --- a/adapters/vrtcal/params_test.go +++ b/adapters/vrtcal/params_test.go @@ -12,7 +12,7 @@ import ( // This file actually intends to test static/bidder-params/vrtcal.json // -// These also validate the format of the external API: request.imp[i].ext.vrtcal +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.vrtcal // TestValidParams makes sure that the Vrtcal schema accepts all imp.ext fields which we intend to support. func TestValidParams(t *testing.T) { validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") diff --git a/adapters/vrtcal/vrtcal.go b/adapters/vrtcal/vrtcal.go index ce8392a3bbd..4fada20a6b1 100644 --- a/adapters/vrtcal/vrtcal.go +++ b/adapters/vrtcal/vrtcal.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -87,7 +87,7 @@ func (a *VrtcalAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalR } // Builder builds a new instance of the Vrtcal adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &VrtcalAdapter{ endpoint: config.Endpoint, } diff --git a/adapters/vrtcal/vrtcal_test.go b/adapters/vrtcal/vrtcal_test.go index 332217650f6..31e6c78e2c1 100644 --- a/adapters/vrtcal/vrtcal_test.go +++ b/adapters/vrtcal/vrtcal_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderVrtcal, config.Adapter{ - Endpoint: "http://rtb.vrtcal.com/bidder_prebid.vap?ssp=1804"}) + Endpoint: "http://rtb.vrtcal.com/bidder_prebid.vap?ssp=1804"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/vrtcal/vrtcaltest/exemplary/web-simple-banner.json b/adapters/vrtcal/vrtcaltest/exemplary/web-simple-banner.json new file mode 100644 index 00000000000..62fd90cfc61 --- /dev/null +++ b/adapters/vrtcal/vrtcaltest/exemplary/web-simple-banner.json @@ -0,0 +1,102 @@ +{ + "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" + } + } + } + ], + "site": { + "id": "fake-site-id", + "page": "http://www.vrtcal.com" + } + }, + "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" + } + } + } + ], + "site": { + "id": "fake-site-id", + "page": "http://www.vrtcal.com" + } + } + }, + "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-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-ad", + "crid": "crid_10", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/vrtcal/vrtcaltest/exemplary/web-simple-video.json b/adapters/vrtcal/vrtcaltest/exemplary/web-simple-video.json new file mode 100644 index 00000000000..f70c547a040 --- /dev/null +++ b/adapters/vrtcal/vrtcaltest/exemplary/web-simple-video.json @@ -0,0 +1,98 @@ +{ + "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" + } + } + } + ], + "site": { + "id": "fake-site-id", + "page": "http://www.vrtcal.com" + } + }, + "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" + } + } + } + ], + "site": { + "id": "fake-site-id", + "page": "http://www.vrtcal.com" + } + } + }, + "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/yahoossp/yahoossp.go b/adapters/yahoossp/yahoossp.go index f0cc23bfc6e..0a5e0f256a2 100644 --- a/adapters/yahoossp/yahoossp.go +++ b/adapters/yahoossp/yahoossp.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -191,7 +191,7 @@ func validateBanner(banner *openrtb2.Banner) error { } // 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) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &adapter{ URI: config.Endpoint, } diff --git a/adapters/yahoossp/yahoossp_test.go b/adapters/yahoossp/yahoossp_test.go index a7702f3f8b4..eeb739c612f 100644 --- a/adapters/yahoossp/yahoossp_test.go +++ b/adapters/yahoossp/yahoossp_test.go @@ -13,7 +13,7 @@ import ( func TestYahooSSPBidderEndpointConfig(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderYahooSSP, config.Adapter{ Endpoint: "http://localhost/bid", - }) + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -25,7 +25,7 @@ func TestYahooSSPBidderEndpointConfig(t *testing.T) { } func TestJsonSamples(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderYahooSSP, config.Adapter{}) + bidder, buildErr := Builder(openrtb_ext.BidderYahooSSP, config.Adapter{}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/yeahmobi/yeahmobi.go b/adapters/yeahmobi/yeahmobi.go index 51c9a44d770..92aff886686 100644 --- a/adapters/yeahmobi/yeahmobi.go +++ b/adapters/yeahmobi/yeahmobi.go @@ -8,7 +8,7 @@ import ( "text/template" "github.com/golang/glog" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -21,7 +21,7 @@ type YeahmobiAdapter struct { } // Builder builds a new instance of the Yeahmobi adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (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) diff --git a/adapters/yeahmobi/yeahmobi_test.go b/adapters/yeahmobi/yeahmobi_test.go index a38480b0486..0b1c39ef214 100644 --- a/adapters/yeahmobi/yeahmobi_test.go +++ b/adapters/yeahmobi/yeahmobi_test.go @@ -11,7 +11,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderYeahmobi, config.Adapter{ - Endpoint: "https://{{.Host}}/prebid/bid"}) + Endpoint: "https://{{.Host}}/prebid/bid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -22,7 +22,7 @@ func TestJsonSamples(t *testing.T) { func TestEndpointTemplateMalformed(t *testing.T) { _, buildErr := Builder(openrtb_ext.BidderYeahmobi, config.Adapter{ - Endpoint: "{{Malformed}}"}) + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Error(t, buildErr) } diff --git a/adapters/yieldlab/params_test.go b/adapters/yieldlab/params_test.go index 8c230c15b15..ed0d2863629 100644 --- a/adapters/yieldlab/params_test.go +++ b/adapters/yieldlab/params_test.go @@ -9,7 +9,7 @@ import ( // This file actually intends to test static/bidder-params/yieldlab.json // -// These also validate the format of the external API: request.imp[i].ext.yieldlab +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.yieldlab // TestValidParams makes sure that the yieldlab schema accepts all imp.ext fields which we intend to support. func TestValidParams(t *testing.T) { @@ -40,22 +40,17 @@ func TestInvalidParams(t *testing.T) { } var validParams = []string{ - `{"adslotId": "123","supplyId":"23456","adSize":"100x100"}`, - `{"adslotId": "123","supplyId":"23456","adSize":"100x100","extId":"asdf"}`, - `{"adslotId": "123","supplyId":"23456","adSize":"100x100","extId":"asdf","targeting":{"a":"b"}}`, - `{"adslotId": "123","supplyId":"23456","adSize":"100x100","targeting":{"a":"b"}}`, - `{"adslotId": "123","supplyId":"23456","adSize":"100x100","targeting":{"a":"b"}}`, + `{"adslotId": "123","supplyId":"23456"}`, + `{"adslotId": "123","supplyId":"23456","extId":"asdf"}`, + `{"adslotId": "123","supplyId":"23456","extId":"asdf","targeting":{"a":"b"}}`, + `{"adslotId": "123","supplyId":"23456","targeting":{"a":"b"}}`, } var invalidParams = []string{ - `{"supplyId":"23456","adSize":"100x100"}`, - `{"adslotId": "123","adSize":"100x100","extId":"asdf"}`, - `{"adslotId": "123","supplyId":"23456","extId":"asdf","targeting":{"a":"b"}}`, - `{"adslotId": "123","supplyId":"23456"}`, - `{"adSize":"100x100","supplyId":"23456"}`, - `{"adslotId": "123","adSize":"100x100"}`, `{"supplyId":"23456"}`, + `{"adslotId": "123","extId":"asdf"}`, `{"adslotId": "123"}`, + `{"supplyId":"23456"}`, `{}`, `[]`, `{"a":"b"}`, diff --git a/adapters/yieldlab/yieldlab.go b/adapters/yieldlab/yieldlab.go index 1447aeaf9b1..5f73973577e 100644 --- a/adapters/yieldlab/yieldlab.go +++ b/adapters/yieldlab/yieldlab.go @@ -11,7 +11,7 @@ import ( "golang.org/x/text/currency" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -26,7 +26,7 @@ type YieldlabAdapter struct { } // Builder builds a new instance of the Yieldlab adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &YieldlabAdapter{ endpoint: config.Endpoint, cacheBuster: defaultCacheBuster, @@ -35,7 +35,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -// Builds endpoint url based on adapter-specific pub settings from imp.ext +// makeEndpointURL builds endpoint url based on adapter-specific pub settings from imp.ext func (a *YieldlabAdapter) makeEndpointURL(req *openrtb2.BidRequest, params *openrtb_ext.ExtImpYieldlab) (string, error) { uri, err := url.Parse(a.endpoint) if err != nil { @@ -49,6 +49,10 @@ func (a *YieldlabAdapter) makeEndpointURL(req *openrtb2.BidRequest, params *open q.Set("ts", a.cacheBuster()) q.Set("t", a.makeTargetingValues(params)) + if hasFormats, formats := a.makeFormats(req); hasFormats { + q.Set("sizes", formats) + } + if req.User != nil && req.User.BuyerUID != "" { q.Set("ids", "ylid:"+req.User.BuyerUID) } @@ -83,11 +87,38 @@ func (a *YieldlabAdapter) makeEndpointURL(req *openrtb2.BidRequest, params *open q.Set("consent", consent) } + if req.Source != nil && req.Source.Ext != nil { + if openRtbSchain := unmarshalSupplyChain(req); openRtbSchain != nil { + if schainValue := makeSupplyChain(*openRtbSchain); schainValue != "" { + q.Set("schain", schainValue) + } + } + } + uri.RawQuery = q.Encode() return uri.String(), nil } +func (a *YieldlabAdapter) makeFormats(req *openrtb2.BidRequest) (bool, string) { + var formats []string + const sizesSeparator, adslotSizesSeparator = "|", "," + for _, impression := range req.Imp { + if !impIsTypeBannerOnly(impression) { + continue + } + + var formatsPerAdslot []string + for _, format := range impression.Banner.Format { + formatsPerAdslot = append(formatsPerAdslot, fmt.Sprintf("%dx%d", format.W, format.H)) + } + adslotID := a.extractAdslotID(impression) + sizesForAdslot := strings.Join(formatsPerAdslot, sizesSeparator) + formats = append(formats, fmt.Sprintf("%s:%s", adslotID, sizesForAdslot)) + } + return len(formats) != 0, strings.Join(formats, adslotSizesSeparator) +} + func (a *YieldlabAdapter) getGDPR(request *openrtb2.BidRequest) (string, string, error) { consent := "" if request.User != nil && request.User.Ext != nil { @@ -321,6 +352,72 @@ func (a *YieldlabAdapter) makeCreativeID(req *openrtb_ext.ExtImpYieldlab, bid *b return fmt.Sprintf(creativeID, req.AdslotID, bid.Pid, a.getWeek()) } +// unmarshalSupplyChain makes the value for the schain URL parameter from the openRTB schain object. +func unmarshalSupplyChain(req *openrtb2.BidRequest) *openrtb2.SupplyChain { + var extSChain openrtb_ext.ExtRequestPrebidSChain + err := json.Unmarshal(req.Source.Ext, &extSChain) + if err != nil { + // req.Source.Ext could be anything so don't handle any errors + return nil + } + return &extSChain.SChain +} + +// makeNodeValue makes the value for the schain URL parameter from the openRTB schain object. +func makeSupplyChain(openRtbSchain openrtb2.SupplyChain) string { + if len(openRtbSchain.Nodes) == 0 { + return "" + } + + const schainPrefixFmt = "%s,%d" + const schainNodeFmt = "!%s,%s,%s,%s,%s,%s,%s" + schainPrefix := fmt.Sprintf(schainPrefixFmt, openRtbSchain.Ver, openRtbSchain.Complete) + var sb strings.Builder + sb.WriteString(schainPrefix) + for _, node := range openRtbSchain.Nodes { + // has to be in order: asi,sid,hp,rid,name,domain,ext + schainNode := fmt.Sprintf( + schainNodeFmt, + makeNodeValue(node.ASI), + makeNodeValue(node.SID), + makeNodeValue(node.HP), + makeNodeValue(node.RID), + makeNodeValue(node.Name), + makeNodeValue(node.Domain), + makeNodeValue(node.Ext), + ) + sb.WriteString(schainNode) + } + return sb.String() +} + +// makeNodeValue converts any known value type from a schain node to a string and does URL encoding if necessary. +func makeNodeValue(nodeParam any) string { + switch nodeParam.(type) { + case string: + return url.QueryEscape(nodeParam.(string)) + case *int8: + pointer := nodeParam.(*int8) + if pointer == nil { + return "" + } + return makeNodeValue(int(*pointer)) + case int: + return strconv.Itoa(nodeParam.(int)) + case json.RawMessage: + if freeFormData := nodeParam.(json.RawMessage); freeFormData != nil { + freeFormJson, err := json.Marshal(freeFormData) + if err != nil { + return "" + } + return makeNodeValue(string(freeFormJson)) + } + return "" + default: + return "" + } +} + func splitSize(size string) (uint64, uint64, error) { sizeParts := strings.Split(size, adsizeSeparator) if len(sizeParts) != 2 { @@ -340,3 +437,8 @@ func splitSize(size string) (uint64, uint64, error) { return width, height, nil } + +// impIsTypeBannerOnly returns true if impression is only from type banner. Mixed typed with banner would also result in false. +func impIsTypeBannerOnly(impression openrtb2.Imp) bool { + return impression.Banner != nil && impression.Audio == nil && impression.Video == nil && impression.Native == nil +} diff --git a/adapters/yieldlab/yieldlab_test.go b/adapters/yieldlab/yieldlab_test.go index 273117e3bdf..b551d8a1289 100644 --- a/adapters/yieldlab/yieldlab_test.go +++ b/adapters/yieldlab/yieldlab_test.go @@ -1,6 +1,9 @@ package yieldlab import ( + "encoding/json" + "github.com/prebid/openrtb/v17/openrtb2" + "strconv" "testing" "github.com/stretchr/testify/assert" @@ -30,7 +33,7 @@ func newTestYieldlabBidder(endpoint string) *YieldlabAdapter { func TestNewYieldlabBidder(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderYieldlab, config.Adapter{ - Endpoint: testURL}) + Endpoint: testURL}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.NoError(t, buildErr) assert.NotNil(t, bidder) @@ -128,9 +131,135 @@ func Test_splitSize(t *testing.T) { } } +func Test_makeNodeValue(t *testing.T) { + int8TestCase := int8(8) + tests := []struct { + name string + nodeParam interface{} + expected string + }{ + { + name: "string with special characters", + nodeParam: "AZ09-._~:/?#[]@!$%&'()*+,;=", + expected: "AZ09-._~%3A%2F%3F%23%5B%5D%40%21%24%25%26%27%28%29%2A%2B%2C%3B%3D", + }, + { + name: "int8 pointer", + nodeParam: &int8TestCase, + expected: "8", + }, + { + name: "int", + nodeParam: 8, + expected: "8", + }, + { + name: "free form data", + nodeParam: json.RawMessage(`{"foo":"bar"}`), + expected: "%7B%22foo%22%3A%22bar%22%7D", + }, + { + name: "unknown type (bool)", + nodeParam: true, + expected: "", + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + actual := makeNodeValue(test.nodeParam) + assert.Equal(t, test.expected, actual) + }) + } +} + +func Test_makeSupplyChain(t *testing.T) { + hp := int8(1) + tests := []struct { + name string + param openrtb2.SupplyChain + expected string + }{ + { + name: "No nodes", + param: openrtb2.SupplyChain{ + Ver: "1.0", + Complete: 1, + }, + expected: "", + }, + { + name: "Not all fields", + param: openrtb2.SupplyChain{ + Ver: "1.0", + Complete: 1, + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "exchange1.com", + SID: "12345", + HP: &hp, + }, + }, + }, + + expected: "1.0,1!exchange1.com,12345,1,,,,", + }, + { + name: "All fields handled in correct order", + param: openrtb2.SupplyChain{ + Ver: "1.0", + Complete: 1, + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "exchange1.com", + SID: "12345", + HP: &hp, + RID: "bid-request-1", + Name: "publisher", + Domain: "publisher.com", + Ext: []byte("{\"ext\":\"test\"}"), + }, + }, + }, + expected: "1.0,1!exchange1.com,12345,1,bid-request-1,publisher,publisher.com,%7B%22ext%22%3A%22test%22%7D", + }, + { + name: "handle simple node.ext type (string)", + param: openrtb2.SupplyChain{ + Ver: "1.0", + Complete: 1, + Nodes: []openrtb2.SupplyChainNode{ + { + Ext: []byte("\"ext\""), + }, + }, + }, + expected: "1.0,1!,,,,,,%22ext%22", + }, + { + name: "handle simple node.ext type (int)", + param: openrtb2.SupplyChain{ + Ver: "1.0", + Complete: 1, + Nodes: []openrtb2.SupplyChainNode{ + { + Ext: []byte(strconv.Itoa(1)), + }, + }, + }, + expected: "1.0,1!,,,,,,1", + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + actual := makeSupplyChain(test.param) + assert.Equal(t, test.expected, actual) + }) + } +} + func TestYieldlabAdapter_makeEndpointURL_invalidEndpoint(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderYieldlab, config.Adapter{ - Endpoint: "test$:/something§"}) + Endpoint: "test$:/something§"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/yieldlab/yieldlabtest/exemplary/banner.json b/adapters/yieldlab/yieldlabtest/exemplary/banner.json index 8dd94404097..fea8b3da6ee 100644 --- a/adapters/yieldlab/yieldlabtest/exemplary/banner.json +++ b/adapters/yieldlab/yieldlabtest/exemplary/banner.json @@ -9,6 +9,10 @@ { "w": 728, "h": 90 + }, + { + "w": 800, + "h": 300 } ] }, @@ -16,7 +20,6 @@ "bidder": { "adslotId": "12345", "supplyId": "123456789", - "adSize": "728x90", "targeting": { "key1": "value1", "key2": "value2" @@ -70,7 +73,7 @@ "169.254.13.37" ] }, - "uri": "https://ad.yieldlab.net/testing/12345?content=json&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads" + "uri": "https://ad.yieldlab.net/testing/12345?content=json&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&sizes=12345%3A728x90%7C800x300&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads" }, "mockResponse": { "status": 200, diff --git a/adapters/yieldlab/yieldlabtest/exemplary/gdpr.json b/adapters/yieldlab/yieldlabtest/exemplary/gdpr.json index 381ba688e09..14e708289aa 100644 --- a/adapters/yieldlab/yieldlabtest/exemplary/gdpr.json +++ b/adapters/yieldlab/yieldlabtest/exemplary/gdpr.json @@ -16,7 +16,6 @@ "bidder": { "adslotId": "12345", "supplyId": "123456789", - "adSize": "728x90", "targeting": { "key1": "value1", "key2": "value2" @@ -78,7 +77,7 @@ "169.254.13.37" ] }, - "uri": "https://ad.yieldlab.net/testing/12345?consent=BOlOrv1OlOr2EAAABADECg-AAAApp7v______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-3zd4u_1vf99yfm1-7etr3tp_87ues2_Xur__79__3z3_9phP78k89r7337Ew-v02&content=json&gdpr=1&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads" + "uri": "https://ad.yieldlab.net/testing/12345?consent=BOlOrv1OlOr2EAAABADECg-AAAApp7v______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-3zd4u_1vf99yfm1-7etr3tp_87ues2_Xur__79__3z3_9phP78k89r7337Ew-v02&content=json&gdpr=1&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&sizes=12345%3A728x90&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads" }, "mockResponse": { "status": 200, diff --git a/adapters/yieldlab/yieldlabtest/exemplary/mixed_types.json b/adapters/yieldlab/yieldlabtest/exemplary/mixed_types.json new file mode 100644 index 00000000000..00f2185262f --- /dev/null +++ b/adapters/yieldlab/yieldlabtest/exemplary/mixed_types.json @@ -0,0 +1,140 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + }, + { + "w": 800, + "h": 300 + } + ] + }, + "video": { + "context": "instream", + "mimes": [ + "video/mp4" + ], + "playerSize": [ + [ + 400, + 600 + ] + ], + "minduration": 1, + "maxduration": 2, + "protocols": [ + 1, + 2 + ], + "w": 1, + "h": 2, + "startdelay": 1, + "placement": 1, + "playbackmethod": [ + 2 + ] + }, + "ext": { + "bidder": { + "adslotId": "12345", + "supplyId": "123456789", + "targeting": { + "key1": "value1", + "key2": "value2" + }, + "extId": "abc" + } + } + } + ], + "user": { + "buyeruid": "34a53e82-0dc3-4815-8b7e-b725ede0361c" + }, + "device": { + "ifa": "hello-ads", + "devicetype": 4, + "connectiontype": 6, + "geo": { + "lat": 51.499488, + "lon": -0.128953 + }, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36", + "ip": "169.254.13.37", + "h": 1098, + "w": 814 + }, + "site": { + "id": "fake-site-id", + "publisher": { + "id": "1" + }, + "page": "http://localhost:9090/gdpr.html" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Accept": [ + "application/json" + ], + "Cookie": [ + "id=34a53e82-0dc3-4815-8b7e-b725ede0361c" + ], + "Referer": [ + "http://localhost:9090/gdpr.html" + ], + "User-Agent": [ + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36" + ], + "X-Forwarded-For": [ + "169.254.13.37" + ] + }, + "uri": "https://ad.yieldlab.net/testing/12345?content=json&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads" + }, + "mockResponse": { + "status": 200, + "body": [ + { + "id": 12345, + "price": 201, + "advertiser": "yieldlab", + "adsize": "728x90", + "pid": 1234, + "did": 5678, + "pvid": "40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5" + } + ] + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "crid": "12345123433", + "dealid": "1234", + "id": "12345", + "impid": "test-imp-id", + "nurl": "https://ad.yieldlab.net/d/12345/123456789/728x90?id=abc&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&pvid=40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5&ts=testing", + "adm": "<VAST version=\"2.0\"><Ad id=\"12345\"><Wrapper><AdSystem>Yieldlab</AdSystem><VASTAdTagURI><![CDATA[ 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 ]]></VASTAdTagURI><Impression></Impression><Creatives></Creatives></Wrapper></Ad></VAST>", + "price": 2.01, + "w": 728, + "h": 90 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/yieldlab/yieldlabtest/exemplary/multiple_impressions.json b/adapters/yieldlab/yieldlabtest/exemplary/multiple_impressions.json index c68748e39f4..ead4be0de5a 100644 --- a/adapters/yieldlab/yieldlabtest/exemplary/multiple_impressions.json +++ b/adapters/yieldlab/yieldlabtest/exemplary/multiple_impressions.json @@ -16,7 +16,6 @@ "bidder": { "adslotId": "12345", "supplyId": "123456789", - "adSize": "728x90", "targeting": { "key1": "value1", "key2": "value2" @@ -39,7 +38,6 @@ "bidder": { "adslotId": "67890", "supplyId": "123456789", - "adSize": "300x250", "targeting": { "key1": "value1", "key2": "value2" @@ -93,7 +91,7 @@ "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" + "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&sizes=12345%3A728x90%2C67890%3A300x250&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads" }, "mockResponse": { "status": 200, diff --git a/adapters/yieldlab/yieldlabtest/exemplary/schain.json b/adapters/yieldlab/yieldlabtest/exemplary/schain.json new file mode 100644 index 00000000000..e3e7230d4d3 --- /dev/null +++ b/adapters/yieldlab/yieldlabtest/exemplary/schain.json @@ -0,0 +1,134 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "adslotId": "12345", + "supplyId": "123456789", + "targeting": { + "key1": "value1", + "key2": "value2" + }, + "extId": "abc" + } + } + } + ], + "source": { + "ext": { + "schain": { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "exchange1.com", + "sid": "1234!abcd", + "hp": 1, + "rid": "bid request&%1", + "name": "publisher", + "domain": "publisher.com", + "ext": { + "freeFormData": 1, + "nested": { + "isTrue": true + } + } + } + ] + } + } + }, + "user": { + "buyeruid": "34a53e82-0dc3-4815-8b7e-b725ede0361c" + }, + "device": { + "ifa": "hello-ads", + "devicetype": 4, + "connectiontype": 6, + "geo": { + "lat": 51.499488, + "lon": -0.128953 + }, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36", + "ip": "169.254.13.37", + "h": 1098, + "w": 814 + }, + "site": { + "id": "fake-site-id", + "publisher": { + "id": "1" + }, + "page": "http://localhost:9090/gdpr.html" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Accept": [ + "application/json" + ], + "Cookie": [ + "id=34a53e82-0dc3-4815-8b7e-b725ede0361c" + ], + "Referer": [ + "http://localhost:9090/gdpr.html" + ], + "User-Agent": [ + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36" + ], + "X-Forwarded-For": [ + "169.254.13.37" + ] + }, + "uri": "https://ad.yieldlab.net/testing/12345?content=json&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&schain=1.0%2C1%21exchange1.com%2C1234%2521abcd%2C1%2Cbid%2Brequest%2526%25251%2Cpublisher%2Cpublisher.com%2C%257B%2522freeFormData%2522%253A1%252C%2522nested%2522%253A%257B%2522isTrue%2522%253Atrue%257D%257D&sizes=12345%3A728x90&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads" + }, + "mockResponse": { + "status": 200, + "body": [ + { + "id": 12345, + "price": 201, + "advertiser": "yieldlab", + "adsize": "728x90", + "pid": 1234, + "did": 5678, + "pvid": "40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5" + } + ] + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "adm": "<script src=\"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\"></script>", + "crid": "12345123433", + "dealid": "1234", + "id": "12345", + "impid": "test-imp-id", + "price": 2.01, + "w": 728, + "h": 90 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/yieldlab/yieldlabtest/exemplary/schain_multiple_nodes.json b/adapters/yieldlab/yieldlabtest/exemplary/schain_multiple_nodes.json new file mode 100644 index 00000000000..d976898f29f --- /dev/null +++ b/adapters/yieldlab/yieldlabtest/exemplary/schain_multiple_nodes.json @@ -0,0 +1,132 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "adslotId": "12345", + "supplyId": "123456789", + "targeting": { + "key1": "value1", + "key2": "value2" + }, + "extId": "abc" + } + } + } + ], + "source": { + "ext": { + "schain": { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "exchange1.com", + "sid": "1234", + "hp": 1, + "ext": "text" + }, + { + "asi": "exchange2.com", + "sid": "abcd", + "hp": 1, + "ext": 1 + } + ] + } + } + }, + "user": { + "buyeruid": "34a53e82-0dc3-4815-8b7e-b725ede0361c" + }, + "device": { + "ifa": "hello-ads", + "devicetype": 4, + "connectiontype": 6, + "geo": { + "lat": 51.499488, + "lon": -0.128953 + }, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36", + "ip": "169.254.13.37", + "h": 1098, + "w": 814 + }, + "site": { + "id": "fake-site-id", + "publisher": { + "id": "1" + }, + "page": "http://localhost:9090/gdpr.html" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Accept": [ + "application/json" + ], + "Cookie": [ + "id=34a53e82-0dc3-4815-8b7e-b725ede0361c" + ], + "Referer": [ + "http://localhost:9090/gdpr.html" + ], + "User-Agent": [ + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36" + ], + "X-Forwarded-For": [ + "169.254.13.37" + ] + }, + "uri": "https://ad.yieldlab.net/testing/12345?content=json&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&schain=1.0%2C1%21exchange1.com%2C1234%2C1%2C%2C%2C%2C%2522text%2522%21exchange2.com%2Cabcd%2C1%2C%2C%2C%2C1&sizes=12345%3A728x90&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads" + }, + "mockResponse": { + "status": 200, + "body": [ + { + "id": 12345, + "price": 201, + "advertiser": "yieldlab", + "adsize": "728x90", + "pid": 1234, + "did": 5678, + "pvid": "40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5" + } + ] + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "adm": "<script src=\"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\"></script>", + "crid": "12345123433", + "dealid": "1234", + "id": "12345", + "impid": "test-imp-id", + "price": 2.01, + "w": 728, + "h": 90 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/yieldlab/yieldlabtest/exemplary/video.json b/adapters/yieldlab/yieldlabtest/exemplary/video.json index b15d63a92e0..3d545a419bb 100644 --- a/adapters/yieldlab/yieldlabtest/exemplary/video.json +++ b/adapters/yieldlab/yieldlabtest/exemplary/video.json @@ -4,19 +4,10 @@ "imp": [ { "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 728, - "h": 90 - } - ] - }, "ext": { "bidder": { "adslotId": "12345", "supplyId": "123456789", - "adSize": "728x90", "targeting": { "key1": "value1", "key2": "value2" diff --git a/adapters/yieldlab/yieldlabtest/exemplary/video_app.json b/adapters/yieldlab/yieldlabtest/exemplary/video_app.json index 17d96ddcb08..d9980e221a3 100644 --- a/adapters/yieldlab/yieldlabtest/exemplary/video_app.json +++ b/adapters/yieldlab/yieldlabtest/exemplary/video_app.json @@ -4,19 +4,10 @@ "imp": [ { "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 728, - "h": 90 - } - ] - }, "ext": { "bidder": { "adslotId": "12345", "supplyId": "123456789", - "adSize": "728x90", "targeting": { "key1": "value1", "key2": "value2" diff --git a/adapters/yieldmo/params_test.go b/adapters/yieldmo/params_test.go index 0a8fe2d10f1..d94c7ff035b 100644 --- a/adapters/yieldmo/params_test.go +++ b/adapters/yieldmo/params_test.go @@ -9,7 +9,7 @@ import ( // This file actually intends to test static/bidder-params/yieldmo.json // -// These also validate the format of the external API: request.imp[i].ext.yieldmo +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.yieldmo // TestValidParams makes sure that the Yieldmo schema accepts all imp.ext fields which we intend to support. func TestValidParams(t *testing.T) { diff --git a/adapters/yieldmo/yieldmo.go b/adapters/yieldmo/yieldmo.go index eaaa489d722..03828ffd4a7 100644 --- a/adapters/yieldmo/yieldmo.go +++ b/adapters/yieldmo/yieldmo.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -149,7 +149,7 @@ func (a *YieldmoAdapter) MakeBids(internalRequest *openrtb2.BidRequest, external } // Builder builds a new instance of the Yieldmo adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &YieldmoAdapter{ endpoint: config.Endpoint, } diff --git a/adapters/yieldmo/yieldmo_test.go b/adapters/yieldmo/yieldmo_test.go index cb0a8d60aa5..1d9426d0643 100644 --- a/adapters/yieldmo/yieldmo_test.go +++ b/adapters/yieldmo/yieldmo_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderYieldmo, config.Adapter{ - Endpoint: "https://ads.yieldmo.com/openrtb2"}) + Endpoint: "https://ads.yieldmo.com/openrtb2"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/yieldone/yieldone.go b/adapters/yieldone/yieldone.go index 4e22b1446a7..9a44f510fe9 100644 --- a/adapters/yieldone/yieldone.go +++ b/adapters/yieldone/yieldone.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -86,12 +86,15 @@ func (a *YieldoneAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa }) } } + if bidResp.Cur != "" { + bidResponse.Currency = bidResp.Cur + } return bidResponse, nil } // Builder builds a new instance of the Yieldone adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &YieldoneAdapter{ endpoint: config.Endpoint, } diff --git a/adapters/yieldone/yieldone_test.go b/adapters/yieldone/yieldone_test.go index 8544c21f4e7..12d634d463d 100644 --- a/adapters/yieldone/yieldone_test.go +++ b/adapters/yieldone/yieldone_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderYieldone, config.Adapter{ - Endpoint: "http://localhost/prebid"}) + Endpoint: "http://localhost/prebid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/zeroclickfraud/zeroclickfraud.go b/adapters/zeroclickfraud/zeroclickfraud.go index 862fd173205..06d1e22b975 100644 --- a/adapters/zeroclickfraud/zeroclickfraud.go +++ b/adapters/zeroclickfraud/zeroclickfraud.go @@ -7,7 +7,7 @@ import ( "strconv" "text/template" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -178,7 +178,7 @@ func getMediaType(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { } // 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) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (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) diff --git a/adapters/zeroclickfraud/zeroclickfraud_test.go b/adapters/zeroclickfraud/zeroclickfraud_test.go index 654d9450da0..e07c43ff7a2 100644 --- a/adapters/zeroclickfraud/zeroclickfraud_test.go +++ b/adapters/zeroclickfraud/zeroclickfraud_test.go @@ -11,7 +11,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderZeroClickFraud, config.Adapter{ - Endpoint: "http://{{.Host}}/openrtb2?sid={{.SourceId}}"}) + Endpoint: "http://{{.Host}}/openrtb2?sid={{.SourceId}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -22,7 +22,7 @@ func TestJsonSamples(t *testing.T) { func TestEndpointTemplateMalformed(t *testing.T) { _, buildErr := Builder(openrtb_ext.BidderZeroClickFraud, config.Adapter{ - Endpoint: "{{Malformed}}"}) + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) assert.Error(t, buildErr) } diff --git a/amp/parse.go b/amp/parse.go index 1ce19a76da3..bbbcee793e5 100644 --- a/amp/parse.go +++ b/amp/parse.go @@ -2,24 +2,35 @@ package amp import ( "errors" + "fmt" "net/http" "strconv" "strings" - "github.com/mxmCherry/openrtb/v15/openrtb2" + tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2" + + "github.com/prebid/openrtb/v17/openrtb2" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" ) // Params defines the parameters of an AMP request. type Params struct { - Account string - CanonicalURL string - Consent string - Debug bool - Origin string - Size Size - Slot string - StoredRequestID string - Timeout *uint64 + Account string + AdditionalConsent string + CanonicalURL string + Consent string + ConsentType int64 + Debug bool + GdprApplies *bool + Origin string + Size Size + Slot string + StoredRequestID string + Targeting string + Timeout *uint64 } // Size defines size information of an AMP request. @@ -31,6 +42,109 @@ type Size struct { Width int64 } +// Policy consent types +const ( + ConsentNone = 0 + ConsentTCF1 = 1 + ConsentTCF2 = 2 + ConsentUSPrivacy = 3 +) + +// ReadPolicy returns a privacy writer in accordance to the query values consent, consent_type and gdpr_applies. +// Returned policy writer could either be GDPR, CCPA or NilPolicy. The second return value is a warning. +func ReadPolicy(ampParams Params, pbsConfigGDPREnabled bool) (privacy.PolicyWriter, error) { + if len(ampParams.Consent) == 0 { + return privacy.NilPolicyWriter{}, nil + } + + var rv privacy.PolicyWriter = privacy.NilPolicyWriter{} + var warning error + var warningMsg string + + // If consent_type was set to CCPA or GDPR TCF2, we return a valid writer even if the consent string is invalid + switch ampParams.ConsentType { + case ConsentTCF1: + warningMsg = "TCF1 consent is deprecated and no longer supported." + case ConsentTCF2: + if pbsConfigGDPREnabled { + rv = buildGdprTCF2ConsentWriter(ampParams) + // Log warning if GDPR consent string is invalid + warningMsg = validateTCf2ConsentString(ampParams.Consent) + } + case ConsentUSPrivacy: + rv = ccpa.ConsentWriter{ampParams.Consent} + if ccpa.ValidateConsent(ampParams.Consent) { + if parseGdprApplies(ampParams.GdprApplies) == 1 { + // Log warning because AMP request comes with both a valid CCPA string and gdpr_applies set to true + warningMsg = "AMP request gdpr_applies value was ignored because provided consent string is a CCPA consent string" + } + } else { + // Log warning if CCPA string is invalid + warningMsg = fmt.Sprintf("Consent string '%s' is not a valid CCPA consent string.", ampParams.Consent) + } + default: + if ccpa.ValidateConsent(ampParams.Consent) { + rv = ccpa.ConsentWriter{ampParams.Consent} + if parseGdprApplies(ampParams.GdprApplies) == 1 { + warningMsg = "AMP request gdpr_applies value was ignored because provided consent string is a CCPA consent string" + } + } else if pbsConfigGDPREnabled && len(validateTCf2ConsentString(ampParams.Consent)) == 0 { + rv = buildGdprTCF2ConsentWriter(ampParams) + } else { + warningMsg = fmt.Sprintf("Consent string '%s' is not recognized as one of the supported formats CCPA or TCF2.", ampParams.Consent) + } + } + + if len(warningMsg) > 0 { + warning = &errortypes.Warning{ + Message: warningMsg, + WarningCode: errortypes.InvalidPrivacyConsentWarningCode, + } + } + return rv, warning +} + +// buildGdprTCF2ConsentWriter returns a gdpr.ConsentWriter that will set regs.ext.gdpr to the value +// of 1 if gdpr_applies wasn't defined. The reason for this is that this function gets called when +// GDPR applies, even if field gdpr_applies wasn't set in the AMP endpoint query. +func buildGdprTCF2ConsentWriter(ampParams Params) gdpr.ConsentWriter { + writer := gdpr.ConsentWriter{Consent: ampParams.Consent} + + // If gdpr_applies was not set, regs.ext.gdpr must equal 1 + var gdprValue int8 = 1 + if ampParams.GdprApplies != nil { + // set regs.ext.gdpr if non-nil gdpr_applies was set to true + gdprValue = parseGdprApplies(ampParams.GdprApplies) + } + writer.RegExtGDPR = &gdprValue + + return writer +} + +// parseGdprApplies returns a 0 if gdprApplies was not set or if false, and a 1 if +// gdprApplies was set to true +func parseGdprApplies(gdprApplies *bool) int8 { + gdpr := int8(0) + + if gdprApplies != nil && *gdprApplies { + gdpr = int8(1) + } + + return gdpr +} + +// ParseParams parses the AMP parameters from a HTTP request. +func validateTCf2ConsentString(consent string) string { + if tcf2.IsConsentV2(consent) { + if _, err := tcf2.ParseString(consent); err != nil { + return err.Error() + } + } else { + return fmt.Sprintf("Consent string '%s' is not a valid TCF2 consent string.", consent) + } + return "" +} + // ParseParams parses the AMP parameters from a HTTP request. func ParseParams(httpRequest *http.Request) (Params, error) { query := httpRequest.URL.Query() @@ -41,11 +155,13 @@ func ParseParams(httpRequest *http.Request) (Params, error) { } params := Params{ - Account: query.Get("account"), - CanonicalURL: query.Get("curl"), - Consent: chooseConsent(query.Get("consent_string"), query.Get("gdpr_consent")), - Debug: query.Get("debug") == "1", - Origin: query.Get("__amp_source_origin"), + Account: query.Get("account"), + AdditionalConsent: query.Get("addtl_consent"), + CanonicalURL: query.Get("curl"), + Consent: chooseConsent(query.Get("consent_string"), query.Get("gdpr_consent")), + ConsentType: parseInt(query.Get("consent_type")), + Debug: query.Get("debug") == "1", + Origin: query.Get("__amp_source_origin"), Size: Size{ Height: parseInt(query.Get("h")), Multisize: parseMultisize(query.Get("ms")), @@ -55,16 +171,34 @@ func ParseParams(httpRequest *http.Request) (Params, error) { }, Slot: query.Get("slot"), StoredRequestID: tagID, - Timeout: parseIntPtr(query.Get("timeout")), + Targeting: query.Get("targeting"), + } + var err error + urlQueryGdprApplies := query.Get("gdpr_applies") + if len(urlQueryGdprApplies) > 0 { + if params.GdprApplies, err = parseBoolPtr(urlQueryGdprApplies); err != nil { + return params, err + } } + + urlQueryTimeout := query.Get("timeout") + if len(urlQueryTimeout) > 0 { + if params.Timeout, err = parseIntPtr(urlQueryTimeout); err != nil { + return params, err + } + } + return params, nil } -func parseIntPtr(value string) *uint64 { - if parsed, err := strconv.ParseUint(value, 10, 64); err == nil { - return &parsed +func parseIntPtr(value string) (*uint64, error) { + var rv uint64 + var err error + + if rv, err = strconv.ParseUint(value, 10, 64); err != nil { + return nil, err } - return nil + return &rv, nil } func parseInt(value string) int64 { @@ -74,6 +208,16 @@ func parseInt(value string) int64 { return 0 } +func parseBoolPtr(value string) (*bool, error) { + var rv bool + var err error + + if rv, err = strconv.ParseBool(value); err != nil { + return nil, err + } + return &rv, nil +} + func parseMultisize(multisize string) []openrtb2.Format { if multisize == "" { return nil diff --git a/amp/parse_test.go b/amp/parse_test.go index 91027f8e67c..d2a9c5eefd9 100644 --- a/amp/parse_test.go +++ b/amp/parse_test.go @@ -4,7 +4,11 @@ import ( "net/http" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" + "github.com/prebid/prebid-server/errortypes" + "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" ) @@ -24,8 +28,9 @@ func TestParseParams(t *testing.T) { }, { description: "All Fields", + // targeting data is encoded string that looks like this: {"gam-key1":"val1","gam-key2":"val2"} query: "tag_id=anyTagID&account=anyAccount&curl=anyCurl&consent_string=anyConsent&debug=1&__amp_source_origin=anyOrigin" + - "&slot=anySlot&timeout=42&h=1&w=2&oh=3&ow=4&ms=10x11,12x13", + "&slot=anySlot&timeout=42&h=1&w=2&oh=3&ow=4&ms=10x11,12x13&targeting=%7B%22gam-key1%22%3A%22val1%22%2C%22gam-key2%22%3A%22val2%22%7D", expectedParams: Params{ Account: "anyAccount", CanonicalURL: "anyCurl", @@ -44,6 +49,7 @@ func TestParseParams(t *testing.T) { {W: 10, H: 11}, {W: 12, H: 13}, }, }, + Targeting: `{"gam-key1":"val1","gam-key2":"val2"}`, }, }, { @@ -92,6 +98,397 @@ func TestParseParams(t *testing.T) { } } +func TestParseIntPtr(t *testing.T) { + var boolZero uint64 = 0 + var boolOne uint64 = 1 + + type testResults struct { + intPtr *uint64 + err bool + } + + testCases := []struct { + desc string + input string + expected testResults + }{ + { + desc: "Input is an empty string: expect nil pointer and error", + input: "", + expected: testResults{ + intPtr: nil, + err: true, + }, + }, + { + desc: "Input is negative number: expect a nil pointer and error", + input: "-1", + expected: testResults{ + intPtr: nil, + err: true, + }, + }, + { + desc: "Input is a string depicting a zero value: expect a reference pointing to zero value, no error", + input: "0", + expected: testResults{ + intPtr: &boolZero, + err: false, + }, + }, + { + desc: "Input is a string depicting a value of 1: expect a reference pointing to the value of 1 and no error", + input: "1", + expected: testResults{ + intPtr: &boolOne, + err: false, + }, + }, + } + for _, tc := range testCases { + resultingIntPtr, resultingErr := parseIntPtr(tc.input) + + assert.Equal(t, tc.expected.intPtr, resultingIntPtr, tc.desc) + if tc.expected.err { + assert.Error(t, resultingErr, tc.desc) + } else { + assert.NoError(t, resultingErr, tc.desc) + } + } +} + +func TestParseBoolPtr(t *testing.T) { + boolTrue := true + boolFalse := false + + type testResults struct { + boolPtr *bool + err bool + } + + testCases := []struct { + desc string + input string + expected testResults + }{ + { + desc: "Input is an empty string: expect nil pointer and error", + input: "", + expected: testResults{ + boolPtr: nil, + err: true, + }, + }, + { + desc: "Input is neither true nor false: expect a nil pointer and error", + input: "other", + expected: testResults{ + boolPtr: nil, + err: true, + }, + }, + { + desc: "Input is the word 'false', expect a reference pointing to false value", + input: "false", + expected: testResults{ + boolPtr: &boolFalse, + err: false, + }, + }, + { + desc: "Input is the word 'true', expect a reference pointing to true value", + input: "true", + expected: testResults{ + boolPtr: &boolTrue, + err: false, + }, + }, + } + for _, tc := range testCases { + resultingBoolPtr, resultingErr := parseBoolPtr(tc.input) + + assert.Equal(t, tc.expected.boolPtr, resultingBoolPtr, tc.desc) + if tc.expected.err { + assert.Error(t, resultingErr, tc.desc) + } else { + assert.NoError(t, resultingErr, tc.desc) + } + } +} + +// TestPrivacyReader asserts the ReadPolicy scenarios +func TestPrivacyReader(t *testing.T) { + int8Zero := int8(0) + int8One := int8(1) + boolTrue := true + boolFalse := false + + type testInput struct { + ampParams Params + } + type expectedResults struct { + policyWriter privacy.PolicyWriter + warning error + } + type testCase struct { + desc string + in testInput + expected expectedResults + } + + testGroups := []struct { + groupDesc string + tests []testCase + }{ + { + groupDesc: "No consent string", + tests: []testCase{ + { + desc: "Params comes with an empty consent string, expect nil policy writer. No warning returned", + expected: expectedResults{policyWriter: privacy.NilPolicyWriter{}, warning: nil}, + }, + }, + }, + { + groupDesc: "TCF1", + tests: []testCase{ + { + desc: "Consent type TCF1: expect nil policy writer. Warning is returned", + in: testInput{ + ampParams: Params{Consent: "VALID_TCF1_CONSENT", ConsentType: ConsentTCF1}, + }, + expected: expectedResults{ + policyWriter: privacy.NilPolicyWriter{}, + warning: &errortypes.Warning{Message: "TCF1 consent is deprecated and no longer supported.", WarningCode: errortypes.InvalidPrivacyConsentWarningCode}, + }, + }, + }, + }, + { + groupDesc: "ConsentNone. In order to be backwards compatible, we'll guess what consent string this is", + tests: []testCase{ + { + desc: "No consent type was specified and invalid consent string provided: expect nil policy writer and a warning", + in: testInput{ + ampParams: Params{Consent: "NOT_CCPA_NOR_GDPR_TCF2"}, + }, + expected: expectedResults{ + policyWriter: privacy.NilPolicyWriter{}, + warning: &errortypes.Warning{Message: "Consent string 'NOT_CCPA_NOR_GDPR_TCF2' is not recognized as one of the supported formats CCPA or TCF2.", WarningCode: errortypes.InvalidPrivacyConsentWarningCode}, + }, + }, + { + desc: "No consent type specified but query params come with a valid CCPA consent string: expect a CCPA consent writer and no error nor warning", + in: testInput{ + ampParams: Params{Consent: "1YYY"}, + }, + expected: expectedResults{ + policyWriter: ccpa.ConsentWriter{"1YYY"}, + warning: nil, + }, + }, + { + desc: "No consent type, valid CCPA consent string and gdpr_applies set to true: expect a CCPA consent writer and a warning", + in: testInput{ + ampParams: Params{Consent: "1YYY", GdprApplies: &boolTrue}, + }, + expected: expectedResults{ + policyWriter: ccpa.ConsentWriter{"1YYY"}, + warning: &errortypes.Warning{Message: "AMP request gdpr_applies value was ignored because provided consent string is a CCPA consent string", WarningCode: errortypes.InvalidPrivacyConsentWarningCode}, + }, + }, + { + desc: "No consent type, valid GDPR consent string and gdpr_applies not set: expect a GDPR consent writer and no error nor warning", + in: testInput{ + ampParams: Params{Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA"}, + }, + expected: expectedResults{ + policyWriter: gdpr.ConsentWriter{"CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", &int8One}, + warning: nil, + }, + }, + }, + }, + { + groupDesc: "Unrecognized consent type. In order to be backwards compatible, we'll guess what consent string type it is", + tests: []testCase{ + { + desc: "Unrecognized consent type was specified and invalid consent string provided: expect nil policy writer and a warning", + in: testInput{ + ampParams: Params{ConsentType: 101, Consent: "NOT_CCPA_NOR_GDPR_TCF2"}, + }, + expected: expectedResults{ + policyWriter: privacy.NilPolicyWriter{}, + warning: &errortypes.Warning{Message: "Consent string 'NOT_CCPA_NOR_GDPR_TCF2' is not recognized as one of the supported formats CCPA or TCF2.", WarningCode: errortypes.InvalidPrivacyConsentWarningCode}, + }, + }, + { + desc: "Unrecognized consent type specified but query params come with a valid CCPA consent string: expect a CCPA consent writer and no error nor warning", + in: testInput{ + ampParams: Params{ConsentType: 101, Consent: "1YYY"}, + }, + expected: expectedResults{ + policyWriter: ccpa.ConsentWriter{"1YYY"}, + warning: nil, + }, + }, + { + desc: "Unrecognized consent type, valid CCPA consent string and gdpr_applies set to true: expect a CCPA consent writer and a warning", + in: testInput{ + ampParams: Params{ConsentType: 101, Consent: "1YYY", GdprApplies: &boolTrue}, + }, + expected: expectedResults{ + policyWriter: ccpa.ConsentWriter{"1YYY"}, + warning: &errortypes.Warning{Message: "AMP request gdpr_applies value was ignored because provided consent string is a CCPA consent string", WarningCode: errortypes.InvalidPrivacyConsentWarningCode}, + }, + }, + { + desc: "Unrecognized consent type, valid TCF2 consent string and gdpr_applies not set: expect GDPR consent writer and no error nor warning", + in: testInput{ + ampParams: Params{ConsentType: 101, Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA"}, + }, + expected: expectedResults{ + policyWriter: gdpr.ConsentWriter{"CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", &int8One}, + warning: nil, + }, + }, + }, + }, + { + groupDesc: "consent type TCF2. Return a valid GDPR consent writer in all scenarios.", + tests: []testCase{ + { + desc: "GDPR consent string is invalid, but consent type is TCF2: return a valid GDPR writer and warn about the GDPR string being invalid", + in: testInput{ + ampParams: Params{Consent: "INVALID_GDPR", ConsentType: ConsentTCF2, GdprApplies: nil}, + }, + expected: expectedResults{ + policyWriter: gdpr.ConsentWriter{"INVALID_GDPR", &int8One}, + warning: &errortypes.Warning{Message: "Consent string 'INVALID_GDPR' is not a valid TCF2 consent string.", WarningCode: errortypes.InvalidPrivacyConsentWarningCode}, + }, + }, + { + desc: "GDPR consent string is invalid, consent type is TCF2, gdpr_applies is set to true: return a valid GDPR writer and warn about the GDPR string being invalid", + in: testInput{ + ampParams: Params{Consent: "INVALID_GDPR", ConsentType: ConsentTCF2, GdprApplies: &boolFalse}, + }, + expected: expectedResults{ + policyWriter: gdpr.ConsentWriter{"INVALID_GDPR", &int8Zero}, + warning: &errortypes.Warning{Message: "Consent string 'INVALID_GDPR' is not a valid TCF2 consent string.", WarningCode: errortypes.InvalidPrivacyConsentWarningCode}, + }, + }, + { + desc: "Valid GDPR consent string, gdpr_applies is set to false, return a valid GDPR writer, no warning", + in: testInput{ + ampParams: Params{Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", ConsentType: ConsentTCF2, GdprApplies: &boolFalse}, + }, + expected: expectedResults{ + policyWriter: gdpr.ConsentWriter{"CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", &int8Zero}, + warning: nil, + }, + }, + { + desc: "Valid GDPR consent string, gdpr_applies is set to true, return a valid GDPR writer and no warning", + in: testInput{ + ampParams: Params{Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", ConsentType: ConsentTCF2, GdprApplies: &boolTrue}, + }, + expected: expectedResults{ + policyWriter: gdpr.ConsentWriter{"CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", &int8One}, + warning: nil, + }, + }, + { + desc: "Valid GDPR consent string, return a valid GDPR writer and no warning", + in: testInput{ + ampParams: Params{Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", ConsentType: ConsentTCF2}, + }, + expected: expectedResults{ + policyWriter: gdpr.ConsentWriter{"CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", &int8One}, + warning: nil, + }, + }, + }, + }, + { + groupDesc: "consent type CCPA. Return a valid CCPA consent writer in all scenarios.", + tests: []testCase{ + { + desc: "CCPA consent string is invalid: return a valid writer a warning about the string being invalid", + in: testInput{ + ampParams: Params{Consent: "XXXX", ConsentType: ConsentUSPrivacy}, + }, + expected: expectedResults{ + policyWriter: ccpa.ConsentWriter{"XXXX"}, + warning: &errortypes.Warning{Message: "Consent string 'XXXX' is not a valid CCPA consent string.", WarningCode: errortypes.InvalidPrivacyConsentWarningCode}, + }, + }, + { + desc: "Valid CCPA consent string, gdpr_applies is set to true: return a valid GDPR writer and warn about the gdpr_applies value.", + in: testInput{ + ampParams: Params{Consent: "1YYY", ConsentType: ConsentUSPrivacy, GdprApplies: &boolTrue}, + }, + expected: expectedResults{ + policyWriter: ccpa.ConsentWriter{"1YYY"}, + warning: &errortypes.Warning{Message: "AMP request gdpr_applies value was ignored because provided consent string is a CCPA consent string", WarningCode: errortypes.InvalidPrivacyConsentWarningCode}, + }, + }, + { + desc: "Valid CCPA consent string, return a valid GDPR writer and no warning", + in: testInput{ + ampParams: Params{Consent: "1YYY", ConsentType: ConsentUSPrivacy}, + }, + expected: expectedResults{ + policyWriter: ccpa.ConsentWriter{"1YYY"}, + warning: nil, + }, + }, + }, + }, + } + for _, group := range testGroups { + for _, tc := range group.tests { + actualPolicyWriter, actualErr := ReadPolicy(tc.in.ampParams, true) + + assert.Equal(t, tc.expected.policyWriter, actualPolicyWriter, tc.desc) + assert.Equal(t, tc.expected.warning, actualErr, tc.desc) + } + } +} + +func TestBuildGdprTCF2ConsentWriter(t *testing.T) { + int8Zero := int8(0) + int8One := int8(1) + boolTrue := true + boolFalse := false + consentString := "CONSENT" + + testCases := []struct { + desc string + inParams Params + expectedWriter gdpr.ConsentWriter + }{ + { + desc: "gdpr_applies not set", + inParams: Params{Consent: consentString}, + expectedWriter: gdpr.ConsentWriter{consentString, &int8One}, + }, + { + desc: "gdpr_applies set to false", + inParams: Params{Consent: consentString, GdprApplies: &boolFalse}, + expectedWriter: gdpr.ConsentWriter{consentString, &int8Zero}, + }, + { + desc: "gdpr_applies set to true", + inParams: Params{Consent: consentString, GdprApplies: &boolTrue}, + expectedWriter: gdpr.ConsentWriter{consentString, &int8One}, + }, + } + for _, tc := range testCases { + actualPolicyWriter := buildGdprTCF2ConsentWriter(tc.inParams) + assert.Equal(t, tc.expectedWriter, actualPolicyWriter, tc.desc) + } +} + func TestParseMultisize(t *testing.T) { testCases := []struct { description string @@ -166,3 +563,33 @@ func TestParseMultisize(t *testing.T) { assert.ElementsMatch(t, test.expectedFormats, result, test.description) } } + +func TestParseGdprApplies(t *testing.T) { + gdprAppliesFalse := false + gdprAppliesTrue := true + + testCases := []struct { + desc string + inGdprApplies *bool + expectRegsExtGdpr int8 + }{ + { + desc: "gdprApplies was not set and defaulted to nil, expect 0", + inGdprApplies: nil, + expectRegsExtGdpr: int8(0), + }, + { + desc: "gdprApplies isn't nil and is set to false, expect a value of 0", + inGdprApplies: &gdprAppliesFalse, + expectRegsExtGdpr: int8(0), + }, + { + desc: "gdprApplies isn't nil and is set to true, expect a value of 1", + inGdprApplies: &gdprAppliesTrue, + expectRegsExtGdpr: int8(1), + }, + } + for _, tc := range testCases { + assert.Equal(t, tc.expectRegsExtGdpr, parseGdprApplies(tc.inGdprApplies), tc.desc) + } +} diff --git a/analytics/config/config.go b/analytics/config/config.go index fa63cb5a1e4..557fec361dc 100644 --- a/analytics/config/config.go +++ b/analytics/config/config.go @@ -1,6 +1,7 @@ package config import ( + "github.com/benbjohnson/clock" "github.com/golang/glog" "github.com/prebid/prebid-server/analytics" "github.com/prebid/prebid-server/analytics/clients" @@ -9,7 +10,7 @@ import ( "github.com/prebid/prebid-server/config" ) -//Modules that need to be logged to need to be initialized here +// Modules that need to be logged to need to be initialized here func NewPBSAnalytics(analytics *config.Analytics) analytics.PBSAnalyticsModule { modules := make(enabledAnalytics, 0) if len(analytics.File.Filename) > 0 { @@ -19,15 +20,17 @@ func NewPBSAnalytics(analytics *config.Analytics) analytics.PBSAnalyticsModule { glog.Fatalf("Could not initialize FileLogger for file %v :%v", analytics.File.Filename, err) } } + if analytics.Pubstack.Enabled { - pubstackModule, err := pubstack.NewPubstackModule( + pubstackModule, err := pubstack.NewModule( clients.GetDefaultHttpInstance(), analytics.Pubstack.ScopeId, analytics.Pubstack.IntakeUrl, analytics.Pubstack.ConfRefresh, analytics.Pubstack.Buffers.EventCount, analytics.Pubstack.Buffers.BufferSize, - analytics.Pubstack.Buffers.Timeout) + analytics.Pubstack.Buffers.Timeout, + clock.New()) if err == nil { modules = append(modules, pubstackModule) } else { @@ -37,7 +40,7 @@ func NewPBSAnalytics(analytics *config.Analytics) analytics.PBSAnalyticsModule { return modules } -//Collection of all the correctly configured analytics modules - implements the PBSAnalyticsModule interface +// Collection of all the correctly configured analytics modules - implements the PBSAnalyticsModule interface type enabledAnalytics []analytics.PBSAnalyticsModule func (ea enabledAnalytics) LogAuctionObject(ao *analytics.AuctionObject) { diff --git a/analytics/config/config_test.go b/analytics/config/config_test.go index 310dbe1a481..76f7847a2b2 100644 --- a/analytics/config/config_test.go +++ b/analytics/config/config_test.go @@ -5,7 +5,7 @@ import ( "os" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/stretchr/testify/assert" "github.com/prebid/prebid-server/analytics" diff --git a/analytics/core.go b/analytics/core.go index c15ae201ac0..e2d5a071ac9 100644 --- a/analytics/core.go +++ b/analytics/core.go @@ -3,7 +3,7 @@ package analytics import ( "time" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" ) @@ -25,7 +25,7 @@ type PBSAnalyticsModule interface { LogNotificationEventObject(*NotificationEvent) } -//Loggable object of a transaction at /openrtb2/auction endpoint +// Loggable object of a transaction at /openrtb2/auction endpoint type AuctionObject struct { Status int Errors []error @@ -35,7 +35,7 @@ type AuctionObject struct { StartTime time.Time } -//Loggable object of a transaction at /openrtb2/amp endpoint +// Loggable object of a transaction at /openrtb2/amp endpoint type AmpObject struct { Status int Errors []error @@ -46,7 +46,7 @@ type AmpObject struct { StartTime time.Time } -//Loggable object of a transaction at /openrtb2/video endpoint +// Loggable object of a transaction at /openrtb2/video endpoint type VideoObject struct { Status int Errors []error @@ -57,7 +57,7 @@ type VideoObject struct { StartTime time.Time } -//Loggable object of a transaction at /setuid +// Loggable object of a transaction at /setuid type SetUIDObject struct { Status int Bidder string @@ -66,7 +66,7 @@ type SetUIDObject struct { Success bool } -//Loggable object of a transaction at /cookie_sync +// Loggable object of a transaction at /cookie_sync type CookieSyncObject struct { Status int Errors []error diff --git a/analytics/filesystem/file_module.go b/analytics/filesystem/file_module.go index 43853382354..d6a03ea2d8a 100644 --- a/analytics/filesystem/file_module.go +++ b/analytics/filesystem/file_module.go @@ -20,12 +20,12 @@ const ( NOTIFICATION_EVENT RequestType = "/event" ) -//Module that can perform transactional logging +// Module that can perform transactional logging type FileLogger struct { Logger *glog.Logger } -//Writes AuctionObject to file +// Writes AuctionObject to file func (f *FileLogger) LogAuctionObject(ao *analytics.AuctionObject) { //Code to parse the object and log in a way required var b bytes.Buffer @@ -34,7 +34,7 @@ func (f *FileLogger) LogAuctionObject(ao *analytics.AuctionObject) { f.Logger.Flush() } -//Writes VideoObject to file +// Writes VideoObject to file func (f *FileLogger) LogVideoObject(vo *analytics.VideoObject) { //Code to parse the object and log in a way required var b bytes.Buffer @@ -43,7 +43,7 @@ func (f *FileLogger) LogVideoObject(vo *analytics.VideoObject) { f.Logger.Flush() } -//Logs SetUIDObject to file +// Logs SetUIDObject to file func (f *FileLogger) LogSetUIDObject(so *analytics.SetUIDObject) { //Code to parse the object and log in a way required var b bytes.Buffer @@ -52,7 +52,7 @@ func (f *FileLogger) LogSetUIDObject(so *analytics.SetUIDObject) { f.Logger.Flush() } -//Logs CookieSyncObject to file +// Logs CookieSyncObject to file func (f *FileLogger) LogCookieSyncObject(cso *analytics.CookieSyncObject) { //Code to parse the object and log in a way required var b bytes.Buffer @@ -61,7 +61,7 @@ func (f *FileLogger) LogCookieSyncObject(cso *analytics.CookieSyncObject) { f.Logger.Flush() } -//Logs AmpObject to file +// Logs AmpObject to file func (f *FileLogger) LogAmpObject(ao *analytics.AmpObject) { if ao == nil { return @@ -73,7 +73,7 @@ func (f *FileLogger) LogAmpObject(ao *analytics.AmpObject) { f.Logger.Flush() } -//Logs NotificationEvent to file +// Logs NotificationEvent to file func (f *FileLogger) LogNotificationEventObject(ne *analytics.NotificationEvent) { if ne == nil { return @@ -85,7 +85,7 @@ func (f *FileLogger) LogNotificationEventObject(ne *analytics.NotificationEvent) f.Logger.Flush() } -//Method to initialize the analytic module +// Method to initialize the analytic module func NewFileLogger(filename string) (analytics.PBSAnalyticsModule, error) { options := glog.LogOptions{ File: filename, diff --git a/analytics/filesystem/file_module_test.go b/analytics/filesystem/file_module_test.go index 74d7c7d50f5..667712c3b6d 100644 --- a/analytics/filesystem/file_module_test.go +++ b/analytics/filesystem/file_module_test.go @@ -9,7 +9,7 @@ import ( "github.com/prebid/prebid-server/analytics" "github.com/prebid/prebid-server/config" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" ) const TEST_DIR string = "testFiles" diff --git a/analytics/pubstack/config.go b/analytics/pubstack/config.go index 472acf68ead..3f604af58e9 100644 --- a/analytics/pubstack/config.go +++ b/analytics/pubstack/config.go @@ -2,14 +2,14 @@ package pubstack import ( "encoding/json" - "github.com/docker/go-units" "net/http" "net/url" "time" + + "github.com/docker/go-units" ) func fetchConfig(client *http.Client, endpoint *url.URL) (*Configuration, error) { - res, err := client.Get(endpoint.String()) if err != nil { return nil, err @@ -49,3 +49,24 @@ func (a *Configuration) isSameAs(b *Configuration) bool { } return sameFeature && sameEndpoint && sameScopeID } + +func (a *Configuration) clone() *Configuration { + c := &Configuration{ + ScopeID: a.ScopeID, + Endpoint: a.Endpoint, + Features: make(map[string]bool, len(a.Features)), + } + + for k, v := range a.Features { + c.Features[k] = v + } + + return c +} + +func (a *Configuration) disableAllFeatures() *Configuration { + for k := range a.Features { + a.Features[k] = false + } + return a +} diff --git a/analytics/pubstack/config_test.go b/analytics/pubstack/config_test.go index bb6fd0bddbb..1b7637291dc 100644 --- a/analytics/pubstack/config_test.go +++ b/analytics/pubstack/config_test.go @@ -1,11 +1,12 @@ package pubstack import ( - "github.com/stretchr/testify/assert" "net/http" "net/http/httptest" "net/url" "testing" + + "github.com/stretchr/testify/assert" ) func TestFetchConfig(t *testing.T) { @@ -24,7 +25,6 @@ func TestFetchConfig(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { defer req.Body.Close() res.Write([]byte(configResponse)) - res.WriteHeader(200) })) defer server.Close() @@ -32,13 +32,13 @@ func TestFetchConfig(t *testing.T) { endpoint, _ := url.Parse(server.URL) cfg, _ := fetchConfig(server.Client(), endpoint) - assert.Equal(t, cfg.ScopeID, "scopeId") - assert.Equal(t, cfg.Endpoint, "https://pubstack.io") - assert.Equal(t, cfg.Features[auction], true) - assert.Equal(t, cfg.Features[cookieSync], true) - assert.Equal(t, cfg.Features[amp], true) - assert.Equal(t, cfg.Features[setUID], false) - assert.Equal(t, cfg.Features[video], false) + assert.Equal(t, "scopeId", cfg.ScopeID) + assert.Equal(t, "https://pubstack.io", cfg.Endpoint) + assert.Equal(t, true, cfg.Features[auction]) + assert.Equal(t, true, cfg.Features[cookieSync]) + assert.Equal(t, true, cfg.Features[amp]) + assert.Equal(t, false, cfg.Features[setUID]) + assert.Equal(t, false, cfg.Features[video]) } func TestFetchConfig_Error(t *testing.T) { @@ -49,7 +49,6 @@ func TestFetchConfig_Error(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { defer req.Body.Close() res.Write([]byte(configResponse)) - res.WriteHeader(200) })) defer server.Close() @@ -58,7 +57,7 @@ func TestFetchConfig_Error(t *testing.T) { cfg, err := fetchConfig(server.Client(), endpoint) assert.Nil(t, cfg) - assert.NotNil(t, err) + assert.Error(t, err) } func TestIsSameAs(t *testing.T) { @@ -98,5 +97,54 @@ func TestIsSameAs(t *testing.T) { assert.True(t, a.isSameAs(b)) b.Features["auction"] = false assert.False(t, a.isSameAs(b)) +} + +func TestClone(t *testing.T) { + config := &Configuration{ + ScopeID: "scopeId", + Endpoint: "endpoint", + Features: map[string]bool{ + "auction": true, + "cookiesync": true, + "amp": true, + "setuid": false, + "video": false, + }, + } + + clone := config.clone() + + assert.Equal(t, config, clone) + assert.NotSame(t, config, clone) +} + +func TestDisableAllFeatures(t *testing.T) { + config := &Configuration{ + ScopeID: "scopeId", + Endpoint: "endpoint", + Features: map[string]bool{ + "auction": true, + "cookiesync": true, + "amp": true, + "setuid": false, + "video": false, + }, + } + + expected := &Configuration{ + ScopeID: "scopeId", + Endpoint: "endpoint", + Features: map[string]bool{ + "auction": false, + "cookiesync": false, + "amp": false, + "setuid": false, + "video": false, + }, + } + + disabled := config.disableAllFeatures() + assert.Equal(t, expected, disabled) + assert.Same(t, config, disabled) } diff --git a/analytics/pubstack/configupdate.go b/analytics/pubstack/configupdate.go new file mode 100644 index 00000000000..622161a04f2 --- /dev/null +++ b/analytics/pubstack/configupdate.go @@ -0,0 +1,61 @@ +package pubstack + +import ( + "fmt" + "net/http" + "net/url" + "time" + + "github.com/prebid/prebid-server/util/task" +) + +// ConfigUpdateTask publishes configurations until the stop channel is signaled. +type ConfigUpdateTask interface { + Start(stop <-chan struct{}) <-chan *Configuration +} + +// ConfigUpdateHttpTask polls an HTTP endpoint on a specified interval and publishes configurations until +// the stop channel is signaled. +type ConfigUpdateHttpTask struct { + task *task.TickerTask + configChan chan *Configuration +} + +func NewConfigUpdateHttpTask(httpClient *http.Client, scope, endpoint, refreshInterval string) (*ConfigUpdateHttpTask, error) { + refreshDuration, err := time.ParseDuration(refreshInterval) + if err != nil { + return nil, fmt.Errorf("fail to parse the module args, arg=analytics.pubstack.configuration_refresh_delay: %v", err) + } + + endpointUrl, err := url.Parse(endpoint + "/bootstrap?scopeId=" + scope) + if err != nil { + return nil, err + } + + configChan := make(chan *Configuration) + + tr := task.NewTickerTaskFromFunc(refreshDuration, func() error { + config, err := fetchConfig(httpClient, endpointUrl) + if err != nil { + return fmt.Errorf("[pubstack] Fail to fetch remote configuration: %v", err) + } + configChan <- config + return nil + }) + + return &ConfigUpdateHttpTask{ + task: tr, + configChan: configChan, + }, nil +} + +func (t *ConfigUpdateHttpTask) Start(stop <-chan struct{}) <-chan *Configuration { + go t.task.Start() + + go func() { + <-stop + t.task.Stop() + }() + + return t.configChan +} diff --git a/analytics/pubstack/configupdate_test.go b/analytics/pubstack/configupdate_test.go new file mode 100644 index 00000000000..fdc76425d35 --- /dev/null +++ b/analytics/pubstack/configupdate_test.go @@ -0,0 +1,105 @@ +package pubstack + +import ( + "net/http" + "net/http/httptest" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewConfigUpdateHttpTask(t *testing.T) { + // configure test config endpoint + var isFirstQuery bool = true + var isFirstQueryMutex sync.Mutex + mux := http.NewServeMux() + mux.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) { + isFirstQueryMutex.Lock() + defer isFirstQueryMutex.Unlock() + + defer req.Body.Close() + + if isFirstQuery { + res.Write([]byte(`{ "scopeId": "scope1", "endpoint": "https://pubstack.io", "features": { "auction": true, "cookiesync": true }}`)) + } else { + res.Write([]byte(`{ "scopeId": "scope2", "endpoint": "https://pubstack.io", "features": { "auction": false, "cookiesync": false }}`)) + } + + isFirstQuery = false + }) + server := httptest.NewServer(mux) + client := server.Client() + + // create task + task, err := NewConfigUpdateHttpTask(client, "scope", server.URL, "5ms") + require.NoError(t, err, "create config task") + + // start task + stopChan := make(chan struct{}) + configChan := task.Start(stopChan) + + // read initial config + expectedInitialConfig := &Configuration{ScopeID: "scope1", Endpoint: "https://pubstack.io", Features: map[string]bool{"auction": true, "cookiesync": true}} + assertConfigChanOne(t, configChan, expectedInitialConfig, "initial config") + + // read updated config + expectedUpdatedConfig := &Configuration{ScopeID: "scope2", Endpoint: "https://pubstack.io", Features: map[string]bool{"auction": false, "cookiesync": false}} + assertConfigChanOne(t, configChan, expectedUpdatedConfig, "updated config") + + // stop task + close(stopChan) + + // no further updates + assertConfigChanNone(t, configChan) +} + +func TestNewConfigUpdateHttpTaskErrors(t *testing.T) { + tests := []struct { + description string + givenEndpoint string + givenRefreshInterval string + expectedError string + }{ + { + description: "refresh interval invalid", + givenEndpoint: "http://valid.com", + givenRefreshInterval: "invalid", + expectedError: `fail to parse the module args, arg=analytics.pubstack.configuration_refresh_delay: time: invalid duration "invalid"`, + }, + { + description: "endpoint invalid", + givenEndpoint: "://invalid.com", + givenRefreshInterval: "10ms", + expectedError: `parse "://invalid.com/bootstrap?scopeId=anyScope": missing protocol scheme`, + }, + } + + for _, test := range tests { + task, err := NewConfigUpdateHttpTask(nil, "anyScope", test.givenEndpoint, test.givenRefreshInterval) + assert.Nil(t, task, test.description) + assert.EqualError(t, err, test.expectedError, test.description) + } +} + +func assertConfigChanNone(t *testing.T, c <-chan *Configuration) bool { + t.Helper() + select { + case <-c: + return assert.Fail(t, "received a unexpected configuration channel event") + case <-time.After(200 * time.Millisecond): + return true + } +} + +func assertConfigChanOne(t *testing.T, c <-chan *Configuration, expectedConfig *Configuration, msgAndArgs ...interface{}) bool { + t.Helper() + select { + case v := <-c: + return assert.Equal(t, expectedConfig, v, msgAndArgs...) + case <-time.After(200 * time.Millisecond): + return assert.Fail(t, "Should receive an event, but did NOT", msgAndArgs...) + } +} diff --git a/analytics/pubstack/eventchannel/eventchannel.go b/analytics/pubstack/eventchannel/eventchannel.go index d9c2bc4117c..022cc5bcf1b 100644 --- a/analytics/pubstack/eventchannel/eventchannel.go +++ b/analytics/pubstack/eventchannel/eventchannel.go @@ -6,6 +6,7 @@ import ( "sync" "time" + "github.com/benbjohnson/clock" "github.com/golang/glog" ) @@ -13,11 +14,13 @@ type Metrics struct { bufferSize int64 eventCount int64 } + type Limit struct { maxByteSize int64 maxEventCount int64 maxTime time.Duration } + type EventChannel struct { gz *gzip.Writer buff *bytes.Buffer @@ -28,9 +31,10 @@ type EventChannel struct { muxGzBuffer sync.RWMutex send Sender limit Limit + clock clock.Clock } -func NewEventChannel(sender Sender, maxByteSize, maxEventCount int64, maxTime time.Duration) *EventChannel { +func NewEventChannel(sender Sender, clock clock.Clock, maxByteSize, maxEventCount int64, maxTime time.Duration) *EventChannel { b := &bytes.Buffer{} gzw := gzip.NewWriter(b) @@ -42,6 +46,7 @@ func NewEventChannel(sender Sender, maxByteSize, maxEventCount int64, maxTime ti metrics: Metrics{}, send: sender, limit: Limit{maxByteSize, maxEventCount, maxTime}, + clock: clock, } go c.start() return &c @@ -116,19 +121,21 @@ func (c *EventChannel) flush() { } func (c *EventChannel) start() { - ticker := time.NewTicker(c.limit.maxTime) + ticker := c.clock.Ticker(c.limit.maxTime) for { select { case <-c.endCh: c.flush() return + // event is received case event := <-c.ch: c.buffer(event) if c.isBufferFull() { c.flush() } + // time between 2 flushes has passed case <-ticker.C: c.flush() diff --git a/analytics/pubstack/eventchannel/eventchannel_test.go b/analytics/pubstack/eventchannel/eventchannel_test.go index f450fb61fe1..90750fd3540 100644 --- a/analytics/pubstack/eventchannel/eventchannel_test.go +++ b/analytics/pubstack/eventchannel/eventchannel_test.go @@ -3,179 +3,145 @@ package eventchannel import ( "bytes" "compress/gzip" - "io/ioutil" + "io" + "math" "sync" "testing" "time" + "github.com/benbjohnson/clock" "github.com/stretchr/testify/assert" ) -var maxByteSize = int64(15) -var maxEventCount = int64(3) +var largeBufferSize = int64(math.MaxInt64) +var largeEventCount = int64(math.MaxInt64) var maxTime = 2 * time.Hour -func readGz(encoded bytes.Buffer) string { - gr, _ := gzip.NewReader(bytes.NewBuffer(encoded.Bytes())) +func readGz(encoded []byte) string { + gr, _ := gzip.NewReader(bytes.NewReader(encoded)) defer gr.Close() - decoded, _ := ioutil.ReadAll(gr) + decoded, _ := io.ReadAll(gr) return string(decoded) } -func newSender(data *[]byte) Sender { +func newSender(dataSent chan []byte) Sender { mux := &sync.Mutex{} return func(payload []byte) error { mux.Lock() defer mux.Unlock() - event := bytes.Buffer{} - event.Write(payload) - *data = append(*data, readGz(event)...) + dataSent <- payload return nil } } -func TestEventChannel_isBufferFull(t *testing.T) { +func readChanOrTimeout(t *testing.T, c <-chan []byte, msgAndArgs ...interface{}) ([]byte, bool) { + t.Helper() + select { + case actual := <-c: + return actual, false + case <-time.After(200 * time.Millisecond): + return nil, assert.Fail(t, "Should receive an event, but did NOT", msgAndArgs...) + } +} - send := func(_ []byte) error { return nil } +func TestEventChannelIsBufferFull(t *testing.T) { + send := func([]byte) error { return nil } + clockMock := clock.NewMock() - eventChannel := NewEventChannel(send, maxByteSize, maxEventCount, maxTime) + maxBufferSize := int64(15) + maxEventCount := int64(3) + + eventChannel := NewEventChannel(send, clockMock, maxBufferSize, maxEventCount, maxTime) defer eventChannel.Close() eventChannel.buffer([]byte("one")) eventChannel.buffer([]byte("two")) - assert.Equal(t, eventChannel.isBufferFull(), false) + assert.False(t, eventChannel.isBufferFull()) // not yet full by either max buffer size or max event count eventChannel.buffer([]byte("three")) - assert.Equal(t, eventChannel.isBufferFull(), true) + assert.True(t, eventChannel.isBufferFull()) // full by event count (3) eventChannel.reset() - assert.Equal(t, eventChannel.isBufferFull(), false) - - eventChannel.buffer([]byte("big-event-abcdefghijklmnopqrstuvwxyz")) + assert.False(t, eventChannel.isBufferFull()) // was just reset, should not be full - assert.Equal(t, eventChannel.isBufferFull(), true) + eventChannel.buffer([]byte("larger-than-15-characters")) + assert.True(t, eventChannel.isBufferFull()) // full by max buffer size } -func TestEventChannel_reset(t *testing.T) { - send := func(_ []byte) error { return nil } +func TestEventChannelReset(t *testing.T) { + send := func([]byte) error { return nil } + clockMock := clock.NewMock() - eventChannel := NewEventChannel(send, maxByteSize, maxEventCount, maxTime) + eventChannel := NewEventChannel(send, clockMock, largeBufferSize, largeEventCount, maxTime) defer eventChannel.Close() - assert.Equal(t, eventChannel.metrics.eventCount, int64(0)) - assert.Equal(t, eventChannel.metrics.bufferSize, int64(0)) + assert.Zero(t, eventChannel.metrics.eventCount) + assert.Zero(t, eventChannel.metrics.bufferSize) eventChannel.buffer([]byte("one")) - eventChannel.buffer([]byte("two")) - assert.NotEqual(t, eventChannel.metrics.eventCount, int64(0)) - assert.NotEqual(t, eventChannel.metrics.bufferSize, int64(0)) + assert.NotZero(t, eventChannel.metrics.eventCount) + assert.NotZero(t, eventChannel.metrics.bufferSize) eventChannel.reset() - assert.Equal(t, eventChannel.buff.Len(), 0) - assert.Equal(t, eventChannel.metrics.eventCount, int64(0)) - assert.Equal(t, eventChannel.metrics.bufferSize, int64(0)) + assert.Zero(t, eventChannel.buff.Len()) + assert.Zero(t, eventChannel.metrics.eventCount) + assert.Zero(t, eventChannel.metrics.bufferSize) } -func TestEventChannel_flush(t *testing.T) { - data := make([]byte, 0) - send := newSender(&data) +func TestEventChannelFlush(t *testing.T) { + dataSent := make(chan []byte) + send := newSender(dataSent) + clockMock := clock.NewMock() - eventChannel := NewEventChannel(send, maxByteSize, maxEventCount, maxTime) + eventChannel := NewEventChannel(send, clockMock, largeBufferSize, largeEventCount, maxTime) defer eventChannel.Close() eventChannel.buffer([]byte("one")) eventChannel.buffer([]byte("two")) eventChannel.buffer([]byte("three")) eventChannel.flush() - time.Sleep(10 * time.Millisecond) - assert.Equal(t, string(data), "onetwothree") + data, _ := readChanOrTimeout(t, dataSent) + assert.Equal(t, "onetwothree", readGz(data)) } -func TestEventChannel_close(t *testing.T) { - data := make([]byte, 0) - send := newSender(&data) +func TestEventChannelClose(t *testing.T) { + dataSent := make(chan []byte) + send := newSender(dataSent) + clockMock := clock.NewMock() - eventChannel := NewEventChannel(send, 15000, 15000, 2*time.Hour) + eventChannel := NewEventChannel(send, clockMock, largeBufferSize, largeEventCount, maxTime) eventChannel.buffer([]byte("one")) eventChannel.buffer([]byte("two")) eventChannel.buffer([]byte("three")) eventChannel.Close() - time.Sleep(10 * time.Millisecond) - - assert.Equal(t, string(data), "onetwothree") + data, _ := readChanOrTimeout(t, dataSent) + assert.Equal(t, "onetwothree", readGz(data)) } -func TestEventChannel_Push(t *testing.T) { - data := make([]byte, 0) - send := newSender(&data) +func TestEventChannelPush(t *testing.T) { + dataSent := make(chan []byte) + send := newSender(dataSent) + clockMock := clock.NewMock() - eventChannel := NewEventChannel(send, 15000, 5, 5*time.Millisecond) + eventChannel := NewEventChannel(send, clockMock, largeBufferSize, largeEventCount, 1*time.Second) defer eventChannel.Close() - eventChannel.Push([]byte("one")) - eventChannel.Push([]byte("two")) - eventChannel.Push([]byte("three")) - eventChannel.Push([]byte("four")) - eventChannel.Push([]byte("five")) - eventChannel.Push([]byte("six")) - eventChannel.Push([]byte("seven")) - - time.Sleep(10 * time.Millisecond) - - assert.Equal(t, string(data), "onetwothreefourfivesixseven") - -} - -func TestEventChannel_OutputFormat(t *testing.T) { - - toGzip := func(payload string) []byte { - var buf bytes.Buffer - zw := gzip.NewWriter(&buf) - - if _, err := zw.Write([]byte(payload)); err != nil { - assert.Fail(t, err.Error()) - } - - if err := zw.Close(); err != nil { - assert.Fail(t, err.Error()) - } - return buf.Bytes() - } - - data := make([]byte, 0) - send := func(payload []byte) error { - data = append(data, payload...) - return nil - } - - eventChannel := NewEventChannel(send, 15000, 10, 2*time.Minute) - - eventChannel.Push([]byte("one")) - time.Sleep(1 * time.Millisecond) - - eventChannel.flush() - - eventChannel.Push([]byte("two")) - time.Sleep(1 * time.Millisecond) - - eventChannel.Push([]byte("three")) - time.Sleep(1 * time.Millisecond) - - eventChannel.Close() - - time.Sleep(1 * time.Millisecond) + eventChannel.Push([]byte("1")) + eventChannel.Push([]byte("2")) + eventChannel.Push([]byte("3")) - expected := append(toGzip("one"), toGzip("twothree")...) + clockMock.Add(1 * time.Second) // trigger event timer - assert.Equal(t, expected, data) + data, _ := readChanOrTimeout(t, dataSent) + assert.ElementsMatch(t, []byte{'1', '2', '3'}, []byte(readGz(data))) } diff --git a/analytics/pubstack/eventchannel/sender_test.go b/analytics/pubstack/eventchannel/sender_test.go index 1185435e4ab..5a6c271207d 100644 --- a/analytics/pubstack/eventchannel/sender_test.go +++ b/analytics/pubstack/eventchannel/sender_test.go @@ -1,7 +1,7 @@ package eventchannel import ( - "io/ioutil" + "io" "net/http" "net/http/httptest" "testing" @@ -13,7 +13,7 @@ func TestBuildEndpointSender(t *testing.T) { requestBody := make([]byte, 10) server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { defer req.Body.Close() - requestBody, _ = ioutil.ReadAll(req.Body) + requestBody, _ = io.ReadAll(req.Body) res.WriteHeader(200) })) @@ -22,8 +22,8 @@ func TestBuildEndpointSender(t *testing.T) { sender := BuildEndpointSender(server.Client(), server.URL, "module") err := sender([]byte("message")) - assert.Equal(t, requestBody, []byte("message")) - assert.Nil(t, err) + assert.Equal(t, []byte("message"), requestBody) + assert.NoError(t, err) } func TestBuildEndpointSender_Error(t *testing.T) { @@ -36,5 +36,5 @@ func TestBuildEndpointSender_Error(t *testing.T) { sender := BuildEndpointSender(server.Client(), server.URL, "module") err := sender([]byte("message")) - assert.NotNil(t, err) + assert.Error(t, err) } diff --git a/analytics/pubstack/helpers/json_test.go b/analytics/pubstack/helpers/json_test.go index 9c33407db3a..dc2c28efc21 100644 --- a/analytics/pubstack/helpers/json_test.go +++ b/analytics/pubstack/helpers/json_test.go @@ -4,27 +4,27 @@ import ( "net/http" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/analytics" + "github.com/stretchr/testify/assert" ) func TestJsonifyAuctionObject(t *testing.T) { ao := &analytics.AuctionObject{ Status: http.StatusOK, } - if _, err := JsonifyAuctionObject(ao, "scopeId"); err != nil { - t.Fail() - } + _, err := JsonifyAuctionObject(ao, "scopeId") + assert.NoError(t, err) } func TestJsonifyVideoObject(t *testing.T) { vo := &analytics.VideoObject{ Status: http.StatusOK, } - if _, err := JsonifyVideoObject(vo, "scopeId"); err != nil { - t.Fail() - } + + _, err := JsonifyVideoObject(vo, "scopeId") + assert.NoError(t, err) } func TestJsonifyCookieSync(t *testing.T) { @@ -32,9 +32,9 @@ func TestJsonifyCookieSync(t *testing.T) { Status: http.StatusOK, BidderStatus: []*analytics.CookieSyncBidder{}, } - if _, err := JsonifyCookieSync(cso, "scopeId"); err != nil { - t.Fail() - } + + _, err := JsonifyCookieSync(cso, "scopeId") + assert.NoError(t, err) } func TestJsonifySetUIDObject(t *testing.T) { @@ -43,9 +43,9 @@ func TestJsonifySetUIDObject(t *testing.T) { Bidder: "any-bidder", UID: "uid string", } - if _, err := JsonifySetUIDObject(so, "scopeId"); err != nil { - t.Fail() - } + + _, err := JsonifySetUIDObject(so, "scopeId") + assert.NoError(t, err) } func TestJsonifyAmpObject(t *testing.T) { @@ -55,7 +55,7 @@ func TestJsonifyAmpObject(t *testing.T) { AuctionResponse: &openrtb2.BidResponse{}, AmpTargetingValues: map[string]string{}, } - if _, err := JsonifyAmpObject(ao, "scopeId"); err != nil { - t.Fail() - } + + _, err := JsonifyAmpObject(ao, "scopeId") + assert.NoError(t, err) } diff --git a/analytics/pubstack/pubstack_module.go b/analytics/pubstack/pubstack_module.go index 60ccde02a8c..987c935f884 100644 --- a/analytics/pubstack/pubstack_module.go +++ b/analytics/pubstack/pubstack_module.go @@ -2,19 +2,19 @@ package pubstack import ( "fmt" - "github.com/prebid/prebid-server/analytics/pubstack/eventchannel" "net/http" - "net/url" "os" "os/signal" "sync" "syscall" "time" + "github.com/benbjohnson/clock" "github.com/golang/glog" - "github.com/prebid/prebid-server/analytics/pubstack/helpers" "github.com/prebid/prebid-server/analytics" + "github.com/prebid/prebid-server/analytics/pubstack/eventchannel" + "github.com/prebid/prebid-server/analytics/pubstack/helpers" ) type Configuration struct { @@ -41,24 +41,32 @@ type bufferConfig struct { type PubstackModule struct { eventChannels map[string]*eventchannel.EventChannel httpClient *http.Client - configCh chan *Configuration sigTermCh chan os.Signal + stopCh chan struct{} scope string cfg *Configuration buffsCfg *bufferConfig muxConfig sync.RWMutex + clock clock.Clock } -func NewPubstackModule(client *http.Client, scope, endpoint, configRefreshDelay string, maxEventCount int, maxByteSize, maxTime string) (analytics.PBSAnalyticsModule, error) { - glog.Infof("[pubstack] Initializing module scope=%s endpoint=%s\n", scope, endpoint) - - // parse args - - refreshDelay, err := time.ParseDuration(configRefreshDelay) +func NewModule(client *http.Client, scope, endpoint, configRefreshDelay string, maxEventCount int, maxByteSize, maxTime string, clock clock.Clock) (analytics.PBSAnalyticsModule, error) { + configUpdateTask, err := NewConfigUpdateHttpTask( + client, + scope, + endpoint, + configRefreshDelay) if err != nil { - return nil, fmt.Errorf("fail to parse the module args, arg=analytics.pubstack.configuration_refresh_delay, :%v", err) + return nil, err } + return NewModuleWithConfigTask(client, scope, endpoint, maxEventCount, maxByteSize, maxTime, configUpdateTask, clock) +} + +func NewModuleWithConfigTask(client *http.Client, scope, endpoint string, maxEventCount int, maxByteSize, maxTime string, configTask ConfigUpdateTask, clock clock.Clock) (analytics.PBSAnalyticsModule, error) { + glog.Infof("[pubstack] Initializing module scope=%s endpoint=%s\n", scope, endpoint) + + // parse args bufferCfg, err := newBufferConfig(maxEventCount, maxByteSize, maxTime) if err != nil { return nil, fmt.Errorf("fail to parse the module args, arg=analytics.pubstack.buffers, :%v", err) @@ -84,24 +92,16 @@ func NewPubstackModule(client *http.Client, scope, endpoint, configRefreshDelay cfg: defaultConfig, buffsCfg: bufferCfg, sigTermCh: make(chan os.Signal), - configCh: make(chan *Configuration), + stopCh: make(chan struct{}), eventChannels: make(map[string]*eventchannel.EventChannel), muxConfig: sync.RWMutex{}, + clock: clock, } + signal.Notify(pb.sigTermCh, os.Interrupt, syscall.SIGTERM) - configUrl, err := url.Parse(pb.cfg.Endpoint + "/bootstrap?scopeId=" + pb.cfg.ScopeID) - if err != nil { - glog.Error(err) - return nil, err - } - go pb.start(configUrl, refreshDelay) - go func() { - err = pb.reloadConfig(configUrl) - if err != nil { - glog.Errorf("[pubstack] Fail to fetch remote configuration: %v", err) - } - }() + configChannel := configTask.Start(pb.stopCh) + go pb.start(configChannel) glog.Info("[pubstack] Pubstack analytics configured and ready") return &pb, nil @@ -180,7 +180,6 @@ func (p *PubstackModule) LogCookieSyncObject(cso *analytics.CookieSyncObject) { } p.eventChannels[cookieSync].Push(payload) - } func (p *PubstackModule) LogAmpObject(ao *analytics.AmpObject) { @@ -199,40 +198,21 @@ func (p *PubstackModule) LogAmpObject(ao *analytics.AmpObject) { } p.eventChannels[amp].Push(payload) - -} - -func (p *PubstackModule) reloadConfig(configUrl *url.URL) error { - config, err := fetchConfig(p.httpClient, configUrl) - if err != nil { - return err - } - p.configCh <- config - return nil } -func (p *PubstackModule) start(configUrl *url.URL, refreshDelay time.Duration) { - - tick := time.NewTicker(refreshDelay) - +func (p *PubstackModule) start(c <-chan *Configuration) { for { select { case <-p.sigTermCh: - p.closeAllEventChannels() + close(p.stopCh) + cfg := p.cfg.clone().disableAllFeatures() + p.updateConfig(cfg) return - case config := <-p.configCh: + case config := <-c: p.updateConfig(config) glog.Infof("[pubstack] Updating config: %v", p.cfg) - case <-tick.C: - go func() { - err := p.reloadConfig(configUrl) - if err != nil { - glog.Errorf("[pubstack] Fail to fetch remote configuration: %v", err) - } - }() } } - } func (p *PubstackModule) updateConfig(config *Configuration) { @@ -246,20 +226,22 @@ func (p *PubstackModule) updateConfig(config *Configuration) { p.cfg = config p.closeAllEventChannels() - if p.isFeatureEnable(amp) { - p.eventChannels[amp] = eventchannel.NewEventChannel(eventchannel.BuildEndpointSender(p.httpClient, p.cfg.Endpoint, amp), p.buffsCfg.size, p.buffsCfg.count, p.buffsCfg.timeout) - } - if p.isFeatureEnable(auction) { - p.eventChannels[auction] = eventchannel.NewEventChannel(eventchannel.BuildEndpointSender(p.httpClient, p.cfg.Endpoint, auction), p.buffsCfg.size, p.buffsCfg.count, p.buffsCfg.timeout) - } - if p.isFeatureEnable(cookieSync) { - p.eventChannels[cookieSync] = eventchannel.NewEventChannel(eventchannel.BuildEndpointSender(p.httpClient, p.cfg.Endpoint, cookieSync), p.buffsCfg.size, p.buffsCfg.count, p.buffsCfg.timeout) - } - if p.isFeatureEnable(video) { - p.eventChannels[video] = eventchannel.NewEventChannel(eventchannel.BuildEndpointSender(p.httpClient, p.cfg.Endpoint, video), p.buffsCfg.size, p.buffsCfg.count, p.buffsCfg.timeout) - } - if p.isFeatureEnable(setUID) { - p.eventChannels[setUID] = eventchannel.NewEventChannel(eventchannel.BuildEndpointSender(p.httpClient, p.cfg.Endpoint, setUID), p.buffsCfg.size, p.buffsCfg.count, p.buffsCfg.timeout) + p.registerChannel(amp) + p.registerChannel(auction) + p.registerChannel(cookieSync) + p.registerChannel(video) + p.registerChannel(setUID) +} + +func (p *PubstackModule) isFeatureEnable(feature string) bool { + val, ok := p.cfg.Features[feature] + return ok && val +} + +func (p *PubstackModule) registerChannel(feature string) { + if p.isFeatureEnable(feature) { + sender := eventchannel.BuildEndpointSender(p.httpClient, p.cfg.Endpoint, feature) + p.eventChannels[feature] = eventchannel.NewEventChannel(sender, p.clock, p.buffsCfg.size, p.buffsCfg.count, p.buffsCfg.timeout) } } @@ -269,8 +251,3 @@ func (p *PubstackModule) closeAllEventChannels() { delete(p.eventChannels, key) } } - -func (p *PubstackModule) isFeatureEnable(feature string) bool { - val, ok := p.cfg.Features[feature] - return ok && val -} diff --git a/analytics/pubstack/pubstack_module_test.go b/analytics/pubstack/pubstack_module_test.go index 0e0b3634508..67598360419 100644 --- a/analytics/pubstack/pubstack_module_test.go +++ b/analytics/pubstack/pubstack_module_test.go @@ -1,17 +1,18 @@ package pubstack import ( - "encoding/json" "net/http" "net/http/httptest" + "os" "testing" "time" + "github.com/benbjohnson/clock" "github.com/prebid/prebid-server/analytics" "github.com/stretchr/testify/assert" ) -func TestPubstackModuleErrors(t *testing.T) { +func TestNewModuleErrors(t *testing.T) { tests := []struct { description string refreshDelay string @@ -39,12 +40,12 @@ func TestPubstackModuleErrors(t *testing.T) { } for _, tt := range tests { - _, err := NewPubstackModule(&http.Client{}, "scope", "http://example.com", tt.refreshDelay, 100, tt.maxByteSize, tt.maxTime) - assert.NotNil(t, err, tt.description) + _, err := NewModule(&http.Client{}, "scope", "http://example.com", tt.refreshDelay, 100, tt.maxByteSize, tt.maxTime, clock.NewMock()) + assert.Error(t, err, tt.description) } } -func TestPubstackModuleSuccess(t *testing.T) { +func TestNewModuleSuccess(t *testing.T) { tests := []struct { description string feature string @@ -88,94 +89,100 @@ func TestPubstackModuleSuccess(t *testing.T) { } for _, tt := range tests { - // original config is loaded when the module is created - // the feature is disabled so no events should be sent + // original config with the feature disabled so no events should be sent origConfig := &Configuration{ Features: map[string]bool{ tt.feature: false, }, } - // updated config is hot-reloaded after some time passes - // the feature is enabled so events should be sent + + // updated config with the feature enabled so events should be sent updatedConfig := &Configuration{ Features: map[string]bool{ tt.feature: true, }, } - // create server with root endpoint that returns the current config - // add an intake endpoint that PBS hits when events are sent - rootCount := 0 + // create server with an intake endpoint that PBS hits when events are sent mux := http.NewServeMux() - mux.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) { - rootCount++ - defer req.Body.Close() - - if rootCount > 1 { - if data, err := json.Marshal(updatedConfig); err != nil { - res.WriteHeader(http.StatusBadRequest) - } else { - res.Write(data) - } - } else { - if data, err := json.Marshal(origConfig); err != nil { - res.WriteHeader(http.StatusBadRequest) - } else { - res.Write(data) - } - } - }) - - intakeChannel := make(chan int) // using a channel rather than examining the count directly to avoid race + intakeChannel := make(chan int) mux.HandleFunc("/intake/"+tt.feature+"/", func(res http.ResponseWriter, req *http.Request) { intakeChannel <- 1 }) server := httptest.NewServer(mux) client := server.Client() - // set the server url on each of the configs + // set the event server url on each of the configs origConfig.Endpoint = server.URL updatedConfig.Endpoint = server.URL - // instantiate module with 25ms config refresh rate - module, err := NewPubstackModule(client, "scope", server.URL, "15ms", 100, "1B", "10ms") - assert.Nil(t, err, tt.description) - - // allow time for the module to load the original config - time.Sleep(10 * time.Millisecond) + // instantiate module with a manual config update task + clockMock := clock.NewMock() + configTask := fakeConfigUpdateTask{} + module, err := NewModuleWithConfigTask(client, "scope", server.URL, 100, "1B", "1s", &configTask, clockMock) + assert.NoError(t, err, tt.description) pubstack, _ := module.(*PubstackModule) - // attempt to log but no event channel was created because the feature is disabled in the original config - tt.logObject(pubstack) - - // verify no event was received over a 10ms period - assertChanNone(t, intakeChannel, tt.description) - - // allow time for the server to start serving the updated config - time.Sleep(10 * time.Millisecond) - - // attempt to log; the event channel should have been created because the feature is enabled in updated config - tt.logObject(pubstack) - // verify an event was received within 10ms - assertChanOne(t, intakeChannel, tt.description) + // original config + configTask.Push(origConfig) + time.Sleep(10 * time.Millisecond) // allow time for the module to load the original config + tt.logObject(pubstack) // attempt to log; no event channel created because feature is disabled in original config + clockMock.Add(1 * time.Second) // trigger event channel sending + assertChanNone(t, intakeChannel, tt.description+":original") // verify no event was received + + // updated config + configTask.Push(updatedConfig) + time.Sleep(10 * time.Millisecond) // allow time for the server to start serving the updated config + tt.logObject(pubstack) // attempt to log; event channel should be created because feature is enabled in updated config + clockMock.Add(1 * time.Second) // trigger event channel sending + assertChanOne(t, intakeChannel, tt.description+":updated") // verify an event was received + + // no config change + configTask.Push(updatedConfig) + time.Sleep(10 * time.Millisecond) // allow time for the server to determine no config change + tt.logObject(pubstack) // attempt to log; event channel should still be created from loading updated config + clockMock.Add(1 * time.Second) // trigger event channel sending + assertChanOne(t, intakeChannel, tt.description+":no_change") // verify an event was received + + // shutdown + pubstack.sigTermCh <- os.Kill // simulate os shutdown signal + time.Sleep(10 * time.Millisecond) // allow time for the server to switch to shutdown generated config + tt.logObject(pubstack) // attempt to log; event channel should be closed from the os kill signal + clockMock.Add(1 * time.Second) // trigger event channel sending + assertChanNone(t, intakeChannel, tt.description+":shutdown") // verify no event was received } } func assertChanNone(t *testing.T, c <-chan int, msgAndArgs ...interface{}) bool { + t.Helper() select { case <-c: return assert.Fail(t, "Should NOT receive an event, but did", msgAndArgs...) - case <-time.After(10 * time.Millisecond): + case <-time.After(100 * time.Millisecond): return true } } func assertChanOne(t *testing.T, c <-chan int, msgAndArgs ...interface{}) bool { + t.Helper() select { case <-c: return true - case <-time.After(10 * time.Millisecond): + case <-time.After(200 * time.Millisecond): return assert.Fail(t, "Should receive an event, but did NOT", msgAndArgs...) } } + +type fakeConfigUpdateTask struct { + configChan chan *Configuration +} + +func (f *fakeConfigUpdateTask) Start(stop <-chan struct{}) <-chan *Configuration { + f.configChan = make(chan *Configuration) + return f.configChan +} + +func (f *fakeConfigUpdateTask) Push(c *Configuration) { + f.configChan <- c +} diff --git a/config/accounts.go b/config/accounts.go index e475da8aa4c..9f52554eda5 100644 --- a/config/accounts.go +++ b/config/accounts.go @@ -1,53 +1,62 @@ package config import ( + "encoding/json" + "fmt" + "strings" + "github.com/prebid/go-gdpr/consentconstants" "github.com/prebid/prebid-server/openrtb_ext" ) -// IntegrationType enumerates the values of integrations Prebid Server can configure for an account -type IntegrationType string +// ChannelType enumerates the values of integrations Prebid Server can configure for an account +type ChannelType string -// Possible values of integration types Prebid Server can configure for an account +// Possible values of channel types Prebid Server can configure for an account const ( - IntegrationTypeAMP IntegrationType = "amp" - IntegrationTypeApp IntegrationType = "app" - IntegrationTypeVideo IntegrationType = "video" - IntegrationTypeWeb IntegrationType = "web" + ChannelAMP ChannelType = "amp" + ChannelApp ChannelType = "app" + ChannelVideo ChannelType = "video" + ChannelWeb ChannelType = "web" ) // Account represents a publisher account configuration type Account struct { - ID string `mapstructure:"id" json:"id"` - Disabled bool `mapstructure:"disabled" json:"disabled"` - CacheTTL DefaultTTLs `mapstructure:"cache_ttl" json:"cache_ttl"` - EventsEnabled bool `mapstructure:"events_enabled" json:"events_enabled"` - CCPA AccountCCPA `mapstructure:"ccpa" json:"ccpa"` - GDPR AccountGDPR `mapstructure:"gdpr" json:"gdpr"` - DebugAllow bool `mapstructure:"debug_allow" json:"debug_allow"` - DefaultIntegration string `mapstructure:"default_integration" json:"default_integration"` - CookieSync CookieSync `mapstructure:"cookie_sync" json:"cookie_sync"` - Events Events `mapstructure:"events" json:"events"` // Don't enable this feature. It is still under developmment - https://github.com/prebid/prebid-server/issues/1725 - TruncateTargetAttribute *int `mapstructure:"truncate_target_attr" json:"truncate_target_attr"` + ID string `mapstructure:"id" json:"id"` + Disabled bool `mapstructure:"disabled" json:"disabled"` + CacheTTL DefaultTTLs `mapstructure:"cache_ttl" json:"cache_ttl"` + EventsEnabled bool `mapstructure:"events_enabled" json:"events_enabled"` + CCPA AccountCCPA `mapstructure:"ccpa" json:"ccpa"` + GDPR AccountGDPR `mapstructure:"gdpr" json:"gdpr"` + DebugAllow bool `mapstructure:"debug_allow" json:"debug_allow"` + DefaultIntegration string `mapstructure:"default_integration" json:"default_integration"` + CookieSync CookieSync `mapstructure:"cookie_sync" json:"cookie_sync"` + Events Events `mapstructure:"events" json:"events"` // Don't enable this feature. It is still under developmment - https://github.com/prebid/prebid-server/issues/1725 + TruncateTargetAttribute *int `mapstructure:"truncate_target_attr" json:"truncate_target_attr"` + AlternateBidderCodes *openrtb_ext.ExtAlternateBidderCodes `mapstructure:"alternatebiddercodes" json:"alternatebiddercodes"` + Hooks AccountHooks `mapstructure:"hooks" json:"hooks"` } // CookieSync represents the account-level defaults for the cookie sync endpoint. type CookieSync struct { - DefaultLimit int `mapstructure:"default_limit" json:"default_limit"` - MaxLimit int `mapstructure:"max_limit" json:"max_limit"` - DefaultCoopSync bool `mapstructure:"default_coop_sync" json:"default_coop_sync"` + DefaultLimit *int `mapstructure:"default_limit" json:"default_limit"` + MaxLimit *int `mapstructure:"max_limit" json:"max_limit"` + DefaultCoopSync *bool `mapstructure:"default_coop_sync" json:"default_coop_sync"` } // AccountCCPA represents account-specific CCPA configuration type AccountCCPA struct { - Enabled *bool `mapstructure:"enabled" json:"enabled,omitempty"` - IntegrationEnabled AccountIntegration `mapstructure:"integration_enabled" json:"integration_enabled"` + Enabled *bool `mapstructure:"enabled" json:"enabled,omitempty"` + IntegrationEnabled AccountChannel `mapstructure:"integration_enabled" json:"integration_enabled"` + ChannelEnabled AccountChannel `mapstructure:"channel_enabled" json:"channel_enabled"` } -// EnabledForIntegrationType indicates whether CCPA is turned on at the account level for the specified integration type -// by using the integration type setting if defined or the general CCPA setting if defined; otherwise it returns nil -func (a *AccountCCPA) EnabledForIntegrationType(integrationType IntegrationType) *bool { - if integrationEnabled := a.IntegrationEnabled.GetByIntegrationType(integrationType); integrationEnabled != nil { +// EnabledForChannelType indicates whether CCPA is turned on at the account level for the specified channel type +// by using the channel type setting if defined or the general CCPA setting if defined; otherwise it returns nil +func (a *AccountCCPA) EnabledForChannelType(channelType ChannelType) *bool { + if channelEnabled := a.ChannelEnabled.GetByChannelType(channelType); channelEnabled != nil { + return channelEnabled + } else if integrationEnabled := a.IntegrationEnabled.GetByChannelType(channelType); integrationEnabled != nil { return integrationEnabled } return a.Enabled @@ -55,8 +64,9 @@ func (a *AccountCCPA) EnabledForIntegrationType(integrationType IntegrationType) // AccountGDPR represents account-specific GDPR configuration type AccountGDPR struct { - Enabled *bool `mapstructure:"enabled" json:"enabled,omitempty"` - IntegrationEnabled AccountIntegration `mapstructure:"integration_enabled" json:"integration_enabled"` + Enabled *bool `mapstructure:"enabled" json:"enabled,omitempty"` + IntegrationEnabled AccountChannel `mapstructure:"integration_enabled" json:"integration_enabled"` + ChannelEnabled AccountChannel `mapstructure:"channel_enabled" json:"channel_enabled"` // Array of basic enforcement vendors that is used to create the hash table so vendor names can be instantly accessed BasicEnforcementVendors []string `mapstructure:"basic_enforcement_vendors" json:"basic_enforcement_vendors"` BasicEnforcementVendorsMap map[string]struct{} @@ -76,21 +86,12 @@ type AccountGDPR struct { SpecialFeature1 AccountGDPRSpecialFeature `mapstructure:"special_feature1" json:"special_feature1"` } -// BasicEnforcementVendor checks if the given bidder is considered a basic enforcement vendor which indicates whether -// weak vendor enforcement applies to that bidder. -func (a *AccountGDPR) BasicEnforcementVendor(bidder openrtb_ext.BidderName) (value, exists bool) { - if a.BasicEnforcementVendorsMap == nil { - return false, false - } - _, found := a.BasicEnforcementVendorsMap[string(bidder)] - - return found, true -} - -// EnabledForIntegrationType indicates whether GDPR is turned on at the account level for the specified integration type -// by using the integration type setting if defined or the general GDPR setting if defined; otherwise it returns nil. -func (a *AccountGDPR) EnabledForIntegrationType(integrationType IntegrationType) *bool { - if integrationEnabled := a.IntegrationEnabled.GetByIntegrationType(integrationType); integrationEnabled != nil { +// EnabledForChannelType indicates whether GDPR is turned on at the account level for the specified channel type +// by using the channel type setting if defined or the general GDPR setting if defined; otherwise it returns nil. +func (a *AccountGDPR) EnabledForChannelType(channelType ChannelType) *bool { + if channelEnabled := a.ChannelEnabled.GetByChannelType(channelType); channelEnabled != nil { + return channelEnabled + } else if integrationEnabled := a.IntegrationEnabled.GetByChannelType(channelType); integrationEnabled != nil { return integrationEnabled } return a.Enabled @@ -122,13 +123,21 @@ func (a *AccountGDPR) PurposeEnforced(purpose consentconstants.Purpose) (value, if a.PurposeConfigs[purpose] == nil { return true, false } - if a.PurposeConfigs[purpose].EnforcePurpose == TCF2FullEnforcement { - return true, true + if a.PurposeConfigs[purpose].EnforcePurpose == nil { + return true, false } - if a.PurposeConfigs[purpose].EnforcePurpose == TCF2NoEnforcement { - return false, true + return *a.PurposeConfigs[purpose].EnforcePurpose, true +} + +// PurposeEnforcementAlgo checks the purpose enforcement algo for a given purpose by first +// looking at the account settings, and if not set there, defaulting to the host configuration. +func (a *AccountGDPR) PurposeEnforcementAlgo(purpose consentconstants.Purpose) (value TCF2EnforcementAlgo, exists bool) { + c, exists := a.PurposeConfigs[purpose] + + if exists && (c.EnforceAlgoID == TCF2BasicEnforcement || c.EnforceAlgoID == TCF2FullEnforcement) { + return c.EnforceAlgoID, true } - return true, false + return TCF2UndefinedEnforcement, false } // PurposeEnforcingVendors gets the account level enforce vendors setting for a given purpose returning the value and @@ -143,17 +152,14 @@ func (a *AccountGDPR) PurposeEnforcingVendors(purpose consentconstants.Purpose) return *a.PurposeConfigs[purpose].EnforceVendors, true } -// PurposeVendorException checks if the given bidder is a vendor exception for a given purpose. -func (a *AccountGDPR) PurposeVendorException(purpose consentconstants.Purpose, bidder openrtb_ext.BidderName) (value, exists bool) { - if a.PurposeConfigs[purpose] == nil { - return false, false - } - if a.PurposeConfigs[purpose].VendorExceptionMap == nil { - return false, false - } - _, found := a.PurposeConfigs[purpose].VendorExceptionMap[bidder] +// PurposeVendorExceptions returns the vendor exception map for a given purpose. +func (a *AccountGDPR) PurposeVendorExceptions(purpose consentconstants.Purpose) (value map[openrtb_ext.BidderName]struct{}, exists bool) { + c, exists := a.PurposeConfigs[purpose] - return found, true + if exists && c.VendorExceptionMap != nil { + return c.VendorExceptionMap, true + } + return nil, false } // PurposeOneTreatmentEnabled gets the account level purpose one treatment enabled setting returning the value and @@ -176,8 +182,11 @@ func (a *AccountGDPR) PurposeOneTreatmentAccessAllowed() (value, exists bool) { // AccountGDPRPurpose represents account-specific GDPR purpose configuration type AccountGDPRPurpose struct { - EnforcePurpose string `mapstructure:"enforce_purpose" json:"enforce_purpose,omitempty"` - EnforceVendors *bool `mapstructure:"enforce_vendors" json:"enforce_vendors,omitempty"` + EnforceAlgo string `mapstructure:"enforce_algo" json:"enforce_algo,omitempty"` + // Integer representation of enforcement algo for performance improvement on compares + EnforceAlgoID TCF2EnforcementAlgo + EnforcePurpose *bool `mapstructure:"enforce_purpose" json:"enforce_purpose,omitempty"` + EnforceVendors *bool `mapstructure:"enforce_vendors" json:"enforce_vendors,omitempty"` // 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" json:"vendor_exceptions"` VendorExceptionMap map[openrtb_ext.BidderName]struct{} @@ -197,28 +206,52 @@ type AccountGDPRPurposeOneTreatment struct { AccessAllowed *bool `mapstructure:"access_allowed"` } -// AccountIntegration indicates whether a particular privacy policy (GDPR, CCPA) is enabled for each integration type -type AccountIntegration struct { +// AccountChannel indicates whether a particular privacy policy (GDPR, CCPA) is enabled for each channel type +type AccountChannel struct { AMP *bool `mapstructure:"amp" json:"amp,omitempty"` App *bool `mapstructure:"app" json:"app,omitempty"` Video *bool `mapstructure:"video" json:"video,omitempty"` Web *bool `mapstructure:"web" json:"web,omitempty"` } -// GetByIntegrationType looks up the account integration enabled setting for the specified integration type -func (a *AccountIntegration) GetByIntegrationType(integrationType IntegrationType) *bool { - var integrationEnabled *bool +// GetByChannelType looks up the account integration enabled setting for the specified channel type +func (a *AccountChannel) GetByChannelType(channelType ChannelType) *bool { + var channelEnabled *bool + + switch channelType { + case ChannelAMP: + channelEnabled = a.AMP + case ChannelApp: + channelEnabled = a.App + case ChannelVideo: + channelEnabled = a.Video + case ChannelWeb: + channelEnabled = a.Web + } + + return channelEnabled +} + +// AccountHooks represents account-specific hooks configuration +type AccountHooks struct { + Modules AccountModules `mapstructure:"modules" json:"modules"` + ExecutionPlan HookExecutionPlan `mapstructure:"execution_plan" json:"execution_plan"` +} + +// AccountModules mapping provides account-level module configuration +// format: map[vendor_name]map[module_name]json.RawMessage +type AccountModules map[string]map[string]json.RawMessage - switch integrationType { - case IntegrationTypeAMP: - integrationEnabled = a.AMP - case IntegrationTypeApp: - integrationEnabled = a.App - case IntegrationTypeVideo: - integrationEnabled = a.Video - case IntegrationTypeWeb: - integrationEnabled = a.Web +// ModuleConfig returns the account-level module config. +// The id argument must be passed in the form "vendor.module_name", +// otherwise an error is returned. +func (m AccountModules) ModuleConfig(id string) (json.RawMessage, error) { + ns := strings.SplitN(id, ".", 2) + if len(ns) < 2 { + return nil, fmt.Errorf("ID must consist of vendor and module names separated by dot, got: %s", id) } - return integrationEnabled + vendor := ns[0] + module := ns[1] + return m[vendor][module], nil } diff --git a/config/accounts_test.go b/config/accounts_test.go index e2e0b169101..1006b0960e8 100644 --- a/config/accounts_test.go +++ b/config/accounts_test.go @@ -1,6 +1,8 @@ package config import ( + "encoding/json" + "errors" "testing" "github.com/prebid/go-gdpr/consentconstants" @@ -8,50 +10,72 @@ import ( "github.com/stretchr/testify/assert" ) -func TestAccountGDPREnabledForIntegrationType(t *testing.T) { +func TestAccountGDPREnabledForChannelType(t *testing.T) { trueValue, falseValue := true, false tests := []struct { - description string - giveIntegrationType IntegrationType - giveGDPREnabled *bool - giveWebGDPREnabled *bool - wantEnabled *bool + description string + giveChannelType ChannelType + giveGDPREnabled *bool + giveWebGDPREnabled *bool + giveWebGDPREnabledForIntegration *bool + wantEnabled *bool }{ { - description: "GDPR Web integration enabled, general GDPR disabled", - giveIntegrationType: IntegrationTypeWeb, - giveGDPREnabled: &falseValue, - giveWebGDPREnabled: &trueValue, - wantEnabled: &trueValue, + description: "GDPR Web channel enabled, general GDPR disabled", + giveChannelType: ChannelWeb, + giveGDPREnabled: &falseValue, + giveWebGDPREnabled: &trueValue, + giveWebGDPREnabledForIntegration: nil, + wantEnabled: &trueValue, }, { - description: "GDPR Web integration disabled, general GDPR enabled", - giveIntegrationType: IntegrationTypeWeb, - giveGDPREnabled: &trueValue, - giveWebGDPREnabled: &falseValue, - wantEnabled: &falseValue, + description: "GDPR Web channel disabled, general GDPR enabled", + giveChannelType: ChannelWeb, + giveGDPREnabled: &trueValue, + giveWebGDPREnabled: &falseValue, + giveWebGDPREnabledForIntegration: nil, + wantEnabled: &falseValue, }, { - description: "GDPR Web integration unspecified, general GDPR disabled", - giveIntegrationType: IntegrationTypeWeb, - giveGDPREnabled: &falseValue, - giveWebGDPREnabled: nil, - wantEnabled: &falseValue, + description: "GDPR Web channel unspecified, general GDPR disabled", + giveChannelType: ChannelWeb, + giveGDPREnabled: &falseValue, + giveWebGDPREnabled: nil, + giveWebGDPREnabledForIntegration: nil, + wantEnabled: &falseValue, }, { - description: "GDPR Web integration unspecified, general GDPR enabled", - giveIntegrationType: IntegrationTypeWeb, - giveGDPREnabled: &trueValue, - giveWebGDPREnabled: nil, - wantEnabled: &trueValue, + description: "GDPR Web channel unspecified, general GDPR enabled", + giveChannelType: ChannelWeb, + giveGDPREnabled: &trueValue, + giveWebGDPREnabled: nil, + giveWebGDPREnabledForIntegration: nil, + wantEnabled: &trueValue, }, { - description: "GDPR Web integration unspecified, general GDPR unspecified", - giveIntegrationType: IntegrationTypeWeb, - giveGDPREnabled: nil, - giveWebGDPREnabled: nil, - wantEnabled: nil, + description: "GDPR Web channel unspecified, general GDPR unspecified", + giveChannelType: ChannelWeb, + giveGDPREnabled: nil, + giveWebGDPREnabled: nil, + giveWebGDPREnabledForIntegration: nil, + wantEnabled: nil, + }, + { + description: "Inegration Enabled is set, and channel enabled isn't", + giveChannelType: ChannelWeb, + giveGDPREnabled: &falseValue, + giveWebGDPREnabled: nil, + giveWebGDPREnabledForIntegration: &trueValue, + wantEnabled: &trueValue, + }, + { + description: "Inegration Enabled is set, and channel enabled is set, channel should have precedence", + giveChannelType: ChannelWeb, + giveGDPREnabled: &falseValue, + giveWebGDPREnabled: &trueValue, + giveWebGDPREnabledForIntegration: &falseValue, + wantEnabled: &trueValue, }, } @@ -59,13 +83,16 @@ func TestAccountGDPREnabledForIntegrationType(t *testing.T) { account := Account{ GDPR: AccountGDPR{ Enabled: tt.giveGDPREnabled, - IntegrationEnabled: AccountIntegration{ + ChannelEnabled: AccountChannel{ Web: tt.giveWebGDPREnabled, }, + IntegrationEnabled: AccountChannel{ + Web: tt.giveWebGDPREnabledForIntegration, + }, }, } - enabled := account.GDPR.EnabledForIntegrationType(tt.giveIntegrationType) + enabled := account.GDPR.EnabledForChannelType(tt.giveChannelType) if tt.wantEnabled == nil { assert.Nil(t, enabled, tt.description) @@ -76,50 +103,72 @@ func TestAccountGDPREnabledForIntegrationType(t *testing.T) { } } -func TestAccountCCPAEnabledForIntegrationType(t *testing.T) { +func TestAccountCCPAEnabledForChannelType(t *testing.T) { trueValue, falseValue := true, false tests := []struct { - description string - giveIntegrationType IntegrationType - giveCCPAEnabled *bool - giveWebCCPAEnabled *bool - wantEnabled *bool + description string + giveChannelType ChannelType + giveCCPAEnabled *bool + giveWebCCPAEnabled *bool + giveWebCCPAEnabledForIntegration *bool + wantEnabled *bool }{ { - description: "CCPA Web integration enabled, general CCPA disabled", - giveIntegrationType: IntegrationTypeWeb, - giveCCPAEnabled: &falseValue, - giveWebCCPAEnabled: &trueValue, - wantEnabled: &trueValue, + description: "CCPA Web channel enabled, general CCPA disabled", + giveChannelType: ChannelWeb, + giveCCPAEnabled: &falseValue, + giveWebCCPAEnabled: &trueValue, + giveWebCCPAEnabledForIntegration: nil, + wantEnabled: &trueValue, }, { - description: "CCPA Web integration disabled, general CCPA enabled", - giveIntegrationType: IntegrationTypeWeb, - giveCCPAEnabled: &trueValue, - giveWebCCPAEnabled: &falseValue, - wantEnabled: &falseValue, + description: "CCPA Web channel disabled, general CCPA enabled", + giveChannelType: ChannelWeb, + giveCCPAEnabled: &trueValue, + giveWebCCPAEnabled: &falseValue, + giveWebCCPAEnabledForIntegration: nil, + wantEnabled: &falseValue, }, { - description: "CCPA Web integration unspecified, general CCPA disabled", - giveIntegrationType: IntegrationTypeWeb, - giveCCPAEnabled: &falseValue, - giveWebCCPAEnabled: nil, - wantEnabled: &falseValue, + description: "CCPA Web channel unspecified, general CCPA disabled", + giveChannelType: ChannelWeb, + giveCCPAEnabled: &falseValue, + giveWebCCPAEnabled: nil, + giveWebCCPAEnabledForIntegration: nil, + wantEnabled: &falseValue, }, { - description: "CCPA Web integration unspecified, general CCPA enabled", - giveIntegrationType: IntegrationTypeWeb, - giveCCPAEnabled: &trueValue, - giveWebCCPAEnabled: nil, - wantEnabled: &trueValue, + description: "CCPA Web channel unspecified, general CCPA enabled", + giveChannelType: ChannelWeb, + giveCCPAEnabled: &trueValue, + giveWebCCPAEnabled: nil, + giveWebCCPAEnabledForIntegration: nil, + wantEnabled: &trueValue, }, { - description: "CCPA Web integration unspecified, general CCPA unspecified", - giveIntegrationType: IntegrationTypeWeb, - giveCCPAEnabled: nil, - giveWebCCPAEnabled: nil, - wantEnabled: nil, + description: "CCPA Web channel unspecified, general CCPA unspecified", + giveChannelType: ChannelWeb, + giveCCPAEnabled: nil, + giveWebCCPAEnabled: nil, + giveWebCCPAEnabledForIntegration: nil, + wantEnabled: nil, + }, + { + description: "Inegration Enabled is set, and channel enabled isn't", + giveChannelType: ChannelWeb, + giveCCPAEnabled: &falseValue, + giveWebCCPAEnabled: nil, + giveWebCCPAEnabledForIntegration: &trueValue, + wantEnabled: &trueValue, + }, + { + description: "Inegration Enabled is set, and channel enabled is set, channel should have precedence", + giveChannelType: ChannelWeb, + giveCCPAEnabled: &falseValue, + giveWebCCPAEnabled: &trueValue, + giveWebCCPAEnabledForIntegration: &falseValue, + wantEnabled: &trueValue, }, } @@ -127,13 +176,16 @@ func TestAccountCCPAEnabledForIntegrationType(t *testing.T) { account := Account{ CCPA: AccountCCPA{ Enabled: tt.giveCCPAEnabled, - IntegrationEnabled: AccountIntegration{ + ChannelEnabled: AccountChannel{ Web: tt.giveWebCCPAEnabled, }, + IntegrationEnabled: AccountChannel{ + Web: tt.giveWebCCPAEnabledForIntegration, + }, }, } - enabled := account.CCPA.EnabledForIntegrationType(tt.giveIntegrationType) + enabled := account.CCPA.EnabledForChannelType(tt.giveChannelType) if tt.wantEnabled == nil { assert.Nil(t, enabled, tt.description) @@ -144,97 +196,97 @@ func TestAccountCCPAEnabledForIntegrationType(t *testing.T) { } } -func TestAccountIntegrationGetByIntegrationType(t *testing.T) { +func TestAccountChannelGetByChannelType(t *testing.T) { trueValue, falseValue := true, false tests := []struct { - description string - giveAMPEnabled *bool - giveAppEnabled *bool - giveVideoEnabled *bool - giveWebEnabled *bool - giveIntegrationType IntegrationType - wantEnabled *bool + description string + giveAMPEnabled *bool + giveAppEnabled *bool + giveVideoEnabled *bool + giveWebEnabled *bool + giveChannelType ChannelType + wantEnabled *bool }{ { - description: "AMP integration setting unspecified, returns nil", - giveIntegrationType: IntegrationTypeAMP, - wantEnabled: nil, + description: "AMP channel setting unspecified, returns nil", + giveChannelType: ChannelAMP, + wantEnabled: nil, }, { - description: "AMP integration disabled, returns false", - giveAMPEnabled: &falseValue, - giveIntegrationType: IntegrationTypeAMP, - wantEnabled: &falseValue, + description: "AMP channel disabled, returns false", + giveAMPEnabled: &falseValue, + giveChannelType: ChannelAMP, + wantEnabled: &falseValue, }, { - description: "AMP integration enabled, returns true", - giveAMPEnabled: &trueValue, - giveIntegrationType: IntegrationTypeAMP, - wantEnabled: &trueValue, + description: "AMP channel enabled, returns true", + giveAMPEnabled: &trueValue, + giveChannelType: ChannelAMP, + wantEnabled: &trueValue, }, { - description: "App integration setting unspecified, returns nil", - giveIntegrationType: IntegrationTypeApp, - wantEnabled: nil, + description: "App channel setting unspecified, returns nil", + giveChannelType: ChannelApp, + wantEnabled: nil, }, { - description: "App integration disabled, returns false", - giveAppEnabled: &falseValue, - giveIntegrationType: IntegrationTypeApp, - wantEnabled: &falseValue, + description: "App channel disabled, returns false", + giveAppEnabled: &falseValue, + giveChannelType: ChannelApp, + wantEnabled: &falseValue, }, { - description: "App integration enabled, returns true", - giveAppEnabled: &trueValue, - giveIntegrationType: IntegrationTypeApp, - wantEnabled: &trueValue, + description: "App channel enabled, returns true", + giveAppEnabled: &trueValue, + giveChannelType: ChannelApp, + wantEnabled: &trueValue, }, { - description: "Video integration setting unspecified, returns nil", - giveIntegrationType: IntegrationTypeVideo, - wantEnabled: nil, + description: "Video channel setting unspecified, returns nil", + giveChannelType: ChannelVideo, + wantEnabled: nil, }, { - description: "Video integration disabled, returns false", - giveVideoEnabled: &falseValue, - giveIntegrationType: IntegrationTypeVideo, - wantEnabled: &falseValue, + description: "Video channel disabled, returns false", + giveVideoEnabled: &falseValue, + giveChannelType: ChannelVideo, + wantEnabled: &falseValue, }, { - description: "Video integration enabled, returns true", - giveVideoEnabled: &trueValue, - giveIntegrationType: IntegrationTypeVideo, - wantEnabled: &trueValue, + description: "Video channel enabled, returns true", + giveVideoEnabled: &trueValue, + giveChannelType: ChannelVideo, + wantEnabled: &trueValue, }, { - description: "Web integration setting unspecified, returns nil", - giveIntegrationType: IntegrationTypeWeb, - wantEnabled: nil, + description: "Web channel setting unspecified, returns nil", + giveChannelType: ChannelWeb, + wantEnabled: nil, }, { - description: "Web integration disabled, returns false", - giveWebEnabled: &falseValue, - giveIntegrationType: IntegrationTypeWeb, - wantEnabled: &falseValue, + description: "Web channel disabled, returns false", + giveWebEnabled: &falseValue, + giveChannelType: ChannelWeb, + wantEnabled: &falseValue, }, { - description: "Web integration enabled, returns true", - giveWebEnabled: &trueValue, - giveIntegrationType: IntegrationTypeWeb, - wantEnabled: &trueValue, + description: "Web channel enabled, returns true", + giveWebEnabled: &trueValue, + giveChannelType: ChannelWeb, + wantEnabled: &trueValue, }, } for _, tt := range tests { - accountIntegration := AccountIntegration{ + accountChannel := AccountChannel{ AMP: tt.giveAMPEnabled, App: tt.giveAppEnabled, Video: tt.giveVideoEnabled, Web: tt.giveWebEnabled, } - result := accountIntegration.GetByIntegrationType(tt.giveIntegrationType) + result := accountChannel.GetByChannelType(tt.giveChannelType) if tt.wantEnabled == nil { assert.Nil(t, result, tt.description) } else { @@ -245,11 +297,14 @@ func TestAccountIntegrationGetByIntegrationType(t *testing.T) { } func TestPurposeEnforced(t *testing.T) { + True := true + False := false + tests := []struct { description string givePurposeConfigNil bool - givePurpose1Enforced string - givePurpose2Enforced string + givePurpose1Enforced *bool + givePurpose2Enforced *bool givePurpose consentconstants.Purpose wantEnforced bool wantEnforcedSet bool @@ -263,28 +318,28 @@ func TestPurposeEnforced(t *testing.T) { }, { description: "Purpose 1 Enforced not set", - givePurpose1Enforced: "", + givePurpose1Enforced: nil, givePurpose: 1, wantEnforced: true, wantEnforcedSet: false, }, { description: "Purpose 1 Enforced set to full enforcement", - givePurpose1Enforced: TCF2FullEnforcement, + givePurpose1Enforced: &True, givePurpose: 1, wantEnforced: true, wantEnforcedSet: true, }, { description: "Purpose 1 Enforced set to no enforcement", - givePurpose1Enforced: TCF2NoEnforcement, + givePurpose1Enforced: &False, givePurpose: 1, wantEnforced: false, wantEnforcedSet: true, }, { description: "Purpose 2 Enforced set to full enforcement", - givePurpose2Enforced: TCF2FullEnforcement, + givePurpose2Enforced: &True, givePurpose: 2, wantEnforced: true, wantEnforcedSet: true, @@ -312,6 +367,74 @@ func TestPurposeEnforced(t *testing.T) { } } +func TestPurposeEnforcementAlgo(t *testing.T) { + tests := []struct { + description string + givePurposeConfigNil bool + givePurpose1Algo TCF2EnforcementAlgo + givePurpose2Algo TCF2EnforcementAlgo + givePurpose consentconstants.Purpose + wantAlgo TCF2EnforcementAlgo + wantAlgoSet bool + }{ + { + description: "Purpose config is nil", + givePurposeConfigNil: true, + givePurpose: 1, + wantAlgo: TCF2UndefinedEnforcement, + wantAlgoSet: false, + }, + { + description: "Purpose 1 enforcement algo is undefined", + givePurpose1Algo: TCF2UndefinedEnforcement, + givePurpose: 1, + wantAlgo: TCF2UndefinedEnforcement, + wantAlgoSet: false, + }, + { + description: "Purpose 1 enforcement algo set to basic", + givePurpose1Algo: TCF2BasicEnforcement, + givePurpose: 1, + wantAlgo: TCF2BasicEnforcement, + wantAlgoSet: true, + }, + { + description: "Purpose 1 enforcement algo set to full", + givePurpose1Algo: TCF2FullEnforcement, + givePurpose: 1, + wantAlgo: TCF2FullEnforcement, + wantAlgoSet: true, + }, + { + description: "Purpose 2 Enforcement algo set to basic", + givePurpose2Algo: TCF2BasicEnforcement, + givePurpose: 2, + wantAlgo: TCF2BasicEnforcement, + wantAlgoSet: true, + }, + } + + for _, tt := range tests { + accountGDPR := AccountGDPR{} + + if !tt.givePurposeConfigNil { + accountGDPR.PurposeConfigs = map[consentconstants.Purpose]*AccountGDPRPurpose{ + 1: { + EnforceAlgoID: tt.givePurpose1Algo, + }, + 2: { + EnforceAlgoID: tt.givePurpose2Algo, + }, + } + } + + value, present := accountGDPR.PurposeEnforcementAlgo(tt.givePurpose) + + assert.Equal(t, tt.wantAlgo, value, tt.description) + assert.Equal(t, tt.wantAlgoSet, present, tt.description) + } +} + func TestPurposeEnforcingVendors(t *testing.T) { tests := []struct { description string @@ -380,73 +503,52 @@ func TestPurposeEnforcingVendors(t *testing.T) { } } -func TestPurposeVendorException(t *testing.T) { +func TestPurposeVendorExceptions(t *testing.T) { tests := []struct { description string givePurposeConfigNil bool givePurpose1ExceptionMap map[openrtb_ext.BidderName]struct{} givePurpose2ExceptionMap map[openrtb_ext.BidderName]struct{} givePurpose consentconstants.Purpose - giveBidder openrtb_ext.BidderName - wantIsVendorException bool - wantVendorExceptionSet bool + wantExceptionMap map[openrtb_ext.BidderName]struct{} + wantExceptionMapSet bool }{ { - description: "Purpose config is nil", - givePurposeConfigNil: true, - givePurpose: 1, - giveBidder: "appnexus", - wantIsVendorException: false, - wantVendorExceptionSet: false, + description: "Purpose config is nil", + givePurposeConfigNil: true, + givePurpose: 1, + // wantExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + wantExceptionMap: nil, + wantExceptionMapSet: false, }, { - description: "Nil - exception map not defined for purpose", - givePurpose: 1, - giveBidder: "appnexus", - wantIsVendorException: false, - wantVendorExceptionSet: false, + description: "Nil - exception map not defined for purpose", + givePurpose: 1, + // wantExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + wantExceptionMap: nil, + wantExceptionMapSet: false, }, { description: "Empty - exception map empty for purpose", givePurpose: 1, givePurpose1ExceptionMap: map[openrtb_ext.BidderName]struct{}{}, - giveBidder: "appnexus", - wantIsVendorException: false, - wantVendorExceptionSet: true, + wantExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + wantExceptionMapSet: true, }, { - description: "One - bidder found in purpose exception map containing one entry", - givePurpose: 1, - givePurpose1ExceptionMap: map[openrtb_ext.BidderName]struct{}{"appnexus": {}}, - giveBidder: "appnexus", - wantIsVendorException: true, - wantVendorExceptionSet: true, - }, - { - description: "Many - bidder found in purpose exception map containing multiple entries", + description: "Nonempty - exception map with multiple entries for purpose", givePurpose: 1, givePurpose1ExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "index": {}}, - giveBidder: "appnexus", - wantIsVendorException: true, - wantVendorExceptionSet: true, + wantExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "index": {}}, + wantExceptionMapSet: true, }, { - description: "Many - bidder not found in purpose exception map containing multiple entries", - givePurpose: 1, - givePurpose1ExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "index": {}}, - givePurpose2ExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "openx": {}}, - giveBidder: "openx", - wantIsVendorException: false, - wantVendorExceptionSet: true, - }, - { - description: "Many - bidder found in different purpose exception map containing multiple entries", + description: "Nonempty - exception map with multiple entries for different purpose", givePurpose: 2, givePurpose1ExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "index": {}}, givePurpose2ExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "openx": {}}, - giveBidder: "openx", - wantIsVendorException: true, - wantVendorExceptionSet: true, + wantExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "openx": {}}, + wantExceptionMapSet: true, }, } @@ -464,10 +566,10 @@ func TestPurposeVendorException(t *testing.T) { } } - value, present := accountGDPR.PurposeVendorException(tt.givePurpose, tt.giveBidder) + value, present := accountGDPR.PurposeVendorExceptions(tt.givePurpose) - assert.Equal(t, tt.wantIsVendorException, value, tt.description) - assert.Equal(t, tt.wantVendorExceptionSet, present, tt.description) + assert.Equal(t, tt.wantExceptionMap, value, tt.description) + assert.Equal(t, tt.wantExceptionMapSet, present, tt.description) } } @@ -652,58 +754,73 @@ func TestPurposeOneTreatmentAccessAllowed(t *testing.T) { } } -func TestBasicEnforcementVendor(t *testing.T) { - tests := []struct { - description string - giveBasicVendorMap map[string]struct{} - giveBidder openrtb_ext.BidderName - wantBasicVendorSet bool - wantIsBasicVendor bool +func TestModulesGetConfig(t *testing.T) { + modules := AccountModules{ + "acme": { + "foo": json.RawMessage(`{"foo": "bar"}`), + "foo.bar": json.RawMessage(`{"foo": "bar"}`), + }, + "acme.foo": { + "baz": json.RawMessage(`{"foo": "bar"}`), + }, + } + + testCases := []struct { + description string + givenId string + givenModules AccountModules + expectedConfig json.RawMessage + expectedError error }{ { - description: "Nil - basic vendor map not defined", - giveBidder: "appnexus", - wantBasicVendorSet: false, - wantIsBasicVendor: false, + description: "Returns module config if found by ID", + givenId: "acme.foo", + givenModules: modules, + expectedConfig: json.RawMessage(`{"foo": "bar"}`), + expectedError: nil, }, { - description: "Empty - basic vendor map empty", - giveBasicVendorMap: map[string]struct{}{}, - giveBidder: "appnexus", - wantBasicVendorSet: true, - wantIsBasicVendor: false, + description: "Returns module config if found by ID", + givenId: "acme.foo.bar", + givenModules: modules, + expectedConfig: json.RawMessage(`{"foo": "bar"}`), + expectedError: nil, }, { - description: "One - bidder found in basic vendor map containing one entry", - giveBasicVendorMap: map[string]struct{}{"appnexus": {}}, - giveBidder: "appnexus", - wantBasicVendorSet: true, - wantIsBasicVendor: true, + description: "Returns nil config if wrong ID provided", + givenId: "invalid_id", + givenModules: modules, + expectedConfig: nil, + expectedError: errors.New("ID must consist of vendor and module names separated by dot, got: invalid_id"), }, { - description: "Many - bidder found in basic vendor map containing multiple entries", - giveBasicVendorMap: map[string]struct{}{"rubicon": {}, "appnexus": {}, "index": {}}, - giveBidder: "appnexus", - wantBasicVendorSet: true, - wantIsBasicVendor: true, + description: "Returns nil config if no matching module exists", + givenId: "acme.bar", + givenModules: modules, + expectedConfig: nil, + expectedError: nil, }, { - description: "Many - bidder not found in basic vendor map containing multiple entries", - giveBasicVendorMap: map[string]struct{}{"rubicon": {}, "appnexus": {}, "index": {}}, - giveBidder: "openx", - wantBasicVendorSet: true, - wantIsBasicVendor: false, + description: "Returns nil config if no matching module exists", + givenId: "acme.foo.baz", + givenModules: modules, + expectedConfig: nil, + expectedError: nil, + }, + { + description: "Returns nil config if no module configs defined in account", + givenId: "acme.foo", + givenModules: nil, + expectedConfig: nil, + expectedError: nil, }, } - for _, tt := range tests { - accountGDPR := AccountGDPR{ - BasicEnforcementVendorsMap: tt.giveBasicVendorMap, - } - - value, present := accountGDPR.BasicEnforcementVendor(tt.giveBidder) - - assert.Equal(t, tt.wantIsBasicVendor, value, tt.description) - assert.Equal(t, tt.wantBasicVendorSet, present, tt.description) + for _, test := range testCases { + t.Run(test.description, func(t *testing.T) { + gotConfig, err := test.givenModules.ModuleConfig(test.givenId) + assert.Equal(t, test.expectedError, err) + assert.Equal(t, test.expectedConfig, gotConfig) + }) } } diff --git a/config/adapter.go b/config/adapter.go index eae47981a70..6a6a5fbef71 100644 --- a/config/adapter.go +++ b/config/adapter.go @@ -1,83 +1,13 @@ package config -import ( - "fmt" - "text/template" - - validator "github.com/asaskevich/govalidator" - "github.com/prebid/prebid-server/macros" -) - type Adapter struct { - 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"` + Endpoint string + ExtraAdapterInfo string // needed for Rubicon - XAPI AdapterXAPI `mapstructure:"xapi"` + XAPI AdapterXAPI // needed for Facebook - PlatformID string `mapstructure:"platform_id"` - AppSecret string `mapstructure:"app_secret"` -} - -type AdapterXAPI struct { - Username string `mapstructure:"username"` - Password string `mapstructure:"password"` - Tracker string `mapstructure:"tracker"` -} - -// validateAdapters validates adapter's endpoint and user sync URL -func validateAdapters(adapterMap map[string]Adapter, errs []error) []error { - for adapterName, adapter := range adapterMap { - if !adapter.Disabled { - errs = validateAdapterEndpoint(adapter.Endpoint, adapterName, errs) - } - } - return errs -} - -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 -func validateAdapterEndpoint(endpoint string, adapterName string, errs []error) []error { - if endpoint == "" { - return append(errs, fmt.Errorf("There's no default endpoint available for %s. Calls to this bidder/exchange will fail. "+ - "Please set adapters.%s.endpoint in your app config", adapterName, adapterName)) - } - - // Create endpoint template - endpointTemplate, err := template.New("endpointTemplate").Parse(endpoint) - if err != nil { - 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, testEndpointTemplateParams) - if err != nil { - return append(errs, fmt.Errorf("Unable to resolve endpoint: %s for adapter: %s. %v", endpoint, adapterName, err)) - } - // Validate the resolved endpoint - // - // 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(resolvedEndpoint) || !validator.IsRequestURL(resolvedEndpoint) { - errs = append(errs, fmt.Errorf("The endpoint: %s for %s is not a valid URL", resolvedEndpoint, adapterName)) - } - return errs + PlatformID string + AppSecret string } diff --git a/config/bidderinfo.go b/config/bidderinfo.go index 0892c0f1685..da606a03318 100644 --- a/config/bidderinfo.go +++ b/config/bidderinfo.go @@ -1,11 +1,20 @@ package config import ( + "errors" "fmt" - "io/ioutil" + "log" + "os" + "path/filepath" "strings" + "text/template" + "github.com/golang/glog" + "github.com/prebid/prebid-server/macros" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/util/sliceutil" + + validator "github.com/asaskevich/govalidator" "gopkg.in/yaml.v3" ) @@ -14,34 +23,68 @@ type BidderInfos map[string]BidderInfo // 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"` - GVLVendorID uint16 `yaml:"gvlVendorID"` - Syncer *Syncer `yaml:"userSync"` + Disabled bool `yaml:"disabled" mapstructure:"disabled"` + Endpoint string `yaml:"endpoint" mapstructure:"endpoint"` + ExtraAdapterInfo string `yaml:"extra_info" mapstructure:"extra_info"` + + Maintainer *MaintainerInfo `yaml:"maintainer" mapstructure:"maintainer"` + Capabilities *CapabilitiesInfo `yaml:"capabilities" mapstructure:"capabilities"` + ModifyingVastXmlAllowed bool `yaml:"modifyingVastXmlAllowed" mapstructure:"modifyingVastXmlAllowed"` + Debug *DebugInfo `yaml:"debug" mapstructure:"debug"` + GVLVendorID uint16 `yaml:"gvlVendorID" mapstructure:"gvlVendorID"` + + Syncer *Syncer `yaml:"userSync" mapstructure:"userSync"` + + Experiment BidderInfoExperiment `yaml:"experiment" mapstructure:"experiment"` + + // needed for backwards compatibility + UserSyncURL string `yaml:"usersync_url" mapstructure:"usersync_url"` + + // needed for Rubicon + XAPI AdapterXAPI `yaml:"xapi" mapstructure:"xapi"` + + // needed for Facebook + PlatformID string `yaml:"platform_id" mapstructure:"platform_id"` + AppSecret string `yaml:"app_secret" mapstructure:"app_secret"` + // EndpointCompression determines, if set, the type of compression the bid request will undergo before being sent to the corresponding bid server + EndpointCompression string `yaml:"endpointCompression" mapstructure:"endpointCompression"` +} + +// BidderInfoExperiment specifies non-production ready feature config for a bidder +type BidderInfoExperiment struct { + AdsCert BidderAdsCert `yaml:"adsCert" mapstructure:"adsCert"` +} + +// BidderAdsCert enables Call Sign feature for bidder +type BidderAdsCert struct { + Enabled bool `yaml:"enabled" mapstructure:"enabled"` } // MaintainerInfo specifies the support email address for a bidder. type MaintainerInfo struct { - Email string `yaml:"email"` + Email string `yaml:"email" mapstructure:"email"` } // CapabilitiesInfo specifies the supported platforms for a bidder. type CapabilitiesInfo struct { - App *PlatformInfo `yaml:"app"` - Site *PlatformInfo `yaml:"site"` + App *PlatformInfo `yaml:"app" mapstructure:"app"` + Site *PlatformInfo `yaml:"site" mapstructure:"site"` } // PlatformInfo specifies the supported media types for a bidder. type PlatformInfo struct { - MediaTypes []openrtb_ext.BidType `yaml:"mediaTypes"` + MediaTypes []openrtb_ext.BidType `yaml:"mediaTypes" mapstructure:"mediaTypes"` } // DebugInfo specifies the supported debug options for a bidder. type DebugInfo struct { - Allow bool `yaml:"allow"` + Allow bool `yaml:"allow" mapstructure:"allow"` +} + +type AdapterXAPI struct { + Username string `yaml:"username" mapstructure:"username"` + Password string `yaml:"password" mapstructure:"password"` + Tracker string `yaml:"tracker" mapstructure:"tracker"` } // Syncer specifies the user sync settings for a bidder. This struct is shared by the account config, @@ -54,7 +97,7 @@ type Syncer struct { // 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"` + Supports []string `yaml:"supports" mapstructure:"supports"` // IFrame configures an iframe endpoint for user syncing. IFrame *SyncerEndpoint `yaml:"iframe" mapstructure:"iframe"` @@ -70,46 +113,6 @@ type Syncer struct { 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 original == nil { - copy.IFrame = s.IFrame.Override(nil) - copy.Redirect = s.Redirect.Override(nil) - } else { - copy.IFrame = s.IFrame.Override(original.IFrame) - copy.Redirect = s.Redirect.Override(original.Redirect) - } - - if s.ExternalURL != "" { - copy.ExternalURL = s.ExternalURL - } - - 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. @@ -121,8 +124,8 @@ func (s *Syncer) Override(original *Syncer) *Syncer { // 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" +// 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 @@ -165,94 +168,387 @@ type SyncerEndpoint struct { 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 +func (bi BidderInfo) IsEnabled() bool { + return !bi.Disabled +} + +type InfoReader interface { + Read() (map[string][]byte, error) +} + +type InfoReaderFromDisk struct { + Path string +} + +func (r InfoReaderFromDisk) Read() (map[string][]byte, error) { + bidderConfigs, err := os.ReadDir(r.Path) + if err != nil { + log.Fatal(err) } - var copy SyncerEndpoint - if original != nil { - copy = *original + bidderInfos := make(map[string][]byte) + for _, bidderConfig := range bidderConfigs { + if bidderConfig.IsDir() { + continue //ignore directories + } + fileName := bidderConfig.Name() + filePath := filepath.Join(r.Path, fileName) + data, err := os.ReadFile(filePath) + if err != nil { + return nil, err + } + bidderInfos[fileName] = data } + return bidderInfos, nil +} - if s == nil { - return © +func LoadBidderInfoFromDisk(path string) (BidderInfos, error) { + bidderInfoReader := InfoReaderFromDisk{Path: path} + return LoadBidderInfo(bidderInfoReader) +} + +func LoadBidderInfo(reader InfoReader) (BidderInfos, error) { + return processBidderInfos(reader, openrtb_ext.NormalizeBidderName) +} + +func processBidderInfos(reader InfoReader, normalizeBidderName func(string) (openrtb_ext.BidderName, bool)) (BidderInfos, error) { + bidderConfigs, err := reader.Read() + if err != nil { + return nil, fmt.Errorf("error loading bidders data") } - if s.URL != "" { - copy.URL = s.URL + infos := BidderInfos{} + + for fileName, data := range bidderConfigs { + bidderName := strings.Split(fileName, ".") + if len(bidderName) == 2 && bidderName[1] == "yaml" { + normalizedBidderName, bidderNameExists := normalizeBidderName(bidderName[0]) + if !bidderNameExists { + return nil, fmt.Errorf("error parsing config for bidder %s: unknown bidder", fileName) + } + info := BidderInfo{} + if err := yaml.Unmarshal(data, &info); err != nil { + return nil, fmt.Errorf("error parsing config for bidder %s: %v", fileName, err) + } + + infos[string(normalizedBidderName)] = info + } } + return infos, nil +} - if s.RedirectURL != "" { - copy.RedirectURL = s.RedirectURL +// ToGVLVendorIDMap transforms a BidderInfos object to a map of bidder names to GVL id. +// Disabled bidders are omitted from the result. +func (infos BidderInfos) ToGVLVendorIDMap() map[openrtb_ext.BidderName]uint16 { + gvlVendorIds := make(map[openrtb_ext.BidderName]uint16, len(infos)) + for name, info := range infos { + if info.IsEnabled() && info.GVLVendorID != 0 { + gvlVendorIds[openrtb_ext.BidderName(name)] = info.GVLVendorID + } } + return gvlVendorIds +} - if s.ExternalURL != "" { - copy.ExternalURL = s.ExternalURL +// validateBidderInfos validates bidder endpoint, info and syncer data +func (infos BidderInfos) validate(errs []error) []error { + for bidderName, bidder := range infos { + if bidder.IsEnabled() { + errs = validateAdapterEndpoint(bidder.Endpoint, bidderName, errs) + + validateInfoErr := validateInfo(bidder, bidderName) + if validateInfoErr != nil { + errs = append(errs, validateInfoErr) + } + + validateSyncerErr := validateSyncer(bidder) + if validateSyncerErr != nil { + errs = append(errs, validateSyncerErr) + } + } } + return errs +} - if s.UserMacro != "" { - copy.UserMacro = s.UserMacro +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 +func validateAdapterEndpoint(endpoint string, bidderName string, errs []error) []error { + if endpoint == "" { + return append(errs, fmt.Errorf("There's no default endpoint available for %s. Calls to this bidder/exchange will fail. "+ + "Please set adapters.%s.endpoint in your app config", bidderName, bidderName)) } - return © + // Create endpoint template + endpointTemplate, err := template.New("endpointTemplate").Parse(endpoint) + if err != nil { + return append(errs, fmt.Errorf("Invalid endpoint template: %s for adapter: %s. %v", endpoint, bidderName, err)) + } + // Resolve macros (if any) in the endpoint URL + resolvedEndpoint, err := macros.ResolveMacros(endpointTemplate, testEndpointTemplateParams) + if err != nil { + return append(errs, fmt.Errorf("Unable to resolve endpoint: %s for adapter: %s. %v", endpoint, bidderName, err)) + } + // Validate the resolved endpoint + // + // 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(resolvedEndpoint) || !validator.IsRequestURL(resolvedEndpoint) { + errs = append(errs, fmt.Errorf("The endpoint: %s for %s is not a valid URL", resolvedEndpoint, bidderName)) + } + return errs } -// 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} - return loadBidderInfo(reader, adapterConfigs, bidders) +func validateInfo(info BidderInfo, bidderName string) error { + if err := validateMaintainer(info.Maintainer, bidderName); err != nil { + return err + } + if err := validateCapabilities(info.Capabilities, bidderName); err != nil { + return err + } + + return nil } -func loadBidderInfo(r infoReader, adapterConfigs map[string]Adapter, bidders []string) (BidderInfos, error) { - infos := BidderInfos{} +func validateMaintainer(info *MaintainerInfo, bidderName string) error { + if info == nil || info.Email == "" { + return fmt.Errorf("missing required field: maintainer.email for adapter: %s", bidderName) + } + return nil +} - for _, bidder := range bidders { - data, err := r.Read(bidder) - if err != nil { - return nil, err +func validateCapabilities(info *CapabilitiesInfo, bidderName string) error { + if info == nil { + return fmt.Errorf("missing required field: capabilities for adapter: %s", bidderName) + } + + if info.App == nil && info.Site == nil { + return fmt.Errorf("at least one of capabilities.site or capabilities.app must exist for adapter: %s", bidderName) + } + + if info.App != nil { + if err := validatePlatformInfo(info.App); err != nil { + return fmt.Errorf("capabilities.app failed validation: %v for adapter: %s", err, bidderName) } + } - info := BidderInfo{} - if err := yaml.Unmarshal(data, &info); err != nil { - return nil, fmt.Errorf("error parsing yaml for bidder %s: %v", bidder, err) + if info.Site != nil { + if err := validatePlatformInfo(info.Site); err != nil { + return fmt.Errorf("capabilities.site failed validation: %v, for adapter: %s", err, bidderName) } + } + return nil +} - info.Enabled = isEnabledByConfig(adapterConfigs, bidder) - infos[bidder] = info +func validatePlatformInfo(info *PlatformInfo) error { + if len(info.MediaTypes) == 0 { + return errors.New("at least one media type needs to be specified") } - return infos, nil -} + for index, mediaType := range info.MediaTypes { + if mediaType != "banner" && mediaType != "video" && mediaType != "native" && mediaType != "audio" { + return fmt.Errorf("unrecognized media type at index %d: %s", index, mediaType) + } + } -func isEnabledByConfig(adapterConfigs map[string]Adapter, bidderName string) bool { - a, ok := adapterConfigs[strings.ToLower(bidderName)] - return ok && !a.Disabled + return nil } -type infoReader interface { - Read(bidder string) ([]byte, error) +func validateSyncer(bidderInfo BidderInfo) error { + if bidderInfo.Syncer == nil { + return nil + } + + for _, supports := range bidderInfo.Syncer.Supports { + if !strings.EqualFold(supports, "iframe") && !strings.EqualFold(supports, "redirect") { + return fmt.Errorf("syncer could not be created, invalid supported endpoint: %s", supports) + } + } + + return nil } -type infoReaderFromDisk struct { - path string +func applyBidderInfoConfigOverrides(configBidderInfos BidderInfos, fsBidderInfos BidderInfos, normalizeBidderName func(string) (openrtb_ext.BidderName, bool)) (BidderInfos, error) { + for bidderName, bidderInfo := range configBidderInfos { + normalizedBidderName, bidderNameExists := normalizeBidderName(bidderName) + if !bidderNameExists { + return nil, fmt.Errorf("error setting configuration for bidder %s: unknown bidder", bidderName) + } + if fsBidderCfg, exists := fsBidderInfos[string(normalizedBidderName)]; exists { + bidderInfo.Syncer = bidderInfo.Syncer.Override(fsBidderCfg.Syncer) + + if bidderInfo.Endpoint == "" && len(fsBidderCfg.Endpoint) > 0 { + bidderInfo.Endpoint = fsBidderCfg.Endpoint + } + if bidderInfo.ExtraAdapterInfo == "" && len(fsBidderCfg.ExtraAdapterInfo) > 0 { + bidderInfo.ExtraAdapterInfo = fsBidderCfg.ExtraAdapterInfo + } + if bidderInfo.Maintainer == nil && fsBidderCfg.Maintainer != nil { + bidderInfo.Maintainer = fsBidderCfg.Maintainer + } + if bidderInfo.Capabilities == nil && fsBidderCfg.Capabilities != nil { + bidderInfo.Capabilities = fsBidderCfg.Capabilities + } + if bidderInfo.Debug == nil && fsBidderCfg.Debug != nil { + bidderInfo.Debug = fsBidderCfg.Debug + } + if bidderInfo.GVLVendorID == 0 && fsBidderCfg.GVLVendorID > 0 { + bidderInfo.GVLVendorID = fsBidderCfg.GVLVendorID + } + if bidderInfo.XAPI.Username == "" && fsBidderCfg.XAPI.Username != "" { + bidderInfo.XAPI.Username = fsBidderCfg.XAPI.Username + } + if bidderInfo.XAPI.Password == "" && fsBidderCfg.XAPI.Password != "" { + bidderInfo.XAPI.Password = fsBidderCfg.XAPI.Password + } + if bidderInfo.XAPI.Tracker == "" && fsBidderCfg.XAPI.Tracker != "" { + bidderInfo.XAPI.Tracker = fsBidderCfg.XAPI.Tracker + } + if bidderInfo.PlatformID == "" && fsBidderCfg.PlatformID != "" { + bidderInfo.PlatformID = fsBidderCfg.PlatformID + } + if bidderInfo.AppSecret == "" && fsBidderCfg.AppSecret != "" { + bidderInfo.AppSecret = fsBidderCfg.AppSecret + } + if bidderInfo.EndpointCompression == "" && fsBidderCfg.EndpointCompression != "" { + bidderInfo.EndpointCompression = fsBidderCfg.EndpointCompression + } + + // 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 bidderInfo.UserSyncURL != "" { + if fsBidderCfg.Syncer == nil { + return nil, 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 = bidderInfo.UserSyncURL + endpointsCount++ + } + if bidderInfo.Syncer.Redirect != nil { + bidderInfo.Syncer.Redirect.URL = bidderInfo.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 = &SyncerEndpoint{URL: bidderInfo.UserSyncURL} + endpointsCount++ + } + if sliceutil.ContainsStringIgnoreCase(bidderInfo.Syncer.Supports, "redirect") { + bidderInfo.Syncer.Redirect = &SyncerEndpoint{URL: bidderInfo.UserSyncURL} + endpointsCount++ + } + } + + if endpointsCount == 0 { + return nil, 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 nil, 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)) + } + + fsBidderInfos[string(normalizedBidderName)] = bidderInfo + } else { + return nil, fmt.Errorf("error finding configuration for bidder %s: unknown bidder", bidderName) + } + } + return fsBidderInfos, nil } -func (r infoReaderFromDisk) Read(bidder string) ([]byte, error) { - path := fmt.Sprintf("%v/%v.yaml", r.path, bidder) - return ioutil.ReadFile(path) +// 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 original == nil { + copy.IFrame = s.IFrame.Override(nil) + copy.Redirect = s.Redirect.Override(nil) + } else { + copy.IFrame = s.IFrame.Override(original.IFrame) + copy.Redirect = s.Redirect.Override(original.Redirect) + } + + if s.ExternalURL != "" { + copy.ExternalURL = s.ExternalURL + } + + if s.SupportCORS != nil { + copy.SupportCORS = s.SupportCORS + } + + return © } -// ToGVLVendorIDMap transforms a BidderInfos object to a map of bidder names to GVL id. Disabled -// bidders are omitted from the result. -func (infos BidderInfos) ToGVLVendorIDMap() map[openrtb_ext.BidderName]uint16 { - m := make(map[openrtb_ext.BidderName]uint16, len(infos)) - for name, info := range infos { - if info.Enabled && info.GVLVendorID != 0 { - m[openrtb_ext.BidderName(name)] = info.GVLVendorID - } +// 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 } - return m + + 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 © } diff --git a/config/bidderinfo_test.go b/config/bidderinfo_test.go index df5b8591525..462e920f3f0 100644 --- a/config/bidderinfo_test.go +++ b/config/bidderinfo_test.go @@ -2,6 +2,7 @@ package config import ( "errors" + "gopkg.in/yaml.v3" "strings" "testing" @@ -9,28 +10,52 @@ import ( "github.com/stretchr/testify/assert" ) -const testInfoFilesPath = "./test/bidder-info" +const testInfoFilesPathValid = "./test/bidder-info-valid" const testSimpleYAML = ` maintainer: email: "some-email@domain.com" gvlVendorID: 42 ` +const fullBidderYAMLConfig = ` +maintainer: + email: "some-email@domain.com" +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native +modifyingVastXmlAllowed: true +debug: + allow: true +gvlVendorID: 42 +experiment: + adsCert: + enabled: true +endpointCompression: "GZIP" +` func TestLoadBidderInfoFromDisk(t *testing.T) { - bidder := "someBidder" + // should appear in result in mixed case + bidder := "stroeerCore" trueValue := true adapterConfigs := make(map[string]Adapter) adapterConfigs[strings.ToLower(bidder)] = Adapter{} - infos, err := LoadBidderInfoFromDisk(testInfoFilesPath, adapterConfigs, []string{bidder}) + infos, err := LoadBidderInfoFromDisk(testInfoFilesPathValid) if err != nil { t.Fatal(err) } expected := BidderInfos{ bidder: { - Enabled: true, + Disabled: false, Maintainer: &MaintainerInfo{ Email: "some-email@domain.com", }, @@ -64,98 +89,454 @@ func TestLoadBidderInfoFromDisk(t *testing.T) { assert.Equal(t, expected, infos) } -func TestLoadBidderInfo(t *testing.T) { - bidder := "someBidder" // important to be mixed case for tests - +func TestProcessBidderInfo(t *testing.T) { testCases := []struct { - description string - givenConfigs map[string]Adapter - givenContent string - givenError error - expectedInfo BidderInfos - expectedError string + description string + bidderInfos map[string][]byte + expectedBidderInfos BidderInfos + expectError string }{ { - description: "Enabled", - givenConfigs: map[string]Adapter{strings.ToLower(bidder): {}}, - givenContent: testSimpleYAML, - expectedInfo: map[string]BidderInfo{ - bidder: { - Enabled: true, + description: "Valid bidder info", + bidderInfos: map[string][]byte{ + "bidderA.yaml": []byte(testSimpleYAML), + }, + expectedBidderInfos: BidderInfos{ + "bidderA": BidderInfo{ Maintainer: &MaintainerInfo{ Email: "some-email@domain.com", }, GVLVendorID: 42, }, }, + expectError: "", + }, + { + description: "Bidder doesn't exist in bidder info list", + bidderInfos: map[string][]byte{ + "unknown.yaml": []byte(testSimpleYAML), + }, + expectedBidderInfos: nil, + expectError: "error parsing config for bidder unknown.yaml", + }, + { + description: "Invalid bidder config", + bidderInfos: map[string][]byte{ + "bidderA.yaml": []byte("invalid bidder config"), + }, + expectedBidderInfos: nil, + expectError: "error parsing config for bidder bidderA.yaml", + }, + } + for _, test := range testCases { + reader := StubInfoReader{test.bidderInfos} + bidderInfos, err := processBidderInfos(reader, mockNormalizeBidderName) + if test.expectError != "" { + assert.ErrorContains(t, err, test.expectError, "") + } else { + assert.Equal(t, test.expectedBidderInfos, bidderInfos, "incorrect bidder infos for test case: %s", test.description) + } + + } + +} + +type StubInfoReader struct { + mockBidderInfos map[string][]byte +} + +func (r StubInfoReader) Read() (map[string][]byte, error) { + return r.mockBidderInfos, nil +} + +var testBidderNames = map[string]openrtb_ext.BidderName{ + "biddera": openrtb_ext.BidderName("bidderA"), + "bidderb": openrtb_ext.BidderName("bidderB"), + "bidder1": openrtb_ext.BidderName("bidder1"), + "bidder2": openrtb_ext.BidderName("bidder2"), + "a": openrtb_ext.BidderName("a"), +} + +func mockNormalizeBidderName(name string) (openrtb_ext.BidderName, bool) { + nameLower := strings.ToLower(name) + bidderName, exists := testBidderNames[nameLower] + return bidderName, exists +} + +func TestToGVLVendorIDMap(t *testing.T) { + givenBidderInfos := BidderInfos{ + "bidderA": BidderInfo{Disabled: false, GVLVendorID: 0}, + "bidderB": BidderInfo{Disabled: false, GVLVendorID: 100}, + "bidderC": BidderInfo{Disabled: true, GVLVendorID: 0}, + "bidderD": BidderInfo{Disabled: true, GVLVendorID: 200}, + } + + expectedGVLVendorIDMap := map[openrtb_ext.BidderName]uint16{ + "bidderB": 100, + } + + result := givenBidderInfos.ToGVLVendorIDMap() + assert.Equal(t, expectedGVLVendorIDMap, result) +} + +const bidderInfoRelativePath = "../static/bidder-info" + +// TestBidderInfoFiles ensures each bidder has a valid static/bidder-info/bidder.yaml file. Validation is performed directly +// against the file system with separate yaml unmarshalling from the LoadBidderInfo func. +func TestBidderInfoFiles(t *testing.T) { + _, err := LoadBidderInfoFromDisk(bidderInfoRelativePath) + if err != nil { + assert.Fail(t, err.Error(), "Errors in bidder info files") + } +} + +func TestBidderInfoValidationPositive(t *testing.T) { + bidderInfos := BidderInfos{ + "bidderA": BidderInfo{ + Endpoint: "http://bidderA.com/openrtb2", + PlatformID: "A", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderA.com", + }, + GVLVendorID: 1, + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeVideo, + openrtb_ext.BidTypeNative, + openrtb_ext.BidTypeBanner, + }, + }, + }, + Syncer: &Syncer{ + Key: "bidderAkey", + Redirect: &SyncerEndpoint{ + URL: "http://bidderA.com/usersync", + UserMacro: "UID", + }, + }, + }, + "bidderB": BidderInfo{ + Endpoint: "http://bidderB.com/openrtb2", + PlatformID: "B", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderA.com", + }, + GVLVendorID: 2, + Capabilities: &CapabilitiesInfo{ + Site: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeVideo, + openrtb_ext.BidTypeNative, + openrtb_ext.BidTypeBanner, + }, + }, + }, + Syncer: &Syncer{ + Key: "bidderBkey", + Redirect: &SyncerEndpoint{ + URL: "http://bidderB.com/usersync", + UserMacro: "UID", + }, + }, }, + } + errs := bidderInfos.validate(make([]error, 0)) + assert.Len(t, errs, 0, "All bidder infos should be correct") +} + +func TestBidderInfoValidationNegative(t *testing.T) { + testCases := []struct { + description string + bidderInfos BidderInfos + expectErrors []error + }{ { - description: "Disabled - Bidder Not Configured", - givenConfigs: map[string]Adapter{}, - givenContent: testSimpleYAML, - expectedInfo: map[string]BidderInfo{ - bidder: { - Enabled: false, + "One bidder incorrect url", + BidderInfos{ + "bidderA": BidderInfo{ + Endpoint: "incorrect", Maintainer: &MaintainerInfo{ - Email: "some-email@domain.com", + Email: "maintainer@bidderA.com", + }, + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeVideo, + }, + }, }, - GVLVendorID: 42, }, }, + []error{ + errors.New("The endpoint: incorrect for bidderA is not a valid URL"), + }, }, { - description: "Disabled - Bidder Wrong Case", - givenConfigs: map[string]Adapter{bidder: {}}, - givenContent: testSimpleYAML, - expectedInfo: map[string]BidderInfo{ - bidder: { - Enabled: false, + "One bidder empty url", + BidderInfos{ + "bidderA": BidderInfo{ + Endpoint: "", Maintainer: &MaintainerInfo{ - Email: "some-email@domain.com", + Email: "maintainer@bidderA.com", + }, + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeVideo, + }, + }, }, - GVLVendorID: 42, }, }, + []error{ + errors.New("There's no default endpoint available for bidderA. Calls to this bidder/exchange will fail. Please set adapters.bidderA.endpoint in your app config"), + }, }, { - description: "Disabled - Explicitly Configured", - givenConfigs: map[string]Adapter{strings.ToLower(bidder): {Disabled: false}}, - givenContent: testSimpleYAML, - expectedInfo: map[string]BidderInfo{ - bidder: { - Enabled: true, + "One bidder incorrect url template", + BidderInfos{ + "bidderA": BidderInfo{ + Endpoint: "http://bidderA.com/openrtb2/getuid?{{.incorrect}}", Maintainer: &MaintainerInfo{ - Email: "some-email@domain.com", + Email: "maintainer@bidderA.com", }, - GVLVendorID: 42, + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeVideo, + }, + }, + }, + }, + }, + []error{ + errors.New("Unable to resolve endpoint: http://bidderA.com/openrtb2/getuid?{{.incorrect}} for adapter: bidderA. template: endpointTemplate:1:37: executing \"endpointTemplate\" at <.incorrect>: can't evaluate field incorrect in type macros.EndpointTemplateParams"), + }, + }, + { + "One bidder incorrect url template parameters", + BidderInfos{ + "bidderA": BidderInfo{ + Endpoint: "http://bidderA.com/openrtb2/getuid?r=[{{.]RedirectURL}}", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderA.com", + }, + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeVideo, + }, + }, + }, + }, + }, + []error{ + errors.New("Invalid endpoint template: http://bidderA.com/openrtb2/getuid?r=[{{.]RedirectURL}} for adapter: bidderA. template: endpointTemplate:1: bad character U+005D ']'"), + }, + }, + { + "One bidder no maintainer", + BidderInfos{ + "bidderA": BidderInfo{ + Endpoint: "http://bidderA.com/openrtb2", + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeVideo, + }, + }, + }, + }, + }, + []error{ + errors.New("missing required field: maintainer.email for adapter: bidderA"), + }, + }, + { + "One bidder missing maintainer email", + BidderInfos{ + "bidderA": BidderInfo{ + Endpoint: "http://bidderA.com/openrtb2", + Maintainer: &MaintainerInfo{ + Email: "", + }, + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeVideo, + }, + }, + }, + }, + }, + []error{ + errors.New("missing required field: maintainer.email for adapter: bidderA"), + }, + }, + { + "One bidder missing capabilities", + BidderInfos{ + "bidderA": BidderInfo{ + Endpoint: "http://bidderA.com/openrtb2", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderA.com", + }, + }, + }, + []error{ + errors.New("missing required field: capabilities for adapter: bidderA"), + }, + }, + { + "One bidder missing capabilities site and app", + BidderInfos{ + "bidderA": BidderInfo{ + Endpoint: "http://bidderA.com/openrtb2", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderA.com", + }, + Capabilities: &CapabilitiesInfo{}, + }, + }, + []error{ + errors.New("at least one of capabilities.site or capabilities.app must exist for adapter: bidderA"), + }, + }, + { + "One bidder incorrect capabilities for app", + BidderInfos{ + "bidderA": BidderInfo{ + Endpoint: "http://bidderA.com/openrtb2", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderA.com", + }, + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + "incorrect", + }, + }, + }, + }, + }, + []error{ + errors.New("capabilities.app failed validation: unrecognized media type at index 0: incorrect for adapter: bidderA"), + }, + }, + { + "One bidder nil capabilities", + BidderInfos{ + "bidderA": BidderInfo{ + Endpoint: "http://bidderA.com/openrtb2", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderA.com", + }, + Capabilities: nil, }, }, + []error{ + errors.New("missing required field: capabilities for adapter: bidderA"), + }, }, { - description: "Read Error", - givenConfigs: map[string]Adapter{strings.ToLower(bidder): {}}, - givenError: errors.New("any read error"), - expectedError: "any read error", + "One bidder invalid syncer", + BidderInfos{ + "bidderA": BidderInfo{ + Endpoint: "http://bidderA.com/openrtb2", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderA.com", + }, + Capabilities: &CapabilitiesInfo{ + Site: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeVideo, + }, + }, + }, + Syncer: &Syncer{ + Supports: []string{"incorrect"}, + }, + }, + }, + []error{ + errors.New("syncer could not be created, invalid supported endpoint: incorrect"), + }, }, { - description: "Unmarshal Error", - givenConfigs: map[string]Adapter{strings.ToLower(bidder): {}}, - givenContent: "invalid yaml", - expectedError: "error parsing yaml for bidder someBidder: yaml: unmarshal errors:\n line 1: cannot unmarshal !!str `invalid...` into config.BidderInfo", + "Two bidders, one with incorrect url", + BidderInfos{ + "bidderA": BidderInfo{ + Endpoint: "incorrect", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderA.com", + }, + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeVideo, + }, + }, + }, + }, + "bidderB": BidderInfo{ + Endpoint: "http://bidderB.com/openrtb2", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderB.com", + }, + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeVideo, + }, + }, + }, + }, + }, + []error{ + errors.New("The endpoint: incorrect for bidderA is not a valid URL"), + }, + }, + { + "Two bidders, both with incorrect url", + BidderInfos{ + "bidderA": BidderInfo{ + Endpoint: "incorrect", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderA.com", + }, + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeVideo, + }, + }, + }, + }, + "bidderB": BidderInfo{ + Endpoint: "incorrect", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderB.com", + }, + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeVideo, + }, + }, + }, + }, + }, + []error{ + errors.New("The endpoint: incorrect for bidderA is not a valid URL"), + errors.New("The endpoint: incorrect for bidderB is not a valid URL"), + }, }, } for _, test := range testCases { - r := fakeInfoReader{test.givenContent, test.givenError} - info, err := loadBidderInfo(r, test.givenConfigs, []string{bidder}) - - if test.expectedError == "" { - assert.NoError(t, err, test.description) - } else { - assert.EqualError(t, err, test.expectedError, test.description) - } - - assert.Equal(t, test.expectedInfo, info, test.description) + errs := test.bidderInfos.validate(make([]error, 0)) + assert.ElementsMatch(t, errs, test.expectErrors, "incorrect errors returned for test: %s", test.description) } } @@ -320,27 +701,296 @@ func TestSyncerEndpointOverride(t *testing.T) { } } -type fakeInfoReader struct { - content string - err error -} +func TestApplyBidderInfoConfigSyncerOverrides(t *testing.T) { + var testCases = []struct { + description string + givenFsBidderInfos BidderInfos + givenConfigBidderInfos BidderInfos + expectedError string + expectedBidderInfos BidderInfos + }{ + { + description: "Syncer Override", + givenFsBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "original"}}}, + givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + expectedBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + }, + { + description: "UserSyncURL Override IFrame", + givenFsBidderInfos: BidderInfos{"a": {Syncer: &Syncer{IFrame: &SyncerEndpoint{URL: "original"}}}}, + givenConfigBidderInfos: BidderInfos{"a": {UserSyncURL: "override"}}, + expectedBidderInfos: BidderInfos{"a": {UserSyncURL: "override", Syncer: &Syncer{IFrame: &SyncerEndpoint{URL: "override"}}}}, + }, + { + description: "UserSyncURL Supports IFrame", + givenFsBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Supports: []string{"iframe"}}}}, + givenConfigBidderInfos: BidderInfos{"a": {UserSyncURL: "override"}}, + expectedBidderInfos: BidderInfos{"a": {UserSyncURL: "override", Syncer: &Syncer{Supports: []string{"iframe"}, IFrame: &SyncerEndpoint{URL: "override"}}}}, + }, + { + description: "UserSyncURL Override Redirect", + givenFsBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Supports: []string{"redirect"}}}}, + givenConfigBidderInfos: BidderInfos{"a": {UserSyncURL: "override"}}, + expectedBidderInfos: BidderInfos{"a": {UserSyncURL: "override", Syncer: &Syncer{Supports: []string{"redirect"}, Redirect: &SyncerEndpoint{URL: "override"}}}}, + }, + { + description: "UserSyncURL Supports Redirect", + givenFsBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Redirect: &SyncerEndpoint{URL: "original"}}}}, + givenConfigBidderInfos: BidderInfos{"a": {UserSyncURL: "override"}}, + expectedBidderInfos: BidderInfos{"a": {UserSyncURL: "override", Syncer: &Syncer{Redirect: &SyncerEndpoint{URL: "override"}}}}, + }, + { + description: "UserSyncURL Override Syncer Not Defined", + givenFsBidderInfos: BidderInfos{"a": {}}, + givenConfigBidderInfos: BidderInfos{"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", + givenFsBidderInfos: BidderInfos{"a": {Syncer: &Syncer{}}}, + givenConfigBidderInfos: BidderInfos{"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", + givenFsBidderInfos: BidderInfos{"a": {Syncer: &Syncer{IFrame: &SyncerEndpoint{URL: "originalIFrame"}, Redirect: &SyncerEndpoint{URL: "originalRedirect"}}}}, + givenConfigBidderInfos: BidderInfos{"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", + givenFsBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Supports: []string{"iframe", "redirect"}}}}, + givenConfigBidderInfos: BidderInfos{"a": {UserSyncURL: "override"}}, + expectedError: "adapters.a.usersync_url cannot be applied, bidder defines multiple user sync endpoints or supports multiple endpoints", + }, + } -func (r fakeInfoReader) Read(bidder string) ([]byte, error) { - return []byte(r.content), r.err + for _, test := range testCases { + bidderInfos, resultErr := applyBidderInfoConfigOverrides(test.givenConfigBidderInfos, test.givenFsBidderInfos, mockNormalizeBidderName) + if test.expectedError == "" { + assert.NoError(t, resultErr, test.description+":err") + assert.Equal(t, test.expectedBidderInfos, bidderInfos, test.description+":result") + } else { + assert.EqualError(t, resultErr, test.expectedError, test.description+":err") + } + } } -func TestToGVLVendorIDMap(t *testing.T) { - givenBidderInfos := BidderInfos{ - "bidderA": BidderInfo{Enabled: true, GVLVendorID: 0}, - "bidderB": BidderInfo{Enabled: true, GVLVendorID: 100}, - "bidderC": BidderInfo{Enabled: false, GVLVendorID: 0}, - "bidderD": BidderInfo{Enabled: false, GVLVendorID: 200}, +func TestApplyBidderInfoConfigOverrides(t *testing.T) { + var testCases = []struct { + description string + givenFsBidderInfos BidderInfos + givenConfigBidderInfos BidderInfos + expectedError string + expectedBidderInfos BidderInfos + }{ + { + description: "Don't override endpoint", + givenFsBidderInfos: BidderInfos{"a": {Endpoint: "original"}}, + givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + expectedBidderInfos: BidderInfos{"a": {Endpoint: "original", Syncer: &Syncer{Key: "override"}}}, + }, + { + description: "Override endpoint", + givenFsBidderInfos: BidderInfos{"a": {Endpoint: "original"}}, + givenConfigBidderInfos: BidderInfos{"a": {Endpoint: "override", Syncer: &Syncer{Key: "override"}}}, + expectedBidderInfos: BidderInfos{"a": {Endpoint: "override", Syncer: &Syncer{Key: "override"}}}, + }, + { + description: "Don't override ExtraAdapterInfo", + givenFsBidderInfos: BidderInfos{"a": {ExtraAdapterInfo: "original"}}, + givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + expectedBidderInfos: BidderInfos{"a": {ExtraAdapterInfo: "original", Syncer: &Syncer{Key: "override"}}}, + }, + { + description: "Override ExtraAdapterInfo", + givenFsBidderInfos: BidderInfos{"a": {ExtraAdapterInfo: "original"}}, + givenConfigBidderInfos: BidderInfos{"a": {ExtraAdapterInfo: "override", Syncer: &Syncer{Key: "override"}}}, + expectedBidderInfos: BidderInfos{"a": {ExtraAdapterInfo: "override", Syncer: &Syncer{Key: "override"}}}, + }, + { + description: "Don't override Maintainer", + givenFsBidderInfos: BidderInfos{"a": {Maintainer: &MaintainerInfo{Email: "original"}}}, + givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + expectedBidderInfos: BidderInfos{"a": {Maintainer: &MaintainerInfo{Email: "original"}, Syncer: &Syncer{Key: "override"}}}, + }, + { + description: "Override maintainer", + givenFsBidderInfos: BidderInfos{"a": {Maintainer: &MaintainerInfo{Email: "original"}}}, + givenConfigBidderInfos: BidderInfos{"a": {Maintainer: &MaintainerInfo{Email: "override"}, Syncer: &Syncer{Key: "override"}}}, + expectedBidderInfos: BidderInfos{"a": {Maintainer: &MaintainerInfo{Email: "override"}, Syncer: &Syncer{Key: "override"}}}, + }, + { + description: "Don't override Capabilities", + givenFsBidderInfos: BidderInfos{"a": { + Capabilities: &CapabilitiesInfo{App: &PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeVideo}}}, + }}, + givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + expectedBidderInfos: BidderInfos{"a": { + Syncer: &Syncer{Key: "override"}, + Capabilities: &CapabilitiesInfo{App: &PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeVideo}}}, + }}, + }, + { + description: "Override Capabilities", + givenFsBidderInfos: BidderInfos{"a": { + Capabilities: &CapabilitiesInfo{App: &PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeVideo}}}, + }}, + givenConfigBidderInfos: BidderInfos{"a": { + Syncer: &Syncer{Key: "override"}, + Capabilities: &CapabilitiesInfo{App: &PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}}}, + }}, + expectedBidderInfos: BidderInfos{"a": { + Syncer: &Syncer{Key: "override"}, + Capabilities: &CapabilitiesInfo{App: &PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}}}, + }}, + }, + { + description: "Don't override Debug", + givenFsBidderInfos: BidderInfos{"a": {Debug: &DebugInfo{Allow: true}}}, + givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + expectedBidderInfos: BidderInfos{"a": {Debug: &DebugInfo{Allow: true}, Syncer: &Syncer{Key: "override"}}}, + }, + { + description: "Override Debug", + givenFsBidderInfos: BidderInfos{"a": {Debug: &DebugInfo{Allow: true}}}, + givenConfigBidderInfos: BidderInfos{"a": {Debug: &DebugInfo{Allow: false}, Syncer: &Syncer{Key: "override"}}}, + expectedBidderInfos: BidderInfos{"a": {Debug: &DebugInfo{Allow: false}, Syncer: &Syncer{Key: "override"}}}, + }, + { + description: "Don't override GVLVendorID", + givenFsBidderInfos: BidderInfos{"a": {GVLVendorID: 5}}, + givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + expectedBidderInfos: BidderInfos{"a": {GVLVendorID: 5, Syncer: &Syncer{Key: "override"}}}, + }, + { + description: "Override GVLVendorID", + givenFsBidderInfos: BidderInfos{"a": {}}, + givenConfigBidderInfos: BidderInfos{"a": {GVLVendorID: 5, Syncer: &Syncer{Key: "override"}}}, + expectedBidderInfos: BidderInfos{"a": {GVLVendorID: 5, Syncer: &Syncer{Key: "override"}}}, + }, + { + description: "Don't override XAPI", + givenFsBidderInfos: BidderInfos{"a": { + XAPI: AdapterXAPI{Username: "username1", Password: "password2", Tracker: "tracker3"}, + }}, + givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + expectedBidderInfos: BidderInfos{"a": { + XAPI: AdapterXAPI{Username: "username1", Password: "password2", Tracker: "tracker3"}, + Syncer: &Syncer{Key: "override"}}}, + }, + { + description: "Override XAPI", + givenFsBidderInfos: BidderInfos{"a": { + XAPI: AdapterXAPI{Username: "username", Password: "password", Tracker: "tracker"}}}, + givenConfigBidderInfos: BidderInfos{"a": { + XAPI: AdapterXAPI{Username: "username1", Password: "password2", Tracker: "tracker3"}, + Syncer: &Syncer{Key: "override"}}}, + expectedBidderInfos: BidderInfos{"a": { + XAPI: AdapterXAPI{Username: "username1", Password: "password2", Tracker: "tracker3"}, + Syncer: &Syncer{Key: "override"}}}, + }, + { + description: "Don't override PlatformID", + givenFsBidderInfos: BidderInfos{"a": {PlatformID: "PlatformID"}}, + givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + expectedBidderInfos: BidderInfos{"a": {PlatformID: "PlatformID", Syncer: &Syncer{Key: "override"}}}, + }, + { + description: "Override PlatformID", + givenFsBidderInfos: BidderInfos{"a": {PlatformID: "PlatformID1"}}, + givenConfigBidderInfos: BidderInfos{"a": {PlatformID: "PlatformID2", Syncer: &Syncer{Key: "override"}}}, + expectedBidderInfos: BidderInfos{"a": {PlatformID: "PlatformID2", Syncer: &Syncer{Key: "override"}}}, + }, + { + description: "Don't override AppSecret", + givenFsBidderInfos: BidderInfos{"a": {AppSecret: "AppSecret"}}, + givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + expectedBidderInfos: BidderInfos{"a": {AppSecret: "AppSecret", Syncer: &Syncer{Key: "override"}}}, + }, + { + description: "Override AppSecret", + givenFsBidderInfos: BidderInfos{"a": {AppSecret: "AppSecret1"}}, + givenConfigBidderInfos: BidderInfos{"a": {AppSecret: "AppSecret2", Syncer: &Syncer{Key: "override"}}}, + expectedBidderInfos: BidderInfos{"a": {AppSecret: "AppSecret2", Syncer: &Syncer{Key: "override"}}}, + }, + { + description: "Don't override EndpointCompression", + givenFsBidderInfos: BidderInfos{"a": {EndpointCompression: "GZIP"}}, + givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + expectedBidderInfos: BidderInfos{"a": {EndpointCompression: "GZIP", Syncer: &Syncer{Key: "override"}}}, + }, + { + description: "Override EndpointCompression", + givenFsBidderInfos: BidderInfos{"a": {EndpointCompression: "GZIP"}}, + givenConfigBidderInfos: BidderInfos{"a": {EndpointCompression: "LZ77", Syncer: &Syncer{Key: "override"}}}, + expectedBidderInfos: BidderInfos{"a": {EndpointCompression: "LZ77", Syncer: &Syncer{Key: "override"}}}, + }, } + for _, test := range testCases { + bidderInfos, resultErr := applyBidderInfoConfigOverrides(test.givenConfigBidderInfos, test.givenFsBidderInfos, mockNormalizeBidderName) + assert.NoError(t, resultErr, test.description+":err") + assert.Equal(t, test.expectedBidderInfos, bidderInfos, test.description+":result") + } +} - expectedGVLVendorIDMap := map[openrtb_ext.BidderName]uint16{ - "bidderB": 100, +func TestApplyBidderInfoConfigOverridesInvalid(t *testing.T) { + var testCases = []struct { + description string + givenFsBidderInfos BidderInfos + givenConfigBidderInfos BidderInfos + expectedError string + expectedBidderInfos BidderInfos + }{ + { + description: "Bidder doesn't exists in bidder list", + givenConfigBidderInfos: BidderInfos{"unknown": {Syncer: &Syncer{Key: "override"}}}, + expectedError: "error setting configuration for bidder unknown: unknown bidder", + }, + { + description: "Bidder doesn't exists in file system", + givenFsBidderInfos: BidderInfos{"unknown": {Endpoint: "original"}}, + givenConfigBidderInfos: BidderInfos{"bidderA": {Syncer: &Syncer{Key: "override"}}}, + expectedError: "error finding configuration for bidder bidderA: unknown bidder", + }, + } + for _, test := range testCases { + _, err := applyBidderInfoConfigOverrides(test.givenConfigBidderInfos, test.givenFsBidderInfos, mockNormalizeBidderName) + assert.ErrorContains(t, err, test.expectedError, test.description+":err") } +} - result := givenBidderInfos.ToGVLVendorIDMap() - assert.Equal(t, expectedGVLVendorIDMap, result) +func TestReadFullYamlBidderConfig(t *testing.T) { + bidder := "bidderA" + bidderInf := BidderInfo{} + err := yaml.Unmarshal([]byte(fullBidderYAMLConfig), &bidderInf) + actualBidderInfo, err := applyBidderInfoConfigOverrides(BidderInfos{bidder: bidderInf}, BidderInfos{bidder: {Syncer: &Syncer{Supports: []string{"iframe"}}}}, mockNormalizeBidderName) + + assert.NoError(t, err, "Error wasn't expected") + + expectedBidderInfo := BidderInfos{ + bidder: { + Disabled: false, + Maintainer: &MaintainerInfo{ + Email: "some-email@domain.com", + }, + GVLVendorID: 42, + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeVideo, openrtb_ext.BidTypeNative}, + }, + Site: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeVideo, openrtb_ext.BidTypeNative}, + }, + }, + Debug: &DebugInfo{Allow: true}, + ModifyingVastXmlAllowed: true, + Syncer: &Syncer{ + Supports: []string{"iframe"}, + }, + Experiment: BidderInfoExperiment{AdsCert: BidderAdsCert{Enabled: true}}, + EndpointCompression: "GZIP", + }, + } + assert.Equalf(t, expectedBidderInfo, actualBidderInfo, "Bidder info objects aren't matching") } diff --git a/config/bidderinfo_validate_test.go b/config/bidderinfo_validate_test.go deleted file mode 100644 index c33c829fc43..00000000000 --- a/config/bidderinfo_validate_test.go +++ /dev/null @@ -1,161 +0,0 @@ -package config_test - -import ( - "errors" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "strings" - "testing" - - "github.com/stretchr/testify/assert" - "gopkg.in/yaml.v3" - - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/usersync" -) - -const bidderInfoRelativePath = "../static/bidder-info" - -// TestBidderInfoFiles ensures each bidder has a valid static/bidder-info/bidder.yaml file. Validation is performed directly -// against the file system with separate yaml unmarshalling from the LoadBidderInfoFromDisk func. -func TestBidderInfoFiles(t *testing.T) { - fileInfos, err := ioutil.ReadDir(bidderInfoRelativePath) - if err != nil { - assert.FailNow(t, "Error reading the static/bidder-info directory: %v", err) - } - - // Ensure YAML Files Are For A Known Core Bidder - for _, fileInfo := range fileInfos { - bidder := strings.TrimSuffix(fileInfo.Name(), ".yaml") - - _, isKnown := openrtb_ext.NormalizeBidderName(bidder) - assert.True(t, isKnown, "unexpected bidder info yaml file %s", fileInfo.Name()) - } - - // Ensure YAML Files Are Defined For Each Core Bidder - 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) - - // Load & Validate Contents - bidderInfos := make(config.BidderInfos) - for _, fileInfo := range fileInfos { - path := fmt.Sprintf(bidderInfoRelativePath + "/" + fileInfo.Name()) - - infoFileData, err := os.Open(path) - assert.NoError(t, err, "Unexpected error: %v", err) - - content, err := ioutil.ReadAll(infoFileData) - assert.NoError(t, err, "Failed to read static/bidder-info/%s: %v", fileInfo.Name(), err) - - 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 *config.BidderInfo) error { - if err := validateMaintainer(info.Maintainer); err != nil { - return err - } - - if err := validateCapabilities(info.Capabilities); err != nil { - return err - } - - return nil -} - -func validateMaintainer(info *config.MaintainerInfo) error { - if info == nil || info.Email == "" { - return errors.New("missing required field: maintainer.email") - } - return nil -} - -func validateCapabilities(info *config.CapabilitiesInfo) error { - if info == nil { - return errors.New("missing required field: capabilities") - } - - if info.App == nil && info.Site == nil { - return errors.New("at least one of capabilities.site or capabilities.app must exist") - } - - if info.App != nil { - if err := validatePlatformInfo(info.App); err != nil { - return fmt.Errorf("capabilities.app failed validation: %v", err) - } - } - - if info.Site != nil { - if err := validatePlatformInfo(info.Site); err != nil { - return fmt.Errorf("capabilities.site failed validation: %v", err) - } - } - return nil -} - -func validatePlatformInfo(info *config.PlatformInfo) error { - if info == nil { - return errors.New("object cannot be empty") - } - - if len(info.MediaTypes) == 0 { - return errors.New("mediaTypes should be an array with at least one string element") - } - - for index, mediaType := range info.MediaTypes { - if mediaType != "banner" && mediaType != "video" && mediaType != "native" && mediaType != "audio" { - return fmt.Errorf("unrecognized media type at index %d: %s", index, mediaType) - } - } - - 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 dc902bd0f16..38def167a3e 100644 --- a/config/config.go +++ b/config/config.go @@ -6,14 +6,17 @@ import ( "fmt" "net/url" "reflect" + "strconv" "strings" "time" "github.com/golang/glog" + "github.com/prebid/openrtb/v17/openrtb2" + "github.com/spf13/viper" + "github.com/prebid/go-gdpr/consentconstants" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" - "github.com/spf13/viper" ) // Configuration specifies the static application config. @@ -51,17 +54,14 @@ type Configuration struct { StoredVideo StoredRequests `mapstructure:"stored_video_req"` StoredResponses StoredRequests `mapstructure:"stored_responses"` - // Adapters should have a key for every openrtb_ext.BidderName, converted to lower-case. - // Se also: https://github.com/spf13/viper/issues/371#issuecomment-335388559 - Adapters map[string]Adapter `mapstructure:"adapters"` - MaxRequestSize int64 `mapstructure:"max_request_size"` - Analytics Analytics `mapstructure:"analytics"` - AMPTimeoutAdjustment int64 `mapstructure:"amp_timeout_adjustment_ms"` - GDPR GDPR `mapstructure:"gdpr"` - CCPA CCPA `mapstructure:"ccpa"` - LMT LMT `mapstructure:"lmt"` - CurrencyConverter CurrencyConverter `mapstructure:"currency_converter"` - DefReqConfig DefReqConfig `mapstructure:"default_request"` + MaxRequestSize int64 `mapstructure:"max_request_size"` + Analytics Analytics `mapstructure:"analytics"` + AMPTimeoutAdjustment int64 `mapstructure:"amp_timeout_adjustment_ms"` + GDPR GDPR `mapstructure:"gdpr"` + CCPA CCPA `mapstructure:"ccpa"` + LMT LMT `mapstructure:"lmt"` + CurrencyConverter CurrencyConverter `mapstructure:"currency_converter"` + DefReqConfig DefReqConfig `mapstructure:"default_request"` VideoStoredRequestRequired bool `mapstructure:"video_stored_request_required"` @@ -91,7 +91,16 @@ type Configuration struct { //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"` + GenerateRequestID bool `mapstructure:"generate_request_id"` + HostSChainNode *openrtb2.SupplyChainNode `mapstructure:"host_schain_node"` + // Experiment configures non-production ready features. + Experiment Experiment `mapstructure:"experiment"` + DataCenter string `mapstructure:"datacenter"` + // BidderInfos supports adapter overrides in extra configs like pbs.json, pbs.yaml, etc. + // Refers to main.go `configFileName` constant + BidderInfos BidderInfos `mapstructure:"adapters"` + // Hooks provides a way to specify hook execution plan for specific endpoints and stages + Hooks Hooks `mapstructure:"hooks"` } const MIN_COOKIE_SIZE_BYTES = 500 @@ -117,7 +126,6 @@ func (cfg *Configuration) validate(v *viper.Viper) []error { } errs = cfg.GDPR.validate(v, errs) errs = cfg.CurrencyConverter.validate(errs) - errs = validateAdapters(cfg.Adapters, errs) errs = cfg.Debug.validate(errs) errs = cfg.ExtCacheURL.validate(errs) if cfg.AccountDefaults.Disabled { @@ -126,6 +134,8 @@ func (cfg *Configuration) validate(v *viper.Viper) []error { if cfg.AccountDefaults.Events.Enabled { glog.Warning(`account_defaults.events will currently not do anything as the feature is still under development. Please follow https://github.com/prebid/prebid-server/issues/1725 for more updates`) } + errs = cfg.Experiment.validate(errs) + errs = cfg.BidderInfos.validate(errs) return errs } @@ -253,11 +263,11 @@ func (cfg *GDPR) validatePurposes(errs []error) []error { } for i := 0; i < len(purposeConfigs); i++ { - enforcePurposeValue := purposeConfigs[i].EnforcePurpose - enforcePurposeField := fmt.Sprintf("gdpr.tcf2.purpose%d.enforce_purpose", (i + 1)) + enforceAlgoValue := purposeConfigs[i].EnforceAlgo + enforceAlgoField := fmt.Sprintf("gdpr.tcf2.purpose%d.enforce_algo", (i + 1)) - if enforcePurposeValue != TCF2NoEnforcement && enforcePurposeValue != TCF2FullEnforcement { - errs = append(errs, fmt.Errorf("%s must be \"no\" or \"full\". Got %s", enforcePurposeField, enforcePurposeValue)) + if enforceAlgoValue != TCF2EnforceAlgoFull && enforceAlgoValue != TCF2EnforceAlgoBasic { + errs = append(errs, fmt.Errorf("%s must be \"basic\" or \"full\". Got %s", enforceAlgoField, enforceAlgoValue)) } } return errs @@ -277,8 +287,16 @@ func (t *GDPRTimeouts) ActiveTimeout() time.Duration { } const ( - TCF2FullEnforcement = "full" - TCF2NoEnforcement = "no" + TCF2EnforceAlgoBasic = "basic" + TCF2EnforceAlgoFull = "full" +) + +type TCF2EnforcementAlgo int + +const ( + TCF2UndefinedEnforcement TCF2EnforcementAlgo = iota + TCF2BasicEnforcement + TCF2FullEnforcement ) // TCF2 defines the TCF2 specific configurations for GDPR @@ -300,18 +318,9 @@ type TCF2 struct { PurposeOneTreatment TCF2PurposeOneTreatment `mapstructure:"purpose_one_treatment"` } -// BasicEnforcementVendor checks if the given bidder is considered a basic enforcement vendor which indicates whether -// weak vendor enforcement applies to that bidder. If set, the legal basis calculation for the bidder only considers -// consent to the purpose, not the vendor. The idea is that the publisher trusts this vendor to enforce the -// appropriate rules on their own. This only comes into play when enforceVendors is true as it lists those vendors that -// are exempt for vendor enforcement. -func (t *TCF2) BasicEnforcementVendor(openrtb_ext.BidderName) bool { - return false -} - -// IntegrationEnabled checks if a given integration type is enabled. All integration types are considered either +// ChannelEnabled checks if a given channel type is enabled. All channel types are considered either // enabled or disabled based on the Enabled flag. -func (t *TCF2) IntegrationEnabled(integrationType IntegrationType) bool { +func (t *TCF2) ChannelEnabled(channelType ChannelType) bool { return t.Enabled } @@ -326,10 +335,15 @@ func (t *TCF2) PurposeEnforced(purpose consentconstants.Purpose) (value bool) { if t.PurposeConfigs[purpose] == nil { return false } - if t.PurposeConfigs[purpose].EnforcePurpose == TCF2FullEnforcement { - return true + return t.PurposeConfigs[purpose].EnforcePurpose +} + +// PurposeEnforcementAlgo returns the default enforcement algorithm for a given purpose +func (t *TCF2) PurposeEnforcementAlgo(purpose consentconstants.Purpose) (value TCF2EnforcementAlgo) { + if c, exists := t.PurposeConfigs[purpose]; exists { + return c.EnforceAlgoID } - return false + return TCF2FullEnforcement } // PurposeEnforcingVendors checks if enforcing vendors is turned on for a given purpose. With enforcing vendors @@ -341,17 +355,15 @@ func (t *TCF2) PurposeEnforcingVendors(purpose consentconstants.Purpose) (value return t.PurposeConfigs[purpose].EnforceVendors } -// PurposeVendorException checks if the specified bidder is considered a vendor exception for a given purpose. If a -// bidder is a vendor exception, the GDPR full enforcement algorithm will bypass the legal basis calculation assuming -// the request is valid and there isn't a "deny all" publisher restriction -func (t *TCF2) PurposeVendorException(purpose consentconstants.Purpose, bidder openrtb_ext.BidderName) (value bool) { - if t.PurposeConfigs[purpose] == nil { - return false - } - if _, ok := t.PurposeConfigs[purpose].VendorExceptionMap[bidder]; ok { - return true +// PurposeVendorExceptions returns the vendor exception map for a given purpose if it exists, otherwise it returns +// an empty map of vendor exceptions +func (t *TCF2) PurposeVendorExceptions(purpose consentconstants.Purpose) (value map[openrtb_ext.BidderName]struct{}) { + c, exists := t.PurposeConfigs[purpose] + + if exists && c.VendorExceptionMap != nil { + return c.VendorExceptionMap } - return false + return make(map[openrtb_ext.BidderName]struct{}, 0) } // FeatureOneEnforced checks if special feature one is enforced. If it is enforced, PBS will determine whether geo @@ -381,9 +393,12 @@ func (t *TCF2) PurposeOneTreatmentAccessAllowed() (value bool) { // Making a purpose struct so purpose specific details can be added later. type TCF2Purpose struct { - Enabled bool `mapstructure:"enabled"` // Deprecated: Use enforce_purpose instead - EnforcePurpose string `mapstructure:"enforce_purpose"` - EnforceVendors bool `mapstructure:"enforce_vendors"` + Enabled bool `mapstructure:"enabled"` // Deprecated: Use enforce_purpose instead + EnforceAlgo string `mapstructure:"enforce_algo"` + // Integer representation of enforcement algo for performance improvement on compares + EnforceAlgoID TCF2EnforcementAlgo + EnforcePurpose bool `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{} @@ -487,6 +502,12 @@ type DisabledMetrics struct { // True if we want to stop collecting account-to-adapter metrics AccountAdapterDetails bool `mapstructure:"account_adapter_details"` + // True if we want to stop collecting account debug request metrics + AccountDebug bool `mapstructure:"account_debug"` + + // True if we want to stop collecting account stored respponses metrics + AccountStoredResponses bool `mapstructure:"account_stored_responses"` + // True if we don't want to collect metrics about the connections prebid // server establishes with bidder servers such as the number of connections // that were created or reused. @@ -494,6 +515,9 @@ type DisabledMetrics struct { // True if we don't want to collect the per adapter GDPR request blocked metric AdapterGDPRRequestBlocked bool `mapstructure:"adapter_gdpr_request_blocked"` + + // True if we want to stop collecting account modules metrics + AccountModulesMetrics bool `mapstructure:"account_modules_metrics"` } func (cfg *Metrics) validate(errs []error) []error { @@ -585,6 +609,16 @@ type Debug struct { OverrideToken string `mapstructure:"override_token"` } +type Server struct { + ExternalUrl string + GvlID int + DataCenter string +} + +func (server *Server) Empty() bool { + return server == nil || (server.DataCenter == "" && server.ExternalUrl == "" && server.GvlID == 0) +} + func (cfg *Debug) validate(errs []error) []error { return cfg.TimeoutNotification.validate(errs) } @@ -606,7 +640,7 @@ func (cfg *TimeoutNotification) validate(errs []error) []error { } // New uses viper to get our server configurations. -func New(v *viper.Viper) (*Configuration, error) { +func New(v *viper.Viper, bidderInfos BidderInfos, normalizeBidderName func(string) (openrtb_ext.BidderName, bool)) (*Configuration, error) { var c Configuration if err := v.Unmarshal(&c); err != nil { return nil, fmt.Errorf("viper failed to unmarshal app config: %v", err) @@ -654,6 +688,16 @@ func New(v *viper.Viper) (*Configuration, error) { 10: &c.GDPR.TCF2.Purpose10, } + // As an alternative to performing several string compares per request, we set the integer representation of + // the enforcement algorithm on each purpose config + for _, pc := range c.GDPR.TCF2.PurposeConfigs { + if pc.EnforceAlgo == TCF2EnforceAlgoBasic { + pc.EnforceAlgoID = TCF2BasicEnforcement + } else { + pc.EnforceAlgoID = TCF2FullEnforcement + } + } + // To look for a purpose's vendor exceptions in O(1) time, for each purpose we fill this hash table with bidders // located in the VendorExceptions field of the GDPR.TCF2.PurposeX struct defined in this file for _, pc := range c.GDPR.TCF2.PurposeConfigs { @@ -689,6 +733,12 @@ func New(v *viper.Viper) (*Configuration, error) { // Migrate combo stored request config to separate stored_reqs and amp stored_reqs configs. resolvedStoredRequestsConfig(&c) + mergedBidderInfos, err := applyBidderInfoConfigOverrides(c.BidderInfos, bidderInfos, normalizeBidderName) + if err != nil { + return nil, err + } + c.BidderInfos = mergedBidderInfos + glog.Info("Logging the resolved configuration:") logGeneral(reflect.ValueOf(c), " \t") if errs := c.validate(v); len(errs) > 0 { @@ -712,7 +762,7 @@ func (cfg *Configuration) AccountDefaultsJSON() json.RawMessage { return cfg.accountDefaultsJSON } -//Allows for protocol relative URL if scheme is empty +// GetBaseURL allows for protocol relative URL if scheme is empty func (cfg *Cache) GetBaseURL() string { cfg.Scheme = strings.ToLower(cfg.Scheme) if strings.Contains(cfg.Scheme, "https") { @@ -729,7 +779,7 @@ func (cfg *Configuration) GetCachedAssetURL(uuid string) string { } // Set the default config values for the viper object we are using. -func SetupViper(v *viper.Viper, filename string) { +func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { if filename != "" { v.SetConfigName(filename) v.AddConfigPath(".") @@ -747,6 +797,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("enable_gzip", false) v.SetDefault("garbage_collector_threshold", 0) v.SetDefault("status_response", "") + v.SetDefault("datacenter", "") v.SetDefault("auction_timeouts_ms.default", 0) v.SetDefault("auction_timeouts_ms.max", 0) v.SetDefault("cache.scheme", "") @@ -770,6 +821,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("host_cookie.value", "") v.SetDefault("host_cookie.ttl_days", 90) v.SetDefault("host_cookie.max_cookie_size_bytes", 0) + v.SetDefault("host_schain_node", nil) v.SetDefault("http_client.max_connections_per_host", 0) // unlimited v.SetDefault("http_client.max_idle_connections", 400) v.SetDefault("http_client.max_idle_connections_per_host", 10) @@ -780,6 +832,8 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("http_client_cache.idle_connection_timeout_seconds", 60) // no metrics configured by default (metrics{host|database|username|password}) v.SetDefault("metrics.disabled_metrics.account_adapter_details", false) + v.SetDefault("metrics.disabled_metrics.account_debug", true) + v.SetDefault("metrics.disabled_metrics.account_stored_responses", true) v.SetDefault("metrics.disabled_metrics.adapter_connections_metrics", true) v.SetDefault("metrics.disabled_metrics.adapter_gdpr_request_blocked", false) v.SetDefault("metrics.influxdb.host", "") @@ -799,20 +853,6 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("stored_requests.filesystem.enabled", false) v.SetDefault("stored_requests.filesystem.directorypath", "./stored_requests/data/by_id") v.SetDefault("stored_requests.directorypath", "./stored_requests/data/by_id") - v.SetDefault("stored_requests.postgres.connection.dbname", "") - v.SetDefault("stored_requests.postgres.connection.host", "") - v.SetDefault("stored_requests.postgres.connection.port", 0) - v.SetDefault("stored_requests.postgres.connection.user", "") - v.SetDefault("stored_requests.postgres.connection.password", "") - v.SetDefault("stored_requests.postgres.fetcher.query", "") - v.SetDefault("stored_requests.postgres.fetcher.amp_query", "") - v.SetDefault("stored_requests.postgres.initialize_caches.timeout_ms", 0) - v.SetDefault("stored_requests.postgres.initialize_caches.query", "") - v.SetDefault("stored_requests.postgres.initialize_caches.amp_query", "") - v.SetDefault("stored_requests.postgres.poll_for_updates.refresh_rate_seconds", 0) - v.SetDefault("stored_requests.postgres.poll_for_updates.timeout_ms", 0) - v.SetDefault("stored_requests.postgres.poll_for_updates.query", "") - v.SetDefault("stored_requests.postgres.poll_for_updates.amp_query", "") v.SetDefault("stored_requests.http.endpoint", "") v.SetDefault("stored_requests.http.amp_endpoint", "") v.SetDefault("stored_requests.in_memory_cache.type", "none") @@ -829,17 +869,6 @@ func SetupViper(v *viper.Viper, filename string) { // PBS is not in the business of storing video content beyond the normal prebid cache system. v.SetDefault("stored_video_req.filesystem.enabled", false) v.SetDefault("stored_video_req.filesystem.directorypath", "") - v.SetDefault("stored_video_req.postgres.connection.dbname", "") - v.SetDefault("stored_video_req.postgres.connection.host", "") - v.SetDefault("stored_video_req.postgres.connection.port", 0) - v.SetDefault("stored_video_req.postgres.connection.user", "") - v.SetDefault("stored_video_req.postgres.connection.password", "") - v.SetDefault("stored_video_req.postgres.fetcher.query", "") - v.SetDefault("stored_video_req.postgres.initialize_caches.timeout_ms", 0) - v.SetDefault("stored_video_req.postgres.initialize_caches.query", "") - v.SetDefault("stored_video_req.postgres.poll_for_updates.refresh_rate_seconds", 0) - v.SetDefault("stored_video_req.postgres.poll_for_updates.timeout_ms", 0) - v.SetDefault("stored_video_req.postgres.poll_for_updates.query", "") v.SetDefault("stored_video_req.http.endpoint", "") v.SetDefault("stored_video_req.in_memory_cache.type", "none") v.SetDefault("stored_video_req.in_memory_cache.ttl_seconds", 0) @@ -853,17 +882,6 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("stored_video_req.http_events.timeout_ms", 0) v.SetDefault("stored_responses.filesystem.enabled", false) v.SetDefault("stored_responses.filesystem.directorypath", "") - v.SetDefault("stored_responses.postgres.connection.dbname", "") - v.SetDefault("stored_responses.postgres.connection.host", "") - v.SetDefault("stored_responses.postgres.connection.port", 0) - v.SetDefault("stored_responses.postgres.connection.user", "") - v.SetDefault("stored_responses.postgres.connection.password", "") - v.SetDefault("stored_responses.postgres.fetcher.query", "") - v.SetDefault("stored_responses.postgres.initialize_caches.timeout_ms", 0) - v.SetDefault("stored_responses.postgres.initialize_caches.query", "") - v.SetDefault("stored_responses.postgres.poll_for_updates.refresh_rate_seconds", 0) - v.SetDefault("stored_responses.postgres.poll_for_updates.timeout_ms", 0) - v.SetDefault("stored_responses.postgres.poll_for_updates.query", "") v.SetDefault("stored_responses.http.endpoint", "") v.SetDefault("stored_responses.in_memory_cache.type", "none") v.SetDefault("stored_responses.in_memory_cache.ttl_seconds", 0) @@ -886,180 +904,13 @@ 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") + v.BindEnv("user_sync.external_url") + v.BindEnv("user_sync.coop_sync.default") + // 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))) - } - - // Disabling adapters by default that require some specific config params. - // If you're using one of these, make sure you check out the documentation (https://github.com/prebid/prebid-server/tree/master/docs/bidders) - // 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.aax.endpoint", "https://prebid.aaxads.com/rtb/pb/aax-prebid") - v.SetDefault("adapters.aax.extra_info", "https://aax.golang.pbs.com") - 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.adf.endpoint", "https://adx.adform.net/adx/openrtb") - 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", "https://pbs.adksrv.com/hb?zone={{.ZoneID}}") - v.SetDefault("adapters.adkerneladn.endpoint", "https://pbs2.adksrv.com/rtbpub?account={{.PublisherID}}") - 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{PUBLISHER_PATH}/bidrequest") - v.SetDefault("adapters.adpone.endpoint", "http://rtb.adpone.com/bid-request?src=prebid_server") - 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}}") - 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") - v.SetDefault("adapters.algorix.endpoint", "https://{{.Host}}.svr-algorix.com/rtb/sa?sid={{.SourceId}}&token={{.AccountID}}") - v.SetDefault("adapters.amx.endpoint", "http://pbs.amxrtb.com/auction/openrtb") - v.SetDefault("adapters.apacdex.endpoint", "http://useast.quantumdex.io/auction/pbs") - v.SetDefault("adapters.applogy.endpoint", "http://rtb.applogy.com/v1/prebid") - v.SetDefault("adapters.appnexus.endpoint", "http://ib.adnxs.com/openrtb2") // Docs: https://wiki.appnexus.com/display/supply/Incoming+Bid+Request+from+SSPs - v.SetDefault("adapters.appnexus.platform_id", "5") - v.SetDefault("adapters.audiencenetwork.disabled", true) - v.SetDefault("adapters.audiencenetwork.endpoint", "https://an.facebook.com/placementbid.ortb") - v.SetDefault("adapters.avocet.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") - 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.bizzclick.endpoint", "http://us-e-node1.bizzclick.com/bid?rtb_seat_id={{.SourceId}}&secret_key={{.AccountID}}") - v.SetDefault("adapters.bliink.endpoint", "http://engine.bliink.io/bid") - v.SetDefault("adapters.bmtm.endpoint", "https://one.elitebidder.com/api/pbs") - v.SetDefault("adapters.brightroll.endpoint", "http://east-bid.ybp.yahoo.com/bid/appnexuspbs") - v.SetDefault("adapters.coinzilla.endpoint", "http://request-global.czilladx.com/serve/prebid-server.php") - v.SetDefault("adapters.colossus.endpoint", "http://colossusssp.com/?c=o&m=rtb") - v.SetDefault("adapters.compass.endpoint", "http://sa-lb.deliverimp.com/pserver") - v.SetDefault("adapters.connectad.endpoint", "http://bidder.connectad.io/API?src=pbs") - v.SetDefault("adapters.consumable.endpoint", "https://e.serverbid.com/api/v2") - v.SetDefault("adapters.conversant.endpoint", "http://api.hb.ad.cpe.dotomi.com/cvx/server/hb/ortb/25") - v.SetDefault("adapters.cpmstar.endpoint", "https://server.cpmstar.com/openrtbbidrq.aspx") - v.SetDefault("adapters.criteo.endpoint", "https://bidder.criteo.com/cdb?profileId=230") - v.SetDefault("adapters.datablocks.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}") - v.SetDefault("adapters.decenterads.endpoint", "http://supply.decenterads.com/?c=o&m=rtb") - v.SetDefault("adapters.deepintent.endpoint", "https://prebid.deepintent.com/prebid") - v.SetDefault("adapters.dmx.endpoint", "https://dmx-direct.districtm.io/b/v2") - v.SetDefault("adapters.emx_digital.endpoint", "https://hb.emxdgt.com") - v.SetDefault("adapters.engagebdr.endpoint", "http://dsp.bnmla.com/hb") - v.SetDefault("adapters.engagebdr_ortb.endpoint", "https://dsp.bnmla.com/bid?sspid=1000204") - v.SetDefault("adapters.eplanning.endpoint", "http://rtb.e-planning.net/pbs/1") - v.SetDefault("adapters.epom.endpoint", "https://an.epom.com/ortb") - v.SetDefault("adapters.epom.disabled", true) - v.SetDefault("adapters.e_volution.endpoint", "http://service.e-volution.ai/pbserver") - v.SetDefault("adapters.gamma.endpoint", "https://hb.gammaplatform.com/adx/request/") - v.SetDefault("adapters.gamoshi.endpoint", "https://rtb.gamoshi.io") - v.SetDefault("adapters.grid.endpoint", "https://grid.bidswitch.net/sp_bid?sp=prebid") - v.SetDefault("adapters.groupm.endpoint", "https://hbopenbid.pubmatic.com/translator?source=prebid-server") - v.SetDefault("adapters.gumgum.endpoint", "https://g2.gumgum.com/providers/prbds2s/bid") - v.SetDefault("adapters.gumgum_ortb.endpoint", "https://g2.gumgum.com/zones/8ylgv2wd/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}}") - v.SetDefault("adapters.ix.disabled", true) - v.SetDefault("adapters.janet.endpoint", "http://ghb.bidder.jmgads.com/pbs/ortb") - v.SetDefault("adapters.jixie.endpoint", "https://hb.jixie.io/v2/hbsvrpost") - v.SetDefault("adapters.kayzen.endpoint", "https://bids-{{.ZoneID}}.bidder.kayzen.io/?exchange={{.AccountID}}") - v.SetDefault("adapters.krushmedia.endpoint", "http://ads4.krushmedia.com/?c=rtb&m=req&key={{.AccountID}}") - v.SetDefault("adapters.invibes.endpoint", "https://{{.ZoneID}}.videostep.com/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") - v.SetDefault("adapters.logicad.endpoint", "https://pbs.ladsp.com/adrequest/prebidserver") - v.SetDefault("adapters.lunamedia.endpoint", "http://api.lunamedia.io/xp/get?pubid={{.PublisherID}}") - v.SetDefault("adapters.sa_lunamedia.endpoint", "http://balancer.lmgssp.com/pserver") - v.SetDefault("adapters.madvertise.endpoint", "https://mobile.mng-ads.com/bidrequest{{.ZoneID}}") - v.SetDefault("adapters.marsmedia.endpoint", "https://bid306.rtbsrv.com/bidder/?bid=f3xtet") - v.SetDefault("adapters.mediafuse.endpoint", "http://ghb.hbmp.mediafuse.com/pbs/ortb") - v.SetDefault("adapters.medianet.endpoint", "https://prebid-adapter.media.net/rtb/pb/prebids2s") - v.SetDefault("adapters.medianet.extra_info", "https://medianet.golang.pbs.com") - v.SetDefault("adapters.mgid.endpoint", "https://prebid.mgid.com/prebid/") - 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}}") - 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") - v.SetDefault("adapters.outbrain.endpoint", "https://prebidtest.zemanta.com/api/bidder/prebidtest/bid/") - v.SetDefault("adapters.pangle.disabled", true) - v.SetDefault("adapters.pgam.endpoint", "http://ghb.pgamssp.com/pbs/ortb") - v.SetDefault("adapters.playwire.endpoint", "https://grid.bidswitch.net/sp_bid?sp=prebid") - v.SetDefault("adapters.playwire_ortb.endpoint", "https://grid.bidswitch.net/sp_bid?sp=prebid") - v.SetDefault("adapters.pubmatic.endpoint", "https://hbopenbid.pubmatic.com/translator?source=prebid-server") - v.SetDefault("adapters.pubnative.endpoint", "http://dsp.pubnative.net/bid/v1/request") - v.SetDefault("adapters.pulsepoint.endpoint", "http://bid.contextweb.com/header/s/ortb/prebid-s2s") - v.SetDefault("adapters.pulsepoint_ortb.endpoint", "http://rts.lga.contextweb.com/rt-seller/bid/openrtb/playwire") - v.SetDefault("adapters.quantumdex.endpoint", "http://useast.quantumdex.io/auction/pbs") - 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") - v.SetDefault("adapters.sharethrough.endpoint", "https://btlr.sharethrough.com/universal/v1?supply_id=FGMrCMMc") - 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") - 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") - v.SetDefault("adapters.trafficgate.endpoint", "http://{{.Host}}.bc-plugin.com/?c=o&m=rtb") - v.SetDefault("adapters.triplelift_native.disabled", true) - v.SetDefault("adapters.triplelift_native.extra_info", "{\"publisher_whitelist\":[]}") - v.SetDefault("adapters.triplelift.endpoint", "https://tlx.3lift.com/s2s/auction?sra=1&supplier_id=20") - 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", "https://targeting.unrulymedia.com/unruly_prebid_server") - v.SetDefault("adapters.valueimpression.endpoint", "http://useast.quantumdex.io/auction/pbs") - v.SetDefault("adapters.verizonmedia.disabled", true) - v.SetDefault("adapters.videobyte.endpoint", "https://x.videobyte.com/ortbhb") - v.SetDefault("adapters.vidoomy.endpoint", "https://p.vidoomy.com/api/rtbserver/pbs") - 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.endpoint", "https://s2shb.ssp.yahoo.com/admax/bid/partners/PBS") - v.SetDefault("adapters.yeahmobi.endpoint", "https://{{.Host}}/prebid/bid") - v.SetDefault("adapters.yieldlab.endpoint", "https://ad.yieldlab.net/yp/") - v.SetDefault("adapters.yieldmo.endpoint", "https://ads.yieldmo.com/exchange/prebid-server") - v.SetDefault("adapters.yieldone.endpoint", "https://y.one.impact-ad.jp/hbs_imp") - v.SetDefault("adapters.yssp.disabled", true) - v.SetDefault("adapters.zeroclickfraud.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}") - v.SetDefault("max_request_size", 1024*256) v.SetDefault("analytics.file.filename", "") v.SetDefault("analytics.pubstack.endpoint", "https://s2s.pbstck.com/v1") @@ -1143,6 +994,8 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("request_validation.ipv4_private_networks", []string{"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "169.254.0.0/16", "127.0.0.0/8"}) v.SetDefault("request_validation.ipv6_private_networks", []string{"::1/128", "fc00::/7", "fe80::/10", "ff00::/8", "2001:db8::/32"}) + bindDatabaseEnvVars(v) + // Set environment variable support: v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) v.SetTypeByDefaultValue(true) @@ -1154,7 +1007,8 @@ func SetupViper(v *viper.Viper, filename string) { migrateConfig(v) migrateConfigPurposeOneTreatment(v) migrateConfigSpecialFeature1(v) - migrateConfigTCF2PurposeEnabledFlags(v) + migrateConfigTCF2PurposeFlags(v) + migrateConfigDatabaseConnection(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 @@ -1169,16 +1023,26 @@ 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_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.purpose1.enforce_algo", TCF2EnforceAlgoFull) + v.SetDefault("gdpr.tcf2.purpose2.enforce_algo", TCF2EnforceAlgoFull) + v.SetDefault("gdpr.tcf2.purpose3.enforce_algo", TCF2EnforceAlgoFull) + v.SetDefault("gdpr.tcf2.purpose4.enforce_algo", TCF2EnforceAlgoFull) + v.SetDefault("gdpr.tcf2.purpose5.enforce_algo", TCF2EnforceAlgoFull) + v.SetDefault("gdpr.tcf2.purpose6.enforce_algo", TCF2EnforceAlgoFull) + v.SetDefault("gdpr.tcf2.purpose7.enforce_algo", TCF2EnforceAlgoFull) + v.SetDefault("gdpr.tcf2.purpose8.enforce_algo", TCF2EnforceAlgoFull) + v.SetDefault("gdpr.tcf2.purpose9.enforce_algo", TCF2EnforceAlgoFull) + v.SetDefault("gdpr.tcf2.purpose10.enforce_algo", TCF2EnforceAlgoFull) + v.SetDefault("gdpr.tcf2.purpose1.enforce_purpose", true) + v.SetDefault("gdpr.tcf2.purpose2.enforce_purpose", true) + v.SetDefault("gdpr.tcf2.purpose3.enforce_purpose", true) + v.SetDefault("gdpr.tcf2.purpose4.enforce_purpose", true) + v.SetDefault("gdpr.tcf2.purpose5.enforce_purpose", true) + v.SetDefault("gdpr.tcf2.purpose6.enforce_purpose", true) + v.SetDefault("gdpr.tcf2.purpose7.enforce_purpose", true) + v.SetDefault("gdpr.tcf2.purpose8.enforce_purpose", true) + v.SetDefault("gdpr.tcf2.purpose9.enforce_purpose", true) + v.SetDefault("gdpr.tcf2.purpose10.enforce_purpose", true) v.SetDefault("gdpr.tcf2.purpose_one_treatment.enabled", true) v.SetDefault("gdpr.tcf2.purpose_one_treatment.access_allowed", true) v.SetDefault("gdpr.tcf2.special_feature1.enforce", true) @@ -1186,6 +1050,20 @@ func SetupViper(v *viper.Viper, filename string) { // Defaults for account_defaults.events.default_url v.SetDefault("account_defaults.events.default_url", "https://PBS_HOST/event?t=##PBS-EVENTTYPE##&vtype=##PBS-VASTEVENT##&b=##PBS-BIDID##&f=i&a=##PBS-ACCOUNTID##&ts=##PBS-TIMESTAMP##&bidder=##PBS-BIDDER##&int=##PBS-INTEGRATION##&mt=##PBS-MEDIATYPE##&ch=##PBS-CHANNEL##&aid=##PBS-AUCTIONID##&l=##PBS-LINEID##") + + v.SetDefault("experiment.adscert.mode", "off") + v.SetDefault("experiment.adscert.inprocess.origin", "") + v.SetDefault("experiment.adscert.inprocess.key", "") + v.SetDefault("experiment.adscert.inprocess.domain_check_interval_seconds", 30) + v.SetDefault("experiment.adscert.inprocess.domain_renewal_interval_seconds", 30) + v.SetDefault("experiment.adscert.remote.url", "") + v.SetDefault("experiment.adscert.remote.signing_timeout_ms", 5) + + v.SetDefault("hooks.enabled", false) + + for bidderName := range bidderInfos { + setBidderDefaults(v, strings.ToLower(bidderName)) + } } func migrateConfig(v *viper.Viper) { @@ -1226,6 +1104,41 @@ func migrateConfigSpecialFeature1(v *viper.Viper) { } } +func migrateConfigTCF2PurposeFlags(v *viper.Viper) { + migrateConfigTCF2EnforcePurposeFlags(v) + migrateConfigTCF2PurposeEnabledFlags(v) +} + +func migrateConfigTCF2EnforcePurposeFlags(v *viper.Viper) { + for i := 1; i <= 10; i++ { + algoField := fmt.Sprintf("gdpr.tcf2.purpose%d.enforce_algo", i) + purposeField := fmt.Sprintf("gdpr.tcf2.purpose%d.enforce_purpose", i) + + if !v.IsSet(purposeField) { + continue + } + if _, ok := v.Get(purposeField).(string); !ok { + continue + } + if v.IsSet(algoField) { + glog.Warningf("using %s and ignoring deprecated %s string type", algoField, purposeField) + } else { + v.Set(algoField, TCF2EnforceAlgoFull) + + glog.Warningf("setting %s to \"%s\" based on deprecated %s string type \"%s\"", algoField, TCF2EnforceAlgoFull, purposeField, v.GetString(purposeField)) + } + + oldPurposeFieldValue := v.GetString(purposeField) + newPurposeFieldValue := "false" + if oldPurposeFieldValue == TCF2EnforceAlgoFull { + newPurposeFieldValue = "true" + } + + glog.Warningf("converting %s from string \"%s\" to bool \"%s\"; string type is deprecated", purposeField, oldPurposeFieldValue, newPurposeFieldValue) + v.Set(purposeField, newPurposeFieldValue) + } +} + func migrateConfigTCF2PurposeEnabledFlags(v *viper.Viper) { for i := 1; i <= 10; i++ { oldField := fmt.Sprintf("gdpr.tcf2.purpose%d.enabled", i) @@ -1237,36 +1150,252 @@ func migrateConfigTCF2PurposeEnabledFlags(v *viper.Viper) { 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) - } + v.Set(newField, oldConfig) } } if v.IsSet(newField) { - if v.GetString(newField) == TCF2FullEnforcement { - v.Set(oldField, "true") - } else { - v.Set(oldField, "false") + v.Set(oldField, strconv.FormatBool(v.GetBool(newField))) + } + } +} + +func migrateConfigDatabaseConnection(v *viper.Viper) { + + type QueryParamMigration struct { + old string + new string + } + + type QueryMigration struct { + name string + params []QueryParamMigration + } + + type Migration struct { + old string + new string + fields []string + queryMigrations []QueryMigration + } + + queryParamMigrations := struct { + RequestIdList QueryParamMigration + ImpIdList QueryParamMigration + IdList QueryParamMigration + LastUpdated QueryParamMigration + }{ + RequestIdList: QueryParamMigration{ + old: "%REQUEST_ID_LIST%", + new: "$REQUEST_ID_LIST", + }, + ImpIdList: QueryParamMigration{ + old: "%IMP_ID_LIST%", + new: "$IMP_ID_LIST", + }, + IdList: QueryParamMigration{ + old: "%ID_LIST%", + new: "$ID_LIST", + }, + LastUpdated: QueryParamMigration{ + old: "$1", + new: "$LAST_UPDATED", + }, + } + + queryMigrations := []QueryMigration{ + { + name: "fetcher.query", + params: []QueryParamMigration{queryParamMigrations.RequestIdList, queryParamMigrations.ImpIdList, queryParamMigrations.IdList}, + }, + { + name: "fetcher.amp_query", + params: []QueryParamMigration{queryParamMigrations.RequestIdList, queryParamMigrations.ImpIdList, queryParamMigrations.IdList}, + }, + { + name: "poll_for_updates.query", + params: []QueryParamMigration{queryParamMigrations.LastUpdated}, + }, + { + name: "poll_for_updates.amp_query", + params: []QueryParamMigration{queryParamMigrations.LastUpdated}, + }, + } + + migrations := []Migration{ + { + old: "stored_requests.postgres", + new: "stored_requests.database", + fields: []string{ + "connection.dbname", + "connection.host", + "connection.port", + "connection.user", + "connection.password", + "fetcher.query", + "fetcher.amp_query", + "initialize_caches.timeout_ms", + "initialize_caches.query", + "initialize_caches.amp_query", + "poll_for_updates.refresh_rate_seconds", + "poll_for_updates.timeout_ms", + "poll_for_updates.query", + "poll_for_updates.amp_query", + }, + queryMigrations: queryMigrations, + }, + { + old: "stored_video_req.postgres", + new: "stored_video_req.database", + fields: []string{ + "connection.dbname", + "connection.host", + "connection.port", + "connection.user", + "connection.password", + "fetcher.query", + "initialize_caches.timeout_ms", + "initialize_caches.query", + "poll_for_updates.refresh_rate_seconds", + "poll_for_updates.timeout_ms", + "poll_for_updates.query", + }, + queryMigrations: queryMigrations, + }, + { + old: "stored_responses.postgres", + new: "stored_responses.database", + fields: []string{ + "connection.dbname", + "connection.host", + "connection.port", + "connection.user", + "connection.password", + "fetcher.query", + "initialize_caches.timeout_ms", + "initialize_caches.query", + "poll_for_updates.refresh_rate_seconds", + "poll_for_updates.timeout_ms", + "poll_for_updates.query", + }, + queryMigrations: queryMigrations, + }, + } + + for _, migration := range migrations { + driverField := migration.new + ".connection.driver" + newConfigInfoPresent := isConfigInfoPresent(v, migration.new, migration.fields) + oldConfigInfoPresent := isConfigInfoPresent(v, migration.old, migration.fields) + + if !newConfigInfoPresent && oldConfigInfoPresent { + glog.Warning(fmt.Sprintf("%s is deprecated and should be changed to %s", migration.old, migration.new)) + glog.Warning(fmt.Sprintf("%s is not set, using default (postgres)", driverField)) + v.Set(driverField, "postgres") + + for _, field := range migration.fields { + oldField := migration.old + "." + field + newField := migration.new + "." + field + if v.IsSet(oldField) { + glog.Warning(fmt.Sprintf("%s is deprecated and should be changed to %s", oldField, newField)) + v.Set(newField, v.Get(oldField)) + } + } + + for _, queryMigration := range migration.queryMigrations { + oldQueryField := migration.old + "." + queryMigration.name + newQueryField := migration.new + "." + queryMigration.name + queryString := v.GetString(oldQueryField) + for _, queryParam := range queryMigration.params { + if strings.Contains(queryString, queryParam.old) { + glog.Warning(fmt.Sprintf("Query param %s for %s is deprecated and should be changed to %s", queryParam.old, oldQueryField, queryParam.new)) + queryString = strings.ReplaceAll(queryString, queryParam.old, queryParam.new) + v.Set(newQueryField, queryString) + } + } + } + } else if newConfigInfoPresent && oldConfigInfoPresent { + glog.Warning(fmt.Sprintf("using %s and ignoring deprecated %s", migration.new, migration.old)) + + for _, field := range migration.fields { + oldField := migration.old + "." + field + newField := migration.new + "." + field + if v.IsSet(oldField) { + glog.Warning(fmt.Sprintf("using %s and ignoring deprecated %s", newField, oldField)) + } } } } } +func isConfigInfoPresent(v *viper.Viper, prefix string, fields []string) bool { + prefix = prefix + "." + for _, field := range fields { + fieldName := prefix + field + if v.IsSet(fieldName) { + return true + } + } + return false +} + +func bindDatabaseEnvVars(v *viper.Viper) { + v.BindEnv("stored_requests.database.connection.driver") + v.BindEnv("stored_requests.database.connection.dbname") + v.BindEnv("stored_requests.database.connection.host") + v.BindEnv("stored_requests.database.connection.port") + v.BindEnv("stored_requests.database.connection.user") + v.BindEnv("stored_requests.database.connection.password") + v.BindEnv("stored_requests.database.fetcher.query") + v.BindEnv("stored_requests.database.fetcher.amp_query") + v.BindEnv("stored_requests.database.initialize_caches.timeout_ms") + v.BindEnv("stored_requests.database.initialize_caches.query") + v.BindEnv("stored_requests.database.initialize_caches.amp_query") + v.BindEnv("stored_requests.database.poll_for_updates.refresh_rate_seconds") + v.BindEnv("stored_requests.database.poll_for_updates.timeout_ms") + v.BindEnv("stored_requests.database.poll_for_updates.query") + v.BindEnv("stored_requests.database.poll_for_updates.amp_query") + v.BindEnv("stored_video_req.database.connection.driver") + v.BindEnv("stored_video_req.database.connection.dbname") + v.BindEnv("stored_video_req.database.connection.host") + v.BindEnv("stored_video_req.database.connection.port") + v.BindEnv("stored_video_req.database.connection.user") + v.BindEnv("stored_video_req.database.connection.password") + v.BindEnv("stored_video_req.database.fetcher.query") + v.BindEnv("stored_video_req.database.initialize_caches.timeout_ms") + v.BindEnv("stored_video_req.database.initialize_caches.query") + v.BindEnv("stored_video_req.database.poll_for_updates.refresh_rate_seconds") + v.BindEnv("stored_video_req.database.poll_for_updates.timeout_ms") + v.BindEnv("stored_video_req.database.poll_for_updates.query") + v.BindEnv("stored_responses.database.connection.driver") + v.BindEnv("stored_responses.database.connection.dbname") + v.BindEnv("stored_responses.database.connection.host") + v.BindEnv("stored_responses.database.connection.port") + v.BindEnv("stored_responses.database.connection.user") + v.BindEnv("stored_responses.database.connection.password") + v.BindEnv("stored_responses.database.fetcher.query") + v.BindEnv("stored_responses.database.initialize_caches.timeout_ms") + v.BindEnv("stored_responses.database.initialize_caches.query") + v.BindEnv("stored_responses.database.poll_for_updates.refresh_rate_seconds") + v.BindEnv("stored_responses.database.poll_for_updates.timeout_ms") + v.BindEnv("stored_responses.database.poll_for_updates.query") +} + func setBidderDefaults(v *viper.Viper, bidder string) { 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+".disabled", "") + v.BindEnv(adapterCfgPrefix+".endpoint", "") + v.BindEnv(adapterCfgPrefix+".extra_info", "") + v.BindEnv(adapterCfgPrefix+".modifyingVastXmlAllowed", "") + v.BindEnv(adapterCfgPrefix+".debug.allow", "") + v.BindEnv(adapterCfgPrefix+".gvlVendorID", "") + v.BindEnv(adapterCfgPrefix+".usersync_url", "") + v.BindEnv(adapterCfgPrefix+".experiment.adsCert.enabled", "") + v.BindEnv(adapterCfgPrefix+".platform_id", "") + v.BindEnv(adapterCfgPrefix+".app_secret", "") + v.BindEnv(adapterCfgPrefix+".xapi.username", "") + v.BindEnv(adapterCfgPrefix+".xapi.password", "") + v.BindEnv(adapterCfgPrefix+".xapi.tracker", "") + v.BindEnv(adapterCfgPrefix+".endpointCompression", "") v.BindEnv(adapterCfgPrefix + ".usersync.key") v.BindEnv(adapterCfgPrefix + ".usersync.default") diff --git a/config/config_test.go b/config/config_test.go index 4f9bc44cd8e..3298bec9d05 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -5,19 +5,39 @@ import ( "errors" "net" "os" + "strconv" "strings" "testing" "time" - "encoding/json" - "github.com/prebid/go-gdpr/consentconstants" - "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" "github.com/spf13/viper" "github.com/stretchr/testify/assert" ) +var bidderInfos = BidderInfos{ + "bidder1": BidderInfo{ + Endpoint: "http://bidder1.com", + Maintainer: &MaintainerInfo{Email: "maintainer@bidder1.com"}, + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}, + }, + }, + }, + "bidder2": BidderInfo{ + Endpoint: "http://bidder2.com", + Maintainer: &MaintainerInfo{Email: "maintainer@bidder2.com"}, + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}, + }, + }, + UserSyncURL: "http://bidder2.com/usersync", + }, +} + func TestExternalCacheURLValidate(t *testing.T) { testCases := []struct { desc string @@ -131,12 +151,13 @@ 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, "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") cmpBools(t, "account_required", cfg.AccountRequired, false) cmpInts(t, "metrics.influxdb.collection_rate_seconds", cfg.Metrics.Influxdb.MetricSendInterval, 20) cmpBools(t, "account_adapter_details", cfg.Metrics.Disabled.AccountAdapterDetails, false) + cmpBools(t, "account_debug", cfg.Metrics.Disabled.AccountDebug, true) + cmpBools(t, "account_stored_responses", cfg.Metrics.Disabled.AccountStoredResponses, true) cmpBools(t, "adapter_connections_metrics", cfg.Metrics.Disabled.AdapterConnectionMetrics, true) cmpBools(t, "adapter_gdpr_request_blocked", cfg.Metrics.Disabled.AdapterGDPRRequestBlocked, false) cmpStrings(t, "certificates_file", cfg.PemCertsFile, "") @@ -144,76 +165,107 @@ 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) + cmpStrings(t, "experiment.adscert.mode", cfg.Experiment.AdCerts.Mode, "off") + cmpStrings(t, "experiment.adscert.inprocess.origin", cfg.Experiment.AdCerts.InProcess.Origin, "") + cmpStrings(t, "experiment.adscert.inprocess.key", cfg.Experiment.AdCerts.InProcess.PrivateKey, "") + cmpInts(t, "experiment.adscert.inprocess.domain_check_interval_seconds", cfg.Experiment.AdCerts.InProcess.DNSCheckIntervalInSeconds, 30) + cmpInts(t, "experiment.adscert.inprocess.domain_renewal_interval_seconds", cfg.Experiment.AdCerts.InProcess.DNSRenewalIntervalInSeconds, 30) + cmpStrings(t, "experiment.adscert.remote.url", cfg.Experiment.AdCerts.Remote.Url, "") + cmpInts(t, "experiment.adscert.remote.signing_timeout_ms", cfg.Experiment.AdCerts.Remote.SigningTimeoutMs, 5) + cmpNils(t, "host_schain_node", cfg.HostSChainNode) + cmpStrings(t, "datacenter", cfg.DataCenter, "") + cmpBools(t, "hooks.enabled", cfg.Hooks.Enabled, false) + cmpBools(t, "account_modules_metrics", cfg.Metrics.Disabled.AccountModulesMetrics, false) //Assert purpose VendorExceptionMap hash tables were built correctly expectedTCF2 := TCF2{ Enabled: true, Purpose1: TCF2Purpose{ Enabled: true, - EnforcePurpose: TCF2FullEnforcement, + EnforceAlgo: TCF2EnforceAlgoFull, + EnforceAlgoID: TCF2FullEnforcement, + EnforcePurpose: true, EnforceVendors: true, VendorExceptions: []openrtb_ext.BidderName{}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose2: TCF2Purpose{ Enabled: true, - EnforcePurpose: TCF2FullEnforcement, + EnforceAlgo: TCF2EnforceAlgoFull, + EnforceAlgoID: TCF2FullEnforcement, + EnforcePurpose: true, EnforceVendors: true, VendorExceptions: []openrtb_ext.BidderName{}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose3: TCF2Purpose{ Enabled: true, - EnforcePurpose: TCF2FullEnforcement, + EnforceAlgo: TCF2EnforceAlgoFull, + EnforceAlgoID: TCF2FullEnforcement, + EnforcePurpose: true, EnforceVendors: true, VendorExceptions: []openrtb_ext.BidderName{}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose4: TCF2Purpose{ Enabled: true, - EnforcePurpose: TCF2FullEnforcement, + EnforceAlgo: TCF2EnforceAlgoFull, + EnforceAlgoID: TCF2FullEnforcement, + EnforcePurpose: true, EnforceVendors: true, VendorExceptions: []openrtb_ext.BidderName{}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose5: TCF2Purpose{ Enabled: true, - EnforcePurpose: TCF2FullEnforcement, + EnforceAlgo: TCF2EnforceAlgoFull, + EnforceAlgoID: TCF2FullEnforcement, + EnforcePurpose: true, EnforceVendors: true, VendorExceptions: []openrtb_ext.BidderName{}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose6: TCF2Purpose{ Enabled: true, - EnforcePurpose: TCF2FullEnforcement, + EnforceAlgo: TCF2EnforceAlgoFull, + EnforceAlgoID: TCF2FullEnforcement, + EnforcePurpose: true, EnforceVendors: true, VendorExceptions: []openrtb_ext.BidderName{}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose7: TCF2Purpose{ Enabled: true, - EnforcePurpose: TCF2FullEnforcement, + EnforceAlgo: TCF2EnforceAlgoFull, + EnforceAlgoID: TCF2FullEnforcement, + EnforcePurpose: true, EnforceVendors: true, VendorExceptions: []openrtb_ext.BidderName{}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose8: TCF2Purpose{ Enabled: true, - EnforcePurpose: TCF2FullEnforcement, + EnforceAlgo: TCF2EnforceAlgoFull, + EnforceAlgoID: TCF2FullEnforcement, + EnforcePurpose: true, EnforceVendors: true, VendorExceptions: []openrtb_ext.BidderName{}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose9: TCF2Purpose{ Enabled: true, - EnforcePurpose: TCF2FullEnforcement, + EnforceAlgo: TCF2EnforceAlgoFull, + EnforceAlgoID: TCF2FullEnforcement, + EnforcePurpose: true, EnforceVendors: true, VendorExceptions: []openrtb_ext.BidderName{}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose10: TCF2Purpose{ Enabled: true, - EnforcePurpose: TCF2FullEnforcement, + EnforceAlgo: TCF2EnforceAlgoFull, + EnforceAlgoID: TCF2FullEnforcement, + EnforcePurpose: true, EnforceVendors: true, VendorExceptions: []openrtb_ext.BidderName{}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, @@ -241,9 +293,6 @@ func TestDefaults(t *testing.T) { 10: &expectedTCF2.Purpose10, } 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(` @@ -258,10 +307,12 @@ gdpr: vendor_exceptions: ["foo1a", "foo1b"] purpose2: enabled: false - enforce_purpose: "no" + enforce_algo: "full" + enforce_purpose: false enforce_vendors: false vendor_exceptions: ["foo2"] purpose3: + enforce_algo: "basic" enforce_vendors: false vendor_exceptions: ["foo3"] purpose4: @@ -303,6 +354,7 @@ host: prebid-server.prebid.org port: 1234 admin_port: 5678 garbage_collector_threshold: 1 +datacenter: "1" auction_timeouts_ms: max: 123 default: 50 @@ -339,33 +391,11 @@ metrics: metric_send_interval: 30 disabled_metrics: account_adapter_details: true + account_debug: false + account_stored_responses: false adapter_connections_metrics: true adapter_gdpr_request_blocked: true -adapters: - appnexus: - endpoint: http://ib.adnxs.com/some/endpoint - 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 - app_secret: 987abc - ix: - endpoint: http://ixtest.com/api - rubicon: - endpoint: http://rubitest.com/api - 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 - adkerneladn: - usersync_url: https://tag.adkernel.com/syncr?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r= + account_modules_metrics: true blacklisted_apps: ["spamAppID","sketchy-app-id"] account_required: true auto_gen_source_tid: false @@ -374,30 +404,24 @@ request_validation: ipv4_private_networks: ["1.1.1.0/24"] ipv6_private_networks: ["1111::/16", "2222::/16"] generate_bid_id: true -`) - -var adapterExtraInfoConfig = []byte(` -adapters: - appnexus: - endpoint: http://ib.adnxs.com/some/endpoint - usersync_url: http://adnxs.com/sync.php?p=prebid - platform_id: appNexus - xapi: - username: appuser - password: 123456 - tracker: anxsTrack - disabled: true - extra_info: "{\"native\":\"http://www.native.org/endpoint\",\"video\":\"http://www.video.org/endpoint\"}" -`) - -var invalidAdapterEndpointConfig = []byte(` -adapters: - appnexus: - endpoint: ib.adnxs.com/some/endpoint - brightroll: - usersync: - redirect: - url: http://http://test-bh.ybp.yahoo.com/sync/appnexuspbs?gdpr={{.GDPR}}&euconsent={{.GDPRConsent}}&url=%s +host_schain_node: + asi: "pbshostcompany.com" + sid: "00001" + rid: "BidRequest" + hp: 1 +experiment: + adscert: + mode: inprocess + inprocess: + origin: "http://test.com" + key: "ABC123" + domain_check_interval_seconds: 40 + domain_renewal_interval_seconds : 60 + remote: + url: "" + signing_timeout_ms: 10 +hooks: + enabled: true `) var oldStoredRequestsConfig = []byte(` @@ -416,6 +440,11 @@ func cmpInts(t *testing.T, key string, a int, b int) { assert.Equal(t, a, b, "%s: %d != %d", key, a, b) } +func cmpInt8s(t *testing.T, key string, a *int8, b *int8) { + t.Helper() + assert.Equal(t, a, b, "%s: %d != %d", key, a, b) +} + func cmpBools(t *testing.T, key string, a bool, b bool) { t.Helper() assert.Equal(t, a, b, "%s: %t != %t", key, a, b) @@ -427,11 +456,13 @@ func cmpNils(t *testing.T, key string, a interface{}) { } func TestFullConfig(t *testing.T) { + int8One := int8(1) + v := viper.New() - SetupViper(v, "") + SetupViper(v, "", bidderInfos) v.SetConfigType("yaml") v.ReadConfig(bytes.NewBuffer(fullConfig)) - cfg, err := New(v) + cfg, err := New(v, bidderInfos, mockNormalizeBidderName) assert.NoError(t, err, "Setting up config should work but it doesn't") cmpStrings(t, "cookie domain", cfg.HostCookie.Domain, "cookies.prebid.org") cmpStrings(t, "cookie name", cfg.HostCookie.CookieName, "userid") @@ -461,6 +492,11 @@ func TestFullConfig(t *testing.T) { 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, "1") + cmpStrings(t, "host_schain_node.asi", cfg.HostSChainNode.ASI, "pbshostcompany.com") + cmpStrings(t, "host_schain_node.sid", cfg.HostSChainNode.SID, "00001") + cmpStrings(t, "host_schain_node.rid", cfg.HostSChainNode.RID, "BidRequest") + cmpInt8s(t, "host_schain_node.hp", cfg.HostSChainNode.HP, &int8One) + cmpStrings(t, "datacenter", cfg.DataCenter, "1") //Assert the NonStandardPublishers was correctly unmarshalled assert.Equal(t, []string{"pub1", "pub2"}, cfg.GDPR.NonStandardPublishers, "gdpr.non_standard_publishers") @@ -487,70 +523,90 @@ func TestFullConfig(t *testing.T) { Enabled: true, Purpose1: TCF2Purpose{ Enabled: true, // true by default - EnforcePurpose: TCF2FullEnforcement, + EnforceAlgo: TCF2EnforceAlgoFull, + EnforceAlgoID: TCF2FullEnforcement, + EnforcePurpose: true, 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, + EnforceAlgo: TCF2EnforceAlgoFull, + EnforceAlgoID: TCF2FullEnforcement, + EnforcePurpose: 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 - EnforcePurpose: TCF2FullEnforcement, + EnforceAlgo: TCF2EnforceAlgoBasic, + EnforceAlgoID: TCF2BasicEnforcement, + EnforcePurpose: true, 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, + EnforceAlgo: TCF2EnforceAlgoFull, + EnforceAlgoID: TCF2FullEnforcement, + EnforcePurpose: true, 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, + EnforceAlgo: TCF2EnforceAlgoFull, + EnforceAlgoID: TCF2FullEnforcement, + EnforcePurpose: true, 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, + EnforceAlgo: TCF2EnforceAlgoFull, + EnforceAlgoID: TCF2FullEnforcement, + EnforcePurpose: true, 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, + EnforceAlgo: TCF2EnforceAlgoFull, + EnforceAlgoID: TCF2FullEnforcement, + EnforcePurpose: true, 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, + EnforceAlgo: TCF2EnforceAlgoFull, + EnforceAlgoID: TCF2FullEnforcement, + EnforcePurpose: true, 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, + EnforceAlgo: TCF2EnforceAlgoFull, + EnforceAlgoID: TCF2FullEnforcement, + EnforcePurpose: true, 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, + EnforceAlgo: TCF2EnforceAlgoFull, + EnforceAlgoID: TCF2FullEnforcement, + EnforcePurpose: true, EnforceVendors: false, VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo10")}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo10"): {}}, @@ -591,26 +647,11 @@ func TestFullConfig(t *testing.T) { cmpInts(t, "metrics.influxdb.metric_send_interval", cfg.Metrics.Influxdb.MetricSendInterval, 30) 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") - 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.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) cmpBools(t, "auto_gen_source_tid", cfg.AutoGenSourceTID, false) cmpBools(t, "account_adapter_details", cfg.Metrics.Disabled.AccountAdapterDetails, true) + cmpBools(t, "account_debug", cfg.Metrics.Disabled.AccountDebug, false) + cmpBools(t, "account_stored_responses", cfg.Metrics.Disabled.AccountStoredResponses, false) cmpBools(t, "adapter_connections_metrics", cfg.Metrics.Disabled.AdapterConnectionMetrics, true) cmpBools(t, "adapter_gdpr_request_blocked", cfg.Metrics.Disabled.AdapterGDPRRequestBlocked, true) cmpStrings(t, "certificates_file", cfg.PemCertsFile, "/etc/ssl/cert.pem") @@ -619,36 +660,15 @@ 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, "") -} - -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) - - // Assert correctly unmarshaled - assert.NoError(t, err, "invalid endpoint in config should return an error") - - // 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") - - // Data type where we'll unmarshal endpoint values and adapter custom extra information - type AppNexusAdapterEndpoints struct { - NativeEndpoint string `json:"native,omitempty"` - VideoEndpoint string `json:"video,omitempty"` - } - var AppNexusAdapterExtraInfo AppNexusAdapterEndpoints - err = json.Unmarshal([]byte(cfg.Adapters[string(openrtb_ext.BidderAppnexus)].ExtraAdapterInfo), &AppNexusAdapterExtraInfo) - - // Assert correctly unmarshaled - assert.NoErrorf(t, err, "Error. Could not unmarshal cfg.Adapters[string(openrtb_ext.BidderAppnexus)].ExtraAdapterInfo. Value: %s. Error: %v \n", cfg.Adapters[string(openrtb_ext.BidderAppnexus)].ExtraAdapterInfo, err) - - // Assert endpoint values - assert.Equal(t, "http://www.native.org/endpoint", AppNexusAdapterExtraInfo.NativeEndpoint) - assert.Equal(t, "http://www.video.org/endpoint", AppNexusAdapterExtraInfo.VideoEndpoint) + cmpStrings(t, "experiment.adscert.mode", cfg.Experiment.AdCerts.Mode, "inprocess") + cmpStrings(t, "experiment.adscert.inprocess.origin", cfg.Experiment.AdCerts.InProcess.Origin, "http://test.com") + cmpStrings(t, "experiment.adscert.inprocess.key", cfg.Experiment.AdCerts.InProcess.PrivateKey, "ABC123") + cmpInts(t, "experiment.adscert.inprocess.domain_check_interval_seconds", cfg.Experiment.AdCerts.InProcess.DNSCheckIntervalInSeconds, 40) + cmpInts(t, "experiment.adscert.inprocess.domain_renewal_interval_seconds", cfg.Experiment.AdCerts.InProcess.DNSRenewalIntervalInSeconds, 60) + cmpStrings(t, "experiment.adscert.remote.url", cfg.Experiment.AdCerts.Remote.Url, "") + cmpInts(t, "experiment.adscert.remote.signing_timeout_ms", cfg.Experiment.AdCerts.Remote.SigningTimeoutMs, 10) + cmpBools(t, "hooks.enabled", cfg.Hooks.Enabled, true) + cmpBools(t, "account_modules_metrics", cfg.Metrics.Disabled.AccountModulesMetrics, true) } func TestValidateConfig(t *testing.T) { @@ -656,16 +676,16 @@ func TestValidateConfig(t *testing.T) { 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}, + Purpose1: TCF2Purpose{EnforceAlgo: TCF2EnforceAlgoBasic}, + Purpose2: TCF2Purpose{EnforceAlgo: TCF2EnforceAlgoFull}, + Purpose3: TCF2Purpose{EnforceAlgo: TCF2EnforceAlgoBasic}, + Purpose4: TCF2Purpose{EnforceAlgo: TCF2EnforceAlgoFull}, + Purpose5: TCF2Purpose{EnforceAlgo: TCF2EnforceAlgoBasic}, + Purpose6: TCF2Purpose{EnforceAlgo: TCF2EnforceAlgoFull}, + Purpose7: TCF2Purpose{EnforceAlgo: TCF2EnforceAlgoBasic}, + Purpose8: TCF2Purpose{EnforceAlgo: TCF2EnforceAlgoFull}, + Purpose9: TCF2Purpose{EnforceAlgo: TCF2EnforceAlgoBasic}, + Purpose10: TCF2Purpose{EnforceAlgo: TCF2EnforceAlgoFull}, }, }, StoredRequests: StoredRequests{ @@ -699,12 +719,12 @@ func TestValidateConfig(t *testing.T) { func TestMigrateConfig(t *testing.T) { v := viper.New() - SetupViper(v, "") + SetupViper(v, "", bidderInfos) v.Set("gdpr.default_value", "0") v.SetConfigType("yaml") v.ReadConfig(bytes.NewBuffer(oldStoredRequestsConfig)) migrateConfig(v) - cfg, err := New(v) + cfg, err := New(v, bidderInfos, mockNormalizeBidderName) assert.NoError(t, err, "Setting up config should work but it doesn't") cmpBools(t, "stored_requests.filesystem.enabled", true, cfg.StoredRequests.Files.Enabled) cmpStrings(t, "stored_requests.filesystem.path", "/somepath", cfg.StoredRequests.Files.Path) @@ -716,9 +736,139 @@ func TestMigrateConfigFromEnv(t *testing.T) { } else { defer os.Unsetenv("PBS_STORED_REQUESTS_FILESYSTEM") } + + if oldval, ok := os.LookupEnv("PBS_ADAPTERS_BIDDER1_ENDPOINT"); ok { + defer os.Setenv("PBS_ADAPTERS_BIDDER1_ENDPOINT", oldval) + } else { + defer os.Unsetenv("PBS_ADAPTERS_BIDDER1_ENDPOINT") + } + os.Setenv("PBS_STORED_REQUESTS_FILESYSTEM", "true") + os.Setenv("PBS_ADAPTERS_BIDDER1_ENDPOINT", "http://bidder1_override.com") cfg, _ := newDefaultConfig(t) cmpBools(t, "stored_requests.filesystem.enabled", true, cfg.StoredRequests.Files.Enabled) + cmpStrings(t, "adapters.bidder1.endpoint", "http://bidder1_override.com", cfg.BidderInfos["bidder1"].Endpoint) +} + +func TestUserSyncFromEnv(t *testing.T) { + truePtr := true + + // setup env vars for testing + if oldval, ok := os.LookupEnv("PBS_ADAPTERS_BIDDER1_USERSYNC_REDIRECT_URL"); ok { + defer os.Setenv("PBS_ADAPTERS_BIDDER1_USERSYNC_REDIRECT_URL", oldval) + } else { + defer os.Unsetenv("PBS_ADAPTERS_BIDDER1_USERSYNC_REDIRECT_URL") + } + + if oldval, ok := os.LookupEnv("PBS_ADAPTERS_BIDDER1_USERSYNC_REDIRECT_USER_MACRO"); ok { + defer os.Setenv("PBS_ADAPTERS_BIDDER1_USERSYNC_REDIRECT_USER_MACRO", oldval) + } else { + defer os.Unsetenv("PBS_ADAPTERS_BIDDER1_USERSYNC_REDIRECT_USER_MACRO") + } + + if oldval, ok := os.LookupEnv("PBS_ADAPTERS_BIDDER1_USERSYNC_SUPPORT_CORS"); ok { + defer os.Setenv("PBS_ADAPTERS_BIDDER1_USERSYNC_SUPPORT_CORS", oldval) + } else { + defer os.Unsetenv("PBS_ADAPTERS_BIDDER1_USERSYNC_SUPPORT_CORS") + } + + if oldval, ok := os.LookupEnv("PBS_ADAPTERS_BIDDER2_USERSYNC_IFRAME_URL"); ok { + defer os.Setenv("PBS_ADAPTERS_BIDDER2_USERSYNC_IFRAME_URL", oldval) + } else { + defer os.Unsetenv("PBS_ADAPTERS_BIDDER2_USERSYNC_IFRAME_URL") + } + + // set new + os.Setenv("PBS_ADAPTERS_BIDDER1_USERSYNC_REDIRECT_URL", "http://some.url/sync?redirect={{.RedirectURL}}") + os.Setenv("PBS_ADAPTERS_BIDDER1_USERSYNC_REDIRECT_USER_MACRO", "[UID]") + os.Setenv("PBS_ADAPTERS_BIDDER1_USERSYNC_SUPPORT_CORS", "true") + os.Setenv("PBS_ADAPTERS_BIDDER2_USERSYNC_IFRAME_URL", "http://somedifferent.url/sync?redirect={{.RedirectURL}}") + + cfg, _ := newDefaultConfig(t) + + assert.Equal(t, "http://some.url/sync?redirect={{.RedirectURL}}", cfg.BidderInfos["bidder1"].Syncer.Redirect.URL) + assert.Equal(t, "[UID]", cfg.BidderInfos["bidder1"].Syncer.Redirect.UserMacro) + assert.Nil(t, cfg.BidderInfos["bidder1"].Syncer.IFrame) + assert.Equal(t, &truePtr, cfg.BidderInfos["bidder1"].Syncer.SupportCORS) + + assert.Equal(t, "http://somedifferent.url/sync?redirect={{.RedirectURL}}", cfg.BidderInfos["bidder2"].Syncer.IFrame.URL) + assert.Nil(t, cfg.BidderInfos["bidder2"].Syncer.Redirect) + assert.Nil(t, cfg.BidderInfos["bidder2"].Syncer.SupportCORS) + + assert.Nil(t, cfg.BidderInfos["brightroll"].Syncer) +} + +func TestBidderInfoFromEnv(t *testing.T) { + // setup env vars for testing + if oldval, ok := os.LookupEnv("PBS_ADAPTERS_BIDDER1_DISABLED"); ok { + defer os.Setenv("PBS_ADAPTERS_BIDDER1_DISABLED", oldval) + } else { + defer os.Unsetenv("PBS_ADAPTERS_BIDDER1_DISABLED") + } + + if oldval, ok := os.LookupEnv("PBS_ADAPTERS_BIDDER1_ENDPOINT"); ok { + defer os.Setenv("PBS_ADAPTERS_BIDDER1_ENDPOINT", oldval) + } else { + defer os.Unsetenv("PBS_ADAPTERS_BIDDER1_ENDPOINT") + } + + if oldval, ok := os.LookupEnv("PBS_ADAPTERS_BIDDER1_EXTRA_INFO"); ok { + defer os.Setenv("PBS_ADAPTERS_BIDDER1_EXTRA_INFO", oldval) + } else { + defer os.Unsetenv("PBS_ADAPTERS_BIDDER1_EXTRA_INFO") + } + + if oldval, ok := os.LookupEnv("PBS_ADAPTERS_BIDDER1_DEBUG_ALLOW"); ok { + defer os.Setenv("PBS_ADAPTERS_BIDDER1_DEBUG_ALLOW", oldval) + } else { + defer os.Unsetenv("PBS_ADAPTERS_BIDDER1_DEBUG_ALLOW") + } + + if oldval, ok := os.LookupEnv("PBS_ADAPTERS_BIDDER1_GVLVENDORID"); ok { + defer os.Setenv("PBS_ADAPTERS_BIDDER1_GVLVENDORID", oldval) + } else { + defer os.Unsetenv("PBS_ADAPTERS_BIDDER1_GVLVENDORID") + } + + if oldval, ok := os.LookupEnv("PBS_ADAPTERS_BIDDER1_EXPERIMENT_ADSCERT_ENABLED"); ok { + defer os.Setenv("PBS_ADAPTERS_BIDDER1_EXPERIMENT_ADSCERT_ENABLED", oldval) + } else { + defer os.Unsetenv("PBS_ADAPTERS_BIDDER1_EXPERIMENT_ADSCERT_ENABLED") + } + + if oldval, ok := os.LookupEnv("PBS_ADAPTERS_BIDDER1_XAPI_USERNAME"); ok { + defer os.Setenv("PBS_ADAPTERS_BIDDER1_XAPI_USERNAME", oldval) + } else { + defer os.Unsetenv("PBS_ADAPTERS_BIDDER1_XAPI_USERNAME") + } + + if oldval, ok := os.LookupEnv("PBS_ADAPTERS_BIDDER1_USERSYNC_REDIRECT_URL"); ok { + defer os.Setenv("PBS_ADAPTERS_BIDDER1_USERSYNC_REDIRECT_URL", oldval) + } else { + defer os.Unsetenv("PBS_ADAPTERS_BIDDER1_USERSYNC_REDIRECT_URL") + } + + // set new + os.Setenv("PBS_ADAPTERS_BIDDER1_DISABLED", "true") + os.Setenv("PBS_ADAPTERS_BIDDER1_ENDPOINT", "http://some.url/override") + os.Setenv("PBS_ADAPTERS_BIDDER1_EXTRA_INFO", `{"extrainfo": true}`) + os.Setenv("PBS_ADAPTERS_BIDDER1_DEBUG_ALLOW", "true") + os.Setenv("PBS_ADAPTERS_BIDDER1_GVLVENDORID", "42") + os.Setenv("PBS_ADAPTERS_BIDDER1_EXPERIMENT_ADSCERT_ENABLED", "true") + os.Setenv("PBS_ADAPTERS_BIDDER1_XAPI_USERNAME", "username_override") + os.Setenv("PBS_ADAPTERS_BIDDER1_USERSYNC_REDIRECT_URL", "http://some.url/sync?redirect={{.RedirectURL}}") + + cfg, _ := newDefaultConfig(t) + + assert.Equal(t, true, cfg.BidderInfos["bidder1"].Disabled) + assert.Equal(t, "http://some.url/override", cfg.BidderInfos["bidder1"].Endpoint) + assert.Equal(t, `{"extrainfo": true}`, cfg.BidderInfos["bidder1"].ExtraAdapterInfo) + + assert.Equal(t, true, cfg.BidderInfos["bidder1"].Debug.Allow) + assert.Equal(t, uint16(42), cfg.BidderInfos["bidder1"].GVLVendorID) + + assert.Equal(t, true, cfg.BidderInfos["bidder1"].Experiment.AdsCert.Enabled) + assert.Equal(t, "username_override", cfg.BidderInfos["bidder1"].XAPI.Username) } func TestMigrateConfigPurposeOneTreatment(t *testing.T) { @@ -937,16 +1087,16 @@ func TestMigrateConfigTCF2PurposeEnabledFlags(t *testing.T) { purpose10: enabled: true `), - wantPurpose1EnforcePurpose: TCF2NoEnforcement, - wantPurpose2EnforcePurpose: TCF2FullEnforcement, - wantPurpose3EnforcePurpose: TCF2NoEnforcement, - wantPurpose4EnforcePurpose: TCF2FullEnforcement, - wantPurpose5EnforcePurpose: TCF2NoEnforcement, - wantPurpose6EnforcePurpose: TCF2FullEnforcement, - wantPurpose7EnforcePurpose: TCF2NoEnforcement, - wantPurpose8EnforcePurpose: TCF2FullEnforcement, - wantPurpose9EnforcePurpose: TCF2NoEnforcement, - wantPurpose10EnforcePurpose: TCF2FullEnforcement, + wantPurpose1EnforcePurpose: falseStr, + wantPurpose2EnforcePurpose: trueStr, + wantPurpose3EnforcePurpose: falseStr, + wantPurpose4EnforcePurpose: trueStr, + wantPurpose5EnforcePurpose: falseStr, + wantPurpose6EnforcePurpose: trueStr, + wantPurpose7EnforcePurpose: falseStr, + wantPurpose8EnforcePurpose: trueStr, + wantPurpose9EnforcePurpose: falseStr, + wantPurpose10EnforcePurpose: trueStr, wantPurpose1Enabled: falseStr, wantPurpose2Enabled: trueStr, wantPurpose3Enabled: falseStr, @@ -964,36 +1114,36 @@ func TestMigrateConfigTCF2PurposeEnabledFlags(t *testing.T) { gdpr: tcf2: purpose1: - enforce_purpose: "full" + enforce_purpose: true purpose2: - enforce_purpose: "no" + enforce_purpose: false purpose3: - enforce_purpose: "full" + enforce_purpose: true purpose4: - enforce_purpose: "no" + enforce_purpose: false purpose5: - enforce_purpose: "full" + enforce_purpose: true purpose6: - enforce_purpose: "no" + enforce_purpose: false purpose7: - enforce_purpose: "full" + enforce_purpose: true purpose8: - enforce_purpose: "no" + enforce_purpose: false purpose9: - enforce_purpose: "full" + enforce_purpose: true purpose10: - enforce_purpose: "no" + enforce_purpose: false `), - wantPurpose1EnforcePurpose: TCF2FullEnforcement, - wantPurpose2EnforcePurpose: TCF2NoEnforcement, - wantPurpose3EnforcePurpose: TCF2FullEnforcement, - wantPurpose4EnforcePurpose: TCF2NoEnforcement, - wantPurpose5EnforcePurpose: TCF2FullEnforcement, - wantPurpose6EnforcePurpose: TCF2NoEnforcement, - wantPurpose7EnforcePurpose: TCF2FullEnforcement, - wantPurpose8EnforcePurpose: TCF2NoEnforcement, - wantPurpose9EnforcePurpose: TCF2FullEnforcement, - wantPurpose10EnforcePurpose: TCF2NoEnforcement, + wantPurpose1EnforcePurpose: trueStr, + wantPurpose2EnforcePurpose: falseStr, + wantPurpose3EnforcePurpose: trueStr, + wantPurpose4EnforcePurpose: falseStr, + wantPurpose5EnforcePurpose: trueStr, + wantPurpose6EnforcePurpose: falseStr, + wantPurpose7EnforcePurpose: trueStr, + wantPurpose8EnforcePurpose: falseStr, + wantPurpose9EnforcePurpose: trueStr, + wantPurpose10EnforcePurpose: falseStr, wantPurpose1Enabled: trueStr, wantPurpose2Enabled: falseStr, wantPurpose3Enabled: trueStr, @@ -1012,45 +1162,45 @@ func TestMigrateConfigTCF2PurposeEnabledFlags(t *testing.T) { tcf2: purpose1: enabled: false - enforce_purpose: "full" + enforce_purpose: true purpose2: enabled: false - enforce_purpose: "full" + enforce_purpose: true purpose3: enabled: false - enforce_purpose: "full" + enforce_purpose: true purpose4: enabled: false - enforce_purpose: "full" + enforce_purpose: true purpose5: enabled: false - enforce_purpose: "full" + enforce_purpose: true purpose6: enabled: false - enforce_purpose: "full" + enforce_purpose: true purpose7: enabled: false - enforce_purpose: "full" + enforce_purpose: true purpose8: enabled: false - enforce_purpose: "full" + enforce_purpose: true purpose9: enabled: false - enforce_purpose: "full" + enforce_purpose: true purpose10: enabled: false - enforce_purpose: "full" + enforce_purpose: true `), - wantPurpose1EnforcePurpose: TCF2FullEnforcement, - wantPurpose2EnforcePurpose: TCF2FullEnforcement, - wantPurpose3EnforcePurpose: TCF2FullEnforcement, - wantPurpose4EnforcePurpose: TCF2FullEnforcement, - wantPurpose5EnforcePurpose: TCF2FullEnforcement, - wantPurpose6EnforcePurpose: TCF2FullEnforcement, - wantPurpose7EnforcePurpose: TCF2FullEnforcement, - wantPurpose8EnforcePurpose: TCF2FullEnforcement, - wantPurpose9EnforcePurpose: TCF2FullEnforcement, - wantPurpose10EnforcePurpose: TCF2FullEnforcement, + wantPurpose1EnforcePurpose: trueStr, + wantPurpose2EnforcePurpose: trueStr, + wantPurpose3EnforcePurpose: trueStr, + wantPurpose4EnforcePurpose: trueStr, + wantPurpose5EnforcePurpose: trueStr, + wantPurpose6EnforcePurpose: trueStr, + wantPurpose7EnforcePurpose: trueStr, + wantPurpose8EnforcePurpose: trueStr, + wantPurpose9EnforcePurpose: trueStr, + wantPurpose10EnforcePurpose: trueStr, wantPurpose1Enabled: trueStr, wantPurpose2Enabled: trueStr, wantPurpose3Enabled: trueStr, @@ -1117,148 +1267,1445 @@ func TestMigrateConfigTCF2PurposeEnabledFlags(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) - - 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) { - cfg, v := newDefaultConfig(t) - cfg.MaxRequestSize = -1 - assertOneError(t, cfg.validate(v), "cfg.max_request_size must be >= 0. Got -1") -} - -func TestNegativePrometheusTimeout(t *testing.T) { - cfg, v := newDefaultConfig(t) - cfg.Metrics.Prometheus.Port = 8001 - cfg.Metrics.Prometheus.TimeoutMillisRaw = 0 - 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) { +func TestMigrateConfigTCF2PurposeFlags(t *testing.T) { tests := []struct { - description string - vendorID int - wantErrorMsg string + description string + config []byte + wantPurpose1EnforceAlgo string + wantPurpose1EnforcePurpose bool + wantPurpose1Enabled bool }{ { - description: "Negative GDPR.HostVendorID", - vendorID: -1, - wantErrorMsg: "gdpr.host_vendor_id must be in the range [0, 65535]. Got -1", + description: "enforce_purpose does not set enforce_algo but sets enabled", + config: []byte(` + gdpr: + tcf2: + purpose1: + enforce_algo: "off" + enforce_purpose: "full" + enabled: false + purpose2: + enforce_purpose: "full" + enabled: false + purpose3: + enabled: false + `), + wantPurpose1EnforceAlgo: "off", + wantPurpose1EnforcePurpose: true, + wantPurpose1Enabled: true, }, { - description: "Overflowed GDPR.HostVendorID", - vendorID: (0xffff) + 1, - wantErrorMsg: "gdpr.host_vendor_id must be in the range [0, 65535]. Got 65536", + description: "enforce_purpose sets enforce_algo and enabled", + config: []byte(` + gdpr: + tcf2: + purpose1: + enforce_purpose: "full" + enabled: false + `), + wantPurpose1EnforceAlgo: "full", + wantPurpose1EnforcePurpose: true, + wantPurpose1Enabled: true, + }, + { + description: "enforce_purpose does not set enforce_algo or enabled", + config: []byte(` + gdpr: + tcf2: + purpose1: + enabled: false + `), + wantPurpose1EnforceAlgo: "", + wantPurpose1EnforcePurpose: false, + wantPurpose1Enabled: false, }, } for _, tt := range tests { - cfg, v := newDefaultConfig(t) - cfg.GDPR.HostVendorID = tt.vendorID - errs := cfg.validate(v) - - assert.Equal(t, 1, len(errs), tt.description) - assert.EqualError(t, errs[0], tt.wantErrorMsg, tt.description) - } -} - -func TestInvalidAMPException(t *testing.T) { - cfg, v := newDefaultConfig(t) - cfg.GDPR.AMPException = true - assertOneError(t, cfg.validate(v), "gdpr.amp_exception has been discontinued and must be removed from your config. If you need to disable GDPR for AMP, you may do so per-account (gdpr.integration_enabled.amp) or at the host level for the default account (account_defaults.gdpr.integration_enabled.amp)") -} - -func TestInvalidGDPRDefaultValue(t *testing.T) { - cfg, v := newDefaultConfig(t) - cfg.GDPR.DefaultValue = "2" - assertOneError(t, cfg.validate(v), "gdpr.default_value must be 0 or 1") -} - -func TestMissingGDPRDefaultValue(t *testing.T) { - v := viper.New() - - cfg, _ := newDefaultConfig(t) - assertOneError(t, cfg.validate(v), "gdpr.default_value is required and must be specified") -} - -func 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") -} + v := viper.New() + v.SetConfigType("yaml") + v.ReadConfig(bytes.NewBuffer(tt.config)) -func TestNegativeCurrencyConverterFetchInterval(t *testing.T) { - v := viper.New() - v.Set("gdpr.default_value", "0") + migrateConfigTCF2PurposeFlags(v) - cfg := Configuration{ - CurrencyConverter: CurrencyConverter{ - FetchIntervalSeconds: -1, - }, + assert.Equal(t, tt.wantPurpose1EnforceAlgo, v.GetString("gdpr.tcf2.purpose1.enforce_algo"), tt.description) + assert.Equal(t, tt.wantPurpose1EnforcePurpose, v.GetBool("gdpr.tcf2.purpose1.enforce_purpose"), tt.description) + assert.Equal(t, tt.wantPurpose1Enabled, v.GetBool("gdpr.tcf2.purpose1.enabled"), tt.description) } - 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(v) - assert.NotNil(t, err, "cfg.currency_converter.fetch_interval_seconds prevent values over %d, but it doesn't", 0xffff) } -func TestLimitTimeout(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 TestMigrateConfigTCF2EnforcePurposeFlags(t *testing.T) { + trueStr := "true" + falseStr := "false" -func TestCookieSizeError(t *testing.T) { - testCases := []struct { - description string - cookieSize int - expectError bool + tests := []struct { + description string + config []byte + wantEnforceAlgosSet bool + wantPurpose1EnforceAlgo string + wantPurpose2EnforceAlgo string + wantPurpose3EnforceAlgo string + wantPurpose4EnforceAlgo string + wantPurpose5EnforceAlgo string + wantPurpose6EnforceAlgo string + wantPurpose7EnforceAlgo string + wantPurpose8EnforceAlgo string + wantPurpose9EnforceAlgo string + wantPurpose10EnforceAlgo string + wantEnforcePurposesSet bool + wantPurpose1EnforcePurpose string + wantPurpose2EnforcePurpose string + wantPurpose3EnforcePurpose string + wantPurpose4EnforcePurpose string + wantPurpose5EnforcePurpose string + wantPurpose6EnforcePurpose string + wantPurpose7EnforcePurpose string + wantPurpose8EnforcePurpose string + wantPurpose9EnforcePurpose string + wantPurpose10EnforcePurpose string }{ - {"MIN_COOKIE_SIZE_BYTES + 1", MIN_COOKIE_SIZE_BYTES + 1, false}, - {"MIN_COOKIE_SIZE_BYTES", MIN_COOKIE_SIZE_BYTES, false}, - {"MIN_COOKIE_SIZE_BYTES - 1", MIN_COOKIE_SIZE_BYTES - 1, true}, - {"Zero", 0, false}, + { + description: "enforce_algo and enforce_purpose are not set", + config: []byte{}, + wantEnforceAlgosSet: false, + wantEnforcePurposesSet: false, + }, + { + description: "enforce_algo not set; set it based on enforce_purpose string value", + 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" + `), + wantEnforceAlgosSet: true, + wantPurpose1EnforceAlgo: TCF2EnforceAlgoFull, + wantPurpose2EnforceAlgo: TCF2EnforceAlgoFull, + wantPurpose3EnforceAlgo: TCF2EnforceAlgoFull, + wantPurpose4EnforceAlgo: TCF2EnforceAlgoFull, + wantPurpose5EnforceAlgo: TCF2EnforceAlgoFull, + wantPurpose6EnforceAlgo: TCF2EnforceAlgoFull, + wantPurpose7EnforceAlgo: TCF2EnforceAlgoFull, + wantPurpose8EnforceAlgo: TCF2EnforceAlgoFull, + wantPurpose9EnforceAlgo: TCF2EnforceAlgoFull, + wantPurpose10EnforceAlgo: TCF2EnforceAlgoFull, + wantEnforcePurposesSet: true, + wantPurpose1EnforcePurpose: trueStr, + wantPurpose2EnforcePurpose: falseStr, + wantPurpose3EnforcePurpose: trueStr, + wantPurpose4EnforcePurpose: falseStr, + wantPurpose5EnforcePurpose: trueStr, + wantPurpose6EnforcePurpose: falseStr, + wantPurpose7EnforcePurpose: trueStr, + wantPurpose8EnforcePurpose: falseStr, + wantPurpose9EnforcePurpose: trueStr, + wantPurpose10EnforcePurpose: falseStr, + }, + { + description: "enforce_algo not set; don't set it based on enforce_purpose bool value", + config: []byte(` + gdpr: + tcf2: + purpose1: + enforce_purpose: true + purpose2: + enforce_purpose: false + purpose3: + enforce_purpose: true + purpose4: + enforce_purpose: false + purpose5: + enforce_purpose: true + purpose6: + enforce_purpose: false + purpose7: + enforce_purpose: true + purpose8: + enforce_purpose: false + purpose9: + enforce_purpose: true + purpose10: + enforce_purpose: false + `), + wantEnforceAlgosSet: false, + wantEnforcePurposesSet: true, + wantPurpose1EnforcePurpose: trueStr, + wantPurpose2EnforcePurpose: falseStr, + wantPurpose3EnforcePurpose: trueStr, + wantPurpose4EnforcePurpose: falseStr, + wantPurpose5EnforcePurpose: trueStr, + wantPurpose6EnforcePurpose: falseStr, + wantPurpose7EnforcePurpose: trueStr, + wantPurpose8EnforcePurpose: falseStr, + wantPurpose9EnforcePurpose: trueStr, + wantPurpose10EnforcePurpose: falseStr, + }, + { + description: "enforce_algo is set and enforce_purpose is not; enforce_algo is unchanged", + config: []byte(` + gdpr: + tcf2: + purpose1: + enforce_algo: "full" + purpose2: + enforce_algo: "full" + purpose3: + enforce_algo: "full" + purpose4: + enforce_algo: "full" + purpose5: + enforce_algo: "full" + purpose6: + enforce_algo: "full" + purpose7: + enforce_algo: "full" + purpose8: + enforce_algo: "full" + purpose9: + enforce_algo: "full" + purpose10: + enforce_algo: "full" + `), + wantEnforceAlgosSet: true, + wantPurpose1EnforceAlgo: TCF2EnforceAlgoFull, + wantPurpose2EnforceAlgo: TCF2EnforceAlgoFull, + wantPurpose3EnforceAlgo: TCF2EnforceAlgoFull, + wantPurpose4EnforceAlgo: TCF2EnforceAlgoFull, + wantPurpose5EnforceAlgo: TCF2EnforceAlgoFull, + wantPurpose6EnforceAlgo: TCF2EnforceAlgoFull, + wantPurpose7EnforceAlgo: TCF2EnforceAlgoFull, + wantPurpose8EnforceAlgo: TCF2EnforceAlgoFull, + wantPurpose9EnforceAlgo: TCF2EnforceAlgoFull, + wantPurpose10EnforceAlgo: TCF2EnforceAlgoFull, + wantEnforcePurposesSet: false, + }, + { + description: "enforce_algo and enforce_purpose are set; enforce_algo is unchanged", + config: []byte(` + gdpr: + tcf2: + purpose1: + enforce_algo: "full" + enforce_purpose: "no" + purpose2: + enforce_algo: "full" + enforce_purpose: "no" + purpose3: + enforce_algo: "full" + enforce_purpose: "no" + purpose4: + enforce_algo: "full" + enforce_purpose: "no" + purpose5: + enforce_algo: "full" + enforce_purpose: "no" + purpose6: + enforce_algo: "full" + enforce_purpose: "no" + purpose7: + enforce_algo: "full" + enforce_purpose: "no" + purpose8: + enforce_algo: "full" + enforce_purpose: "no" + purpose9: + enforce_algo: "full" + enforce_purpose: "no" + purpose10: + enforce_algo: "full" + enforce_purpose: "no" + `), + wantEnforceAlgosSet: true, + wantPurpose1EnforceAlgo: TCF2EnforceAlgoFull, + wantPurpose2EnforceAlgo: TCF2EnforceAlgoFull, + wantPurpose3EnforceAlgo: TCF2EnforceAlgoFull, + wantPurpose4EnforceAlgo: TCF2EnforceAlgoFull, + wantPurpose5EnforceAlgo: TCF2EnforceAlgoFull, + wantPurpose6EnforceAlgo: TCF2EnforceAlgoFull, + wantPurpose7EnforceAlgo: TCF2EnforceAlgoFull, + wantPurpose8EnforceAlgo: TCF2EnforceAlgoFull, + wantPurpose9EnforceAlgo: TCF2EnforceAlgoFull, + wantPurpose10EnforceAlgo: TCF2EnforceAlgoFull, + wantEnforcePurposesSet: true, + wantPurpose1EnforcePurpose: falseStr, + wantPurpose2EnforcePurpose: falseStr, + wantPurpose3EnforcePurpose: falseStr, + wantPurpose4EnforcePurpose: falseStr, + wantPurpose5EnforcePurpose: falseStr, + wantPurpose6EnforcePurpose: falseStr, + wantPurpose7EnforcePurpose: falseStr, + wantPurpose8EnforcePurpose: falseStr, + wantPurpose9EnforcePurpose: falseStr, + wantPurpose10EnforcePurpose: falseStr, + }, + } + + for _, tt := range tests { + v := viper.New() + v.SetConfigType("yaml") + v.ReadConfig(bytes.NewBuffer(tt.config)) + + migrateConfigTCF2EnforcePurposeFlags(v) + + if tt.wantEnforceAlgosSet { + assert.Equal(t, tt.wantPurpose1EnforceAlgo, v.GetString("gdpr.tcf2.purpose1.enforce_algo"), tt.description) + assert.Equal(t, tt.wantPurpose2EnforceAlgo, v.GetString("gdpr.tcf2.purpose2.enforce_algo"), tt.description) + assert.Equal(t, tt.wantPurpose3EnforceAlgo, v.GetString("gdpr.tcf2.purpose3.enforce_algo"), tt.description) + assert.Equal(t, tt.wantPurpose4EnforceAlgo, v.GetString("gdpr.tcf2.purpose4.enforce_algo"), tt.description) + assert.Equal(t, tt.wantPurpose5EnforceAlgo, v.GetString("gdpr.tcf2.purpose5.enforce_algo"), tt.description) + assert.Equal(t, tt.wantPurpose6EnforceAlgo, v.GetString("gdpr.tcf2.purpose6.enforce_algo"), tt.description) + assert.Equal(t, tt.wantPurpose7EnforceAlgo, v.GetString("gdpr.tcf2.purpose7.enforce_algo"), tt.description) + assert.Equal(t, tt.wantPurpose8EnforceAlgo, v.GetString("gdpr.tcf2.purpose8.enforce_algo"), tt.description) + assert.Equal(t, tt.wantPurpose9EnforceAlgo, v.GetString("gdpr.tcf2.purpose9.enforce_algo"), tt.description) + assert.Equal(t, tt.wantPurpose10EnforceAlgo, v.GetString("gdpr.tcf2.purpose10.enforce_algo"), tt.description) + } else { + assert.Nil(t, v.Get("gdpr.tcf2.purpose1.enforce_algo"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose2.enforce_algo"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose3.enforce_algo"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose4.enforce_algo"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose5.enforce_algo"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose6.enforce_algo"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose7.enforce_algo"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose8.enforce_algo"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose9.enforce_algo"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose10.enforce_algo"), tt.description) + } + + if tt.wantEnforcePurposesSet { + 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) + } 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) + } + } +} + +func TestMigrateConfigDatabaseConnection(t *testing.T) { + type configs struct { + old []byte + new []byte + both []byte + } + + // Stored Requests Config Migration + storedReqestsConfigs := configs{ + old: []byte(` + stored_requests: + postgres: + connection: + dbname: "old_connection_dbname" + host: "old_connection_host" + port: 1000 + user: "old_connection_user" + password: "old_connection_password" + fetcher: + query: "old_fetcher_query" + amp_query: "old_fetcher_amp_query" + initialize_caches: + timeout_ms: 1000 + query: "old_initialize_caches_query" + amp_query: "old_initialize_caches_amp_query" + poll_for_updates: + refresh_rate_seconds: 1000 + timeout_ms: 1000 + query: "old_poll_for_updates_query" + amp_query: "old_poll_for_updates_amp_query" + `), + new: []byte(` + stored_requests: + database: + connection: + dbname: "new_connection_dbname" + host: "new_connection_host" + port: 2000 + user: "new_connection_user" + password: "new_connection_password" + fetcher: + query: "new_fetcher_query" + amp_query: "new_fetcher_amp_query" + initialize_caches: + timeout_ms: 2000 + query: "new_initialize_caches_query" + amp_query: "new_initialize_caches_amp_query" + poll_for_updates: + refresh_rate_seconds: 2000 + timeout_ms: 2000 + query: "new_poll_for_updates_query" + amp_query: "new_poll_for_updates_amp_query" + `), + both: []byte(` + stored_requests: + postgres: + connection: + dbname: "old_connection_dbname" + host: "old_connection_host" + port: 1000 + user: "old_connection_user" + password: "old_connection_password" + fetcher: + query: "old_fetcher_query" + amp_query: "old_fetcher_amp_query" + initialize_caches: + timeout_ms: 1000 + query: "old_initialize_caches_query" + amp_query: "old_initialize_caches_amp_query" + poll_for_updates: + refresh_rate_seconds: 1000 + timeout_ms: 1000 + query: "old_poll_for_updates_query" + amp_query: "old_poll_for_updates_amp_query" + database: + connection: + dbname: "new_connection_dbname" + host: "new_connection_host" + port: 2000 + user: "new_connection_user" + password: "new_connection_password" + fetcher: + query: "new_fetcher_query" + amp_query: "new_fetcher_amp_query" + initialize_caches: + timeout_ms: 2000 + query: "new_initialize_caches_query" + amp_query: "new_initialize_caches_amp_query" + poll_for_updates: + refresh_rate_seconds: 2000 + timeout_ms: 2000 + query: "new_poll_for_updates_query" + amp_query: "new_poll_for_updates_amp_query" + `), + } + + storedRequestsTests := []struct { + description string + config []byte + + want_connection_dbname string + want_connection_host string + want_connection_port int + want_connection_user string + want_connection_password string + want_fetcher_query string + want_fetcher_amp_query string + want_initialize_caches_timeout_ms int + want_initialize_caches_query string + want_initialize_caches_amp_query string + want_poll_for_updates_refresh_rate_seconds int + want_poll_for_updates_timeout_ms int + want_poll_for_updates_query string + want_poll_for_updates_amp_query string + }{ + { + description: "New config and old config not set", + config: []byte{}, + }, + { + description: "New config not set, old config set", + config: storedReqestsConfigs.old, + + want_connection_dbname: "old_connection_dbname", + want_connection_host: "old_connection_host", + want_connection_port: 1000, + want_connection_user: "old_connection_user", + want_connection_password: "old_connection_password", + want_fetcher_query: "old_fetcher_query", + want_fetcher_amp_query: "old_fetcher_amp_query", + want_initialize_caches_timeout_ms: 1000, + want_initialize_caches_query: "old_initialize_caches_query", + want_initialize_caches_amp_query: "old_initialize_caches_amp_query", + want_poll_for_updates_refresh_rate_seconds: 1000, + want_poll_for_updates_timeout_ms: 1000, + want_poll_for_updates_query: "old_poll_for_updates_query", + want_poll_for_updates_amp_query: "old_poll_for_updates_amp_query", + }, + { + description: "New config set, old config not set", + config: storedReqestsConfigs.new, + + want_connection_dbname: "new_connection_dbname", + want_connection_host: "new_connection_host", + want_connection_port: 2000, + want_connection_user: "new_connection_user", + want_connection_password: "new_connection_password", + want_fetcher_query: "new_fetcher_query", + want_fetcher_amp_query: "new_fetcher_amp_query", + want_initialize_caches_timeout_ms: 2000, + want_initialize_caches_query: "new_initialize_caches_query", + want_initialize_caches_amp_query: "new_initialize_caches_amp_query", + want_poll_for_updates_refresh_rate_seconds: 2000, + want_poll_for_updates_timeout_ms: 2000, + want_poll_for_updates_query: "new_poll_for_updates_query", + want_poll_for_updates_amp_query: "new_poll_for_updates_amp_query", + }, + { + description: "New config and old config set", + config: storedReqestsConfigs.both, + + want_connection_dbname: "new_connection_dbname", + want_connection_host: "new_connection_host", + want_connection_port: 2000, + want_connection_user: "new_connection_user", + want_connection_password: "new_connection_password", + want_fetcher_query: "new_fetcher_query", + want_fetcher_amp_query: "new_fetcher_amp_query", + want_initialize_caches_timeout_ms: 2000, + want_initialize_caches_query: "new_initialize_caches_query", + want_initialize_caches_amp_query: "new_initialize_caches_amp_query", + want_poll_for_updates_refresh_rate_seconds: 2000, + want_poll_for_updates_timeout_ms: 2000, + want_poll_for_updates_query: "new_poll_for_updates_query", + want_poll_for_updates_amp_query: "new_poll_for_updates_amp_query", + }, + } + + for _, tt := range storedRequestsTests { + v := viper.New() + v.SetConfigType("yaml") + v.ReadConfig(bytes.NewBuffer(tt.config)) + + migrateConfigDatabaseConnection(v) + + if len(tt.config) > 0 { + assert.Equal(t, tt.want_connection_dbname, v.GetString("stored_requests.database.connection.dbname"), tt.description) + assert.Equal(t, tt.want_connection_host, v.GetString("stored_requests.database.connection.host"), tt.description) + assert.Equal(t, tt.want_connection_port, v.GetInt("stored_requests.database.connection.port"), tt.description) + assert.Equal(t, tt.want_connection_user, v.GetString("stored_requests.database.connection.user"), tt.description) + assert.Equal(t, tt.want_connection_password, v.GetString("stored_requests.database.connection.password"), tt.description) + assert.Equal(t, tt.want_fetcher_query, v.GetString("stored_requests.database.fetcher.query"), tt.description) + assert.Equal(t, tt.want_fetcher_amp_query, v.GetString("stored_requests.database.fetcher.amp_query"), tt.description) + assert.Equal(t, tt.want_initialize_caches_timeout_ms, v.GetInt("stored_requests.database.initialize_caches.timeout_ms"), tt.description) + assert.Equal(t, tt.want_initialize_caches_query, v.GetString("stored_requests.database.initialize_caches.query"), tt.description) + assert.Equal(t, tt.want_initialize_caches_amp_query, v.GetString("stored_requests.database.initialize_caches.amp_query"), tt.description) + assert.Equal(t, tt.want_poll_for_updates_refresh_rate_seconds, v.GetInt("stored_requests.database.poll_for_updates.refresh_rate_seconds"), tt.description) + assert.Equal(t, tt.want_poll_for_updates_timeout_ms, v.GetInt("stored_requests.database.poll_for_updates.timeout_ms"), tt.description) + assert.Equal(t, tt.want_poll_for_updates_query, v.GetString("stored_requests.database.poll_for_updates.query"), tt.description) + assert.Equal(t, tt.want_poll_for_updates_amp_query, v.GetString("stored_requests.database.poll_for_updates.amp_query"), tt.description) + } else { + assert.Nil(t, v.Get("stored_requests.database.connection.dbname"), tt.description) + assert.Nil(t, v.Get("stored_requests.database.connection.host"), tt.description) + assert.Nil(t, v.Get("stored_requests.database.connection.port"), tt.description) + assert.Nil(t, v.Get("stored_requests.database.connection.user"), tt.description) + assert.Nil(t, v.Get("stored_requests.database.connection.password"), tt.description) + assert.Nil(t, v.Get("stored_requests.database.fetcher.query"), tt.description) + assert.Nil(t, v.Get("stored_requests.database.fetcher.amp_query"), tt.description) + assert.Nil(t, v.Get("stored_requests.database.initialize_caches.timeout_ms"), tt.description) + assert.Nil(t, v.Get("stored_requests.database.initialize_caches.query"), tt.description) + assert.Nil(t, v.Get("stored_requests.database.initialize_caches.amp_query"), tt.description) + assert.Nil(t, v.Get("stored_requests.database.poll_for_updates.refresh_rate_seconds"), tt.description) + assert.Nil(t, v.Get("stored_requests.database.poll_for_updates.timeout_ms"), tt.description) + assert.Nil(t, v.Get("stored_requests.database.poll_for_updates.query"), tt.description) + assert.Nil(t, v.Get("stored_requests.database.poll_for_updates.amp_query"), tt.description) + } + } + + // Stored Video Reqs Config Migration + storedVideoReqsConfigs := configs{ + old: []byte(` + stored_video_req: + postgres: + connection: + dbname: "old_connection_dbname" + host: "old_connection_host" + port: 1000 + user: "old_connection_user" + password: "old_connection_password" + fetcher: + query: "old_fetcher_query" + initialize_caches: + timeout_ms: 1000 + query: "old_initialize_caches_query" + poll_for_updates: + refresh_rate_seconds: 1000 + timeout_ms: 1000 + query: "old_poll_for_updates_query" + `), + new: []byte(` + stored_video_req: + database: + connection: + dbname: "new_connection_dbname" + host: "new_connection_host" + port: 2000 + user: "new_connection_user" + password: "new_connection_password" + fetcher: + query: "new_fetcher_query" + initialize_caches: + timeout_ms: 2000 + query: "new_initialize_caches_query" + poll_for_updates: + refresh_rate_seconds: 2000 + timeout_ms: 2000 + query: "new_poll_for_updates_query" + `), + both: []byte(` + stored_video_req: + postgres: + connection: + dbname: "old_connection_dbname" + host: "old_connection_host" + port: 1000 + user: "old_connection_user" + password: "old_connection_password" + fetcher: + query: "old_fetcher_query" + initialize_caches: + timeout_ms: 1000 + query: "old_initialize_caches_query" + poll_for_updates: + refresh_rate_seconds: 1000 + timeout_ms: 1000 + query: "old_poll_for_updates_query" + database: + connection: + dbname: "new_connection_dbname" + host: "new_connection_host" + port: 2000 + user: "new_connection_user" + password: "new_connection_password" + fetcher: + query: "new_fetcher_query" + initialize_caches: + timeout_ms: 2000 + query: "new_initialize_caches_query" + poll_for_updates: + refresh_rate_seconds: 2000 + timeout_ms: 2000 + query: "new_poll_for_updates_query" + `), + } + + storedVideoReqsTests := []struct { + description string + config []byte + + want_connection_dbname string + want_connection_host string + want_connection_port int + want_connection_user string + want_connection_password string + want_fetcher_query string + want_initialize_caches_timeout_ms int + want_initialize_caches_query string + want_poll_for_updates_refresh_rate_seconds int + want_poll_for_updates_timeout_ms int + want_poll_for_updates_query string + }{ + { + description: "New config and old config not set", + config: []byte{}, + }, + { + description: "New config not set, old config set", + config: storedVideoReqsConfigs.old, + + want_connection_dbname: "old_connection_dbname", + want_connection_host: "old_connection_host", + want_connection_port: 1000, + want_connection_user: "old_connection_user", + want_connection_password: "old_connection_password", + want_fetcher_query: "old_fetcher_query", + want_initialize_caches_timeout_ms: 1000, + want_initialize_caches_query: "old_initialize_caches_query", + want_poll_for_updates_refresh_rate_seconds: 1000, + want_poll_for_updates_timeout_ms: 1000, + want_poll_for_updates_query: "old_poll_for_updates_query", + }, + { + description: "New config set, old config not set", + config: storedVideoReqsConfigs.new, + + want_connection_dbname: "new_connection_dbname", + want_connection_host: "new_connection_host", + want_connection_port: 2000, + want_connection_user: "new_connection_user", + want_connection_password: "new_connection_password", + want_fetcher_query: "new_fetcher_query", + want_initialize_caches_timeout_ms: 2000, + want_initialize_caches_query: "new_initialize_caches_query", + want_poll_for_updates_refresh_rate_seconds: 2000, + want_poll_for_updates_timeout_ms: 2000, + want_poll_for_updates_query: "new_poll_for_updates_query", + }, + { + description: "New config and old config set", + config: storedVideoReqsConfigs.both, + + want_connection_dbname: "new_connection_dbname", + want_connection_host: "new_connection_host", + want_connection_port: 2000, + want_connection_user: "new_connection_user", + want_connection_password: "new_connection_password", + want_fetcher_query: "new_fetcher_query", + want_initialize_caches_timeout_ms: 2000, + want_initialize_caches_query: "new_initialize_caches_query", + want_poll_for_updates_refresh_rate_seconds: 2000, + want_poll_for_updates_timeout_ms: 2000, + want_poll_for_updates_query: "new_poll_for_updates_query", + }, + } + + for _, tt := range storedVideoReqsTests { + v := viper.New() + v.SetConfigType("yaml") + v.ReadConfig(bytes.NewBuffer(tt.config)) + + migrateConfigDatabaseConnection(v) + + if len(tt.config) > 0 { + assert.Equal(t, tt.want_connection_dbname, v.Get("stored_video_req.database.connection.dbname").(string), tt.description) + assert.Equal(t, tt.want_connection_host, v.Get("stored_video_req.database.connection.host").(string), tt.description) + assert.Equal(t, tt.want_connection_port, v.Get("stored_video_req.database.connection.port").(int), tt.description) + assert.Equal(t, tt.want_connection_user, v.Get("stored_video_req.database.connection.user").(string), tt.description) + assert.Equal(t, tt.want_connection_password, v.Get("stored_video_req.database.connection.password").(string), tt.description) + assert.Equal(t, tt.want_fetcher_query, v.Get("stored_video_req.database.fetcher.query").(string), tt.description) + assert.Equal(t, tt.want_initialize_caches_timeout_ms, v.Get("stored_video_req.database.initialize_caches.timeout_ms").(int), tt.description) + assert.Equal(t, tt.want_initialize_caches_query, v.Get("stored_video_req.database.initialize_caches.query").(string), tt.description) + assert.Equal(t, tt.want_poll_for_updates_refresh_rate_seconds, v.Get("stored_video_req.database.poll_for_updates.refresh_rate_seconds").(int), tt.description) + assert.Equal(t, tt.want_poll_for_updates_timeout_ms, v.Get("stored_video_req.database.poll_for_updates.timeout_ms").(int), tt.description) + assert.Equal(t, tt.want_poll_for_updates_query, v.Get("stored_video_req.database.poll_for_updates.query").(string), tt.description) + } else { + assert.Nil(t, v.Get("stored_video_req.database.connection.dbname"), tt.description) + assert.Nil(t, v.Get("stored_video_req.database.connection.host"), tt.description) + assert.Nil(t, v.Get("stored_video_req.database.connection.port"), tt.description) + assert.Nil(t, v.Get("stored_video_req.database.connection.user"), tt.description) + assert.Nil(t, v.Get("stored_video_req.database.connection.password"), tt.description) + assert.Nil(t, v.Get("stored_video_req.database.fetcher.query"), tt.description) + assert.Nil(t, v.Get("stored_video_req.database.initialize_caches.timeout_ms"), tt.description) + assert.Nil(t, v.Get("stored_video_req.database.initialize_caches.query"), tt.description) + assert.Nil(t, v.Get("stored_video_req.database.poll_for_updates.refresh_rate_seconds"), tt.description) + assert.Nil(t, v.Get("stored_video_req.database.poll_for_updates.timeout_ms"), tt.description) + assert.Nil(t, v.Get("stored_video_req.database.poll_for_updates.query"), tt.description) + } + } + + // Stored Responses Config Migration + storedResponsesConfigs := configs{ + old: []byte(` + stored_responses: + postgres: + connection: + dbname: "old_connection_dbname" + host: "old_connection_host" + port: 1000 + user: "old_connection_user" + password: "old_connection_password" + fetcher: + query: "old_fetcher_query" + initialize_caches: + timeout_ms: 1000 + query: "old_initialize_caches_query" + poll_for_updates: + refresh_rate_seconds: 1000 + timeout_ms: 1000 + query: "old_poll_for_updates_query" + `), + new: []byte(` + stored_responses: + database: + connection: + dbname: "new_connection_dbname" + host: "new_connection_host" + port: 2000 + user: "new_connection_user" + password: "new_connection_password" + fetcher: + query: "new_fetcher_query" + initialize_caches: + timeout_ms: 2000 + query: "new_initialize_caches_query" + poll_for_updates: + refresh_rate_seconds: 2000 + timeout_ms: 2000 + query: "new_poll_for_updates_query" + `), + both: []byte(` + stored_responses: + postgres: + connection: + dbname: "old_connection_dbname" + host: "old_connection_host" + port: 1000 + user: "old_connection_user" + password: "old_connection_password" + fetcher: + query: "old_fetcher_query" + initialize_caches: + timeout_ms: 1000 + query: "old_initialize_caches_query" + poll_for_updates: + refresh_rate_seconds: 1000 + timeout_ms: 1000 + query: "old_poll_for_updates_query" + database: + connection: + dbname: "new_connection_dbname" + host: "new_connection_host" + port: 2000 + user: "new_connection_user" + password: "new_connection_password" + fetcher: + query: "new_fetcher_query" + initialize_caches: + timeout_ms: 2000 + query: "new_initialize_caches_query" + poll_for_updates: + refresh_rate_seconds: 2000 + timeout_ms: 2000 + query: "new_poll_for_updates_query" + `), + } + + storedResponsesTests := []struct { + description string + config []byte + + want_connection_dbname string + want_connection_host string + want_connection_port int + want_connection_user string + want_connection_password string + want_fetcher_query string + want_initialize_caches_timeout_ms int + want_initialize_caches_query string + want_poll_for_updates_refresh_rate_seconds int + want_poll_for_updates_timeout_ms int + want_poll_for_updates_query string + }{ + { + description: "New config and old config not set", + config: []byte{}, + }, + { + description: "New config not set, old config set", + config: storedResponsesConfigs.old, + + want_connection_dbname: "old_connection_dbname", + want_connection_host: "old_connection_host", + want_connection_port: 1000, + want_connection_user: "old_connection_user", + want_connection_password: "old_connection_password", + want_fetcher_query: "old_fetcher_query", + want_initialize_caches_timeout_ms: 1000, + want_initialize_caches_query: "old_initialize_caches_query", + want_poll_for_updates_refresh_rate_seconds: 1000, + want_poll_for_updates_timeout_ms: 1000, + want_poll_for_updates_query: "old_poll_for_updates_query", + }, + { + description: "New config set, old config not set", + config: storedResponsesConfigs.new, + + want_connection_dbname: "new_connection_dbname", + want_connection_host: "new_connection_host", + want_connection_port: 2000, + want_connection_user: "new_connection_user", + want_connection_password: "new_connection_password", + want_fetcher_query: "new_fetcher_query", + want_initialize_caches_timeout_ms: 2000, + want_initialize_caches_query: "new_initialize_caches_query", + want_poll_for_updates_refresh_rate_seconds: 2000, + want_poll_for_updates_timeout_ms: 2000, + want_poll_for_updates_query: "new_poll_for_updates_query", + }, + { + description: "New config and old config set", + config: storedResponsesConfigs.both, + + want_connection_dbname: "new_connection_dbname", + want_connection_host: "new_connection_host", + want_connection_port: 2000, + want_connection_user: "new_connection_user", + want_connection_password: "new_connection_password", + want_fetcher_query: "new_fetcher_query", + want_initialize_caches_timeout_ms: 2000, + want_initialize_caches_query: "new_initialize_caches_query", + want_poll_for_updates_refresh_rate_seconds: 2000, + want_poll_for_updates_timeout_ms: 2000, + want_poll_for_updates_query: "new_poll_for_updates_query", + }, + } + + for _, tt := range storedResponsesTests { + v := viper.New() + v.SetConfigType("yaml") + v.ReadConfig(bytes.NewBuffer(tt.config)) + + migrateConfigDatabaseConnection(v) + + if len(tt.config) > 0 { + assert.Equal(t, tt.want_connection_dbname, v.Get("stored_responses.database.connection.dbname").(string), tt.description) + assert.Equal(t, tt.want_connection_host, v.Get("stored_responses.database.connection.host").(string), tt.description) + assert.Equal(t, tt.want_connection_port, v.Get("stored_responses.database.connection.port").(int), tt.description) + assert.Equal(t, tt.want_connection_user, v.Get("stored_responses.database.connection.user").(string), tt.description) + assert.Equal(t, tt.want_connection_password, v.Get("stored_responses.database.connection.password").(string), tt.description) + assert.Equal(t, tt.want_fetcher_query, v.Get("stored_responses.database.fetcher.query").(string), tt.description) + assert.Equal(t, tt.want_initialize_caches_timeout_ms, v.Get("stored_responses.database.initialize_caches.timeout_ms").(int), tt.description) + assert.Equal(t, tt.want_initialize_caches_query, v.Get("stored_responses.database.initialize_caches.query").(string), tt.description) + assert.Equal(t, tt.want_poll_for_updates_refresh_rate_seconds, v.Get("stored_responses.database.poll_for_updates.refresh_rate_seconds").(int), tt.description) + assert.Equal(t, tt.want_poll_for_updates_timeout_ms, v.Get("stored_responses.database.poll_for_updates.timeout_ms").(int), tt.description) + assert.Equal(t, tt.want_poll_for_updates_query, v.Get("stored_responses.database.poll_for_updates.query").(string), tt.description) + } else { + assert.Nil(t, v.Get("stored_responses.database.connection.dbname"), tt.description) + assert.Nil(t, v.Get("stored_responses.database.connection.host"), tt.description) + assert.Nil(t, v.Get("stored_responses.database.connection.port"), tt.description) + assert.Nil(t, v.Get("stored_responses.database.connection.user"), tt.description) + assert.Nil(t, v.Get("stored_responses.database.connection.password"), tt.description) + assert.Nil(t, v.Get("stored_responses.database.fetcher.query"), tt.description) + assert.Nil(t, v.Get("stored_responses.database.initialize_caches.timeout_ms"), tt.description) + assert.Nil(t, v.Get("stored_responses.database.initialize_caches.query"), tt.description) + assert.Nil(t, v.Get("stored_responses.database.poll_for_updates.refresh_rate_seconds"), tt.description) + assert.Nil(t, v.Get("stored_responses.database.poll_for_updates.timeout_ms"), tt.description) + assert.Nil(t, v.Get("stored_responses.database.poll_for_updates.query"), tt.description) + } + } +} + +func TestMigrateConfigDatabaseConnectionUsingEnvVars(t *testing.T) { + tests := []struct { + description string + prefix string + setDatabaseEnvVars bool + setPostgresEnvVars bool + }{ + { + description: "stored requests old config set", + prefix: "stored_requests", + setPostgresEnvVars: true, + }, + { + description: "stored requests new config set", + prefix: "stored_requests", + setDatabaseEnvVars: true, + }, + { + description: "stored requests old and new config set", + prefix: "stored_requests", + setDatabaseEnvVars: true, + setPostgresEnvVars: true, + }, + { + description: "stored video requests old config set", + prefix: "stored_video_req", + setPostgresEnvVars: true, + }, + { + description: "stored video requests new config set", + prefix: "stored_video_req", + setDatabaseEnvVars: true, + }, + { + description: "stored video requests old and new config set", + prefix: "stored_video_req", + setDatabaseEnvVars: true, + setPostgresEnvVars: true, + }, + { + description: "stored responses old config set", + prefix: "stored_responses", + setPostgresEnvVars: true, + }, + { + description: "stored responses new config set", + prefix: "stored_responses", + setDatabaseEnvVars: true, + }, + { + description: "stored responses old and new config set", + prefix: "stored_responses", + setDatabaseEnvVars: true, + setPostgresEnvVars: true, + }, + } + + pgValues := map[string]string{ + "CONNECTION_DBNAME": "pg-dbname", + "CONNECTION_HOST": "pg-host", + "CONNECTION_PORT": "1", + "CONNECTION_USER": "pg-user", + "CONNECTION_PASSWORD": "pg-password", + "FETCHER_QUERY": "pg-fetcher-query", + "FETCHER_AMP_QUERY": "pg-fetcher-amp-query", + "INITIALIZE_CACHES_TIMEOUT_MS": "2", + "INITIALIZE_CACHES_QUERY": "pg-init-caches-query", + "INITIALIZE_CACHES_AMP_QUERY": "pg-init-caches-amp-query", + "POLL_FOR_UPDATES_REFRESH_RATE_SECONDS": "3", + "POLL_FOR_UPDATES_TIMEOUT_MS": "4", + "POLL_FOR_UPDATES_QUERY": "pg-poll-query $LAST_UPDATED", + "POLL_FOR_UPDATES_AMP_QUERY": "pg-poll-amp-query $LAST_UPDATED", + } + dbValues := map[string]string{ + "CONNECTION_DBNAME": "db-dbname", + "CONNECTION_HOST": "db-host", + "CONNECTION_PORT": "5", + "CONNECTION_USER": "db-user", + "CONNECTION_PASSWORD": "db-password", + "FETCHER_QUERY": "db-fetcher-query", + "FETCHER_AMP_QUERY": "db-fetcher-amp-query", + "INITIALIZE_CACHES_TIMEOUT_MS": "6", + "INITIALIZE_CACHES_QUERY": "db-init-caches-query", + "INITIALIZE_CACHES_AMP_QUERY": "db-init-caches-amp-query", + "POLL_FOR_UPDATES_REFRESH_RATE_SECONDS": "7", + "POLL_FOR_UPDATES_TIMEOUT_MS": "8", + "POLL_FOR_UPDATES_QUERY": "db-poll-query $LAST_UPDATED", + "POLL_FOR_UPDATES_AMP_QUERY": "db-poll-amp-query $LAST_UPDATED", + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + prefix := "PBS_" + strings.ToUpper(tt.prefix) + + // validation rules require in memory cache type to not be "none" + // given that we want to set the poll for update queries to non-empty values + envVarName := prefix + "_IN_MEMORY_CACHE_TYPE" + if oldval, ok := os.LookupEnv(envVarName); ok { + defer os.Setenv(envVarName, oldval) + } else { + defer os.Unsetenv(envVarName) + } + os.Setenv(envVarName, "unbounded") + + if tt.setPostgresEnvVars { + for suffix, v := range pgValues { + envVarName := prefix + "_POSTGRES_" + suffix + if oldval, ok := os.LookupEnv(envVarName); ok { + defer os.Setenv(envVarName, oldval) + } else { + defer os.Unsetenv(envVarName) + } + os.Setenv(envVarName, v) + } + } + if tt.setDatabaseEnvVars { + for suffix, v := range dbValues { + envVarName := prefix + "_DATABASE_" + suffix + if oldval, ok := os.LookupEnv(envVarName); ok { + defer os.Setenv(envVarName, oldval) + } else { + defer os.Unsetenv(envVarName) + } + os.Setenv(envVarName, v) + } + } + + c, _ := newDefaultConfig(t) + + expectedDatabaseValues := map[string]string{} + if tt.setDatabaseEnvVars { + expectedDatabaseValues = dbValues + } else if tt.setPostgresEnvVars { + expectedDatabaseValues = pgValues + } + + if tt.prefix == "stored_requests" { + assert.Equal(t, expectedDatabaseValues["CONNECTION_DBNAME"], c.StoredRequests.Database.ConnectionInfo.Database, tt.description) + assert.Equal(t, expectedDatabaseValues["CONNECTION_HOST"], c.StoredRequests.Database.ConnectionInfo.Host, tt.description) + assert.Equal(t, expectedDatabaseValues["CONNECTION_PORT"], strconv.Itoa(c.StoredRequests.Database.ConnectionInfo.Port), tt.description) + assert.Equal(t, expectedDatabaseValues["CONNECTION_USER"], c.StoredRequests.Database.ConnectionInfo.Username, tt.description) + assert.Equal(t, expectedDatabaseValues["CONNECTION_PASSWORD"], c.StoredRequests.Database.ConnectionInfo.Password, tt.description) + assert.Equal(t, expectedDatabaseValues["FETCHER_QUERY"], c.StoredRequests.Database.FetcherQueries.QueryTemplate, tt.description) + assert.Equal(t, expectedDatabaseValues["INITIALIZE_CACHES_TIMEOUT_MS"], strconv.Itoa(c.StoredRequests.Database.CacheInitialization.Timeout), tt.description) + assert.Equal(t, expectedDatabaseValues["INITIALIZE_CACHES_QUERY"], c.StoredRequests.Database.CacheInitialization.Query, tt.description) + assert.Equal(t, expectedDatabaseValues["POLL_FOR_UPDATES_REFRESH_RATE_SECONDS"], strconv.Itoa(c.StoredRequests.Database.PollUpdates.RefreshRate), tt.description) + assert.Equal(t, expectedDatabaseValues["POLL_FOR_UPDATES_TIMEOUT_MS"], strconv.Itoa(c.StoredRequests.Database.PollUpdates.Timeout), tt.description) + assert.Equal(t, expectedDatabaseValues["POLL_FOR_UPDATES_QUERY"], c.StoredRequests.Database.PollUpdates.Query, tt.description) + // AMP queries are only migrated for stored requests + assert.Equal(t, expectedDatabaseValues["FETCHER_AMP_QUERY"], c.StoredRequests.Database.FetcherQueries.AmpQueryTemplate, tt.description) + assert.Equal(t, expectedDatabaseValues["INITIALIZE_CACHES_AMP_QUERY"], c.StoredRequests.Database.CacheInitialization.AmpQuery, tt.description) + assert.Equal(t, expectedDatabaseValues["POLL_FOR_UPDATES_AMP_QUERY"], c.StoredRequests.Database.PollUpdates.AmpQuery, tt.description) + } else if tt.prefix == "stored_video_req" { + assert.Equal(t, expectedDatabaseValues["CONNECTION_DBNAME"], c.StoredVideo.Database.ConnectionInfo.Database, tt.description) + assert.Equal(t, expectedDatabaseValues["CONNECTION_HOST"], c.StoredVideo.Database.ConnectionInfo.Host, tt.description) + assert.Equal(t, expectedDatabaseValues["CONNECTION_PORT"], strconv.Itoa(c.StoredVideo.Database.ConnectionInfo.Port), tt.description) + assert.Equal(t, expectedDatabaseValues["CONNECTION_USER"], c.StoredVideo.Database.ConnectionInfo.Username, tt.description) + assert.Equal(t, expectedDatabaseValues["CONNECTION_PASSWORD"], c.StoredVideo.Database.ConnectionInfo.Password, tt.description) + assert.Equal(t, expectedDatabaseValues["FETCHER_QUERY"], c.StoredVideo.Database.FetcherQueries.QueryTemplate, tt.description) + assert.Equal(t, expectedDatabaseValues["INITIALIZE_CACHES_TIMEOUT_MS"], strconv.Itoa(c.StoredVideo.Database.CacheInitialization.Timeout), tt.description) + assert.Equal(t, expectedDatabaseValues["INITIALIZE_CACHES_QUERY"], c.StoredVideo.Database.CacheInitialization.Query, tt.description) + assert.Equal(t, expectedDatabaseValues["POLL_FOR_UPDATES_REFRESH_RATE_SECONDS"], strconv.Itoa(c.StoredVideo.Database.PollUpdates.RefreshRate), tt.description) + assert.Equal(t, expectedDatabaseValues["POLL_FOR_UPDATES_TIMEOUT_MS"], strconv.Itoa(c.StoredVideo.Database.PollUpdates.Timeout), tt.description) + assert.Equal(t, expectedDatabaseValues["POLL_FOR_UPDATES_QUERY"], c.StoredVideo.Database.PollUpdates.Query, tt.description) + assert.Empty(t, c.StoredVideo.Database.FetcherQueries.AmpQueryTemplate, tt.description) + assert.Empty(t, c.StoredVideo.Database.CacheInitialization.AmpQuery, tt.description) + assert.Empty(t, c.StoredVideo.Database.PollUpdates.AmpQuery, tt.description) + } else { + assert.Equal(t, expectedDatabaseValues["CONNECTION_DBNAME"], c.StoredResponses.Database.ConnectionInfo.Database, tt.description) + assert.Equal(t, expectedDatabaseValues["CONNECTION_HOST"], c.StoredResponses.Database.ConnectionInfo.Host, tt.description) + assert.Equal(t, expectedDatabaseValues["CONNECTION_PORT"], strconv.Itoa(c.StoredResponses.Database.ConnectionInfo.Port), tt.description) + assert.Equal(t, expectedDatabaseValues["CONNECTION_USER"], c.StoredResponses.Database.ConnectionInfo.Username, tt.description) + assert.Equal(t, expectedDatabaseValues["CONNECTION_PASSWORD"], c.StoredResponses.Database.ConnectionInfo.Password, tt.description) + assert.Equal(t, expectedDatabaseValues["FETCHER_QUERY"], c.StoredResponses.Database.FetcherQueries.QueryTemplate, tt.description) + assert.Equal(t, expectedDatabaseValues["INITIALIZE_CACHES_TIMEOUT_MS"], strconv.Itoa(c.StoredResponses.Database.CacheInitialization.Timeout), tt.description) + assert.Equal(t, expectedDatabaseValues["INITIALIZE_CACHES_QUERY"], c.StoredResponses.Database.CacheInitialization.Query, tt.description) + assert.Equal(t, expectedDatabaseValues["POLL_FOR_UPDATES_REFRESH_RATE_SECONDS"], strconv.Itoa(c.StoredResponses.Database.PollUpdates.RefreshRate), tt.description) + assert.Equal(t, expectedDatabaseValues["POLL_FOR_UPDATES_TIMEOUT_MS"], strconv.Itoa(c.StoredResponses.Database.PollUpdates.Timeout), tt.description) + assert.Equal(t, expectedDatabaseValues["POLL_FOR_UPDATES_QUERY"], c.StoredResponses.Database.PollUpdates.Query, tt.description) + assert.Empty(t, c.StoredResponses.Database.FetcherQueries.AmpQueryTemplate, tt.description) + assert.Empty(t, c.StoredResponses.Database.CacheInitialization.AmpQuery, tt.description) + assert.Empty(t, c.StoredResponses.Database.PollUpdates.AmpQuery, tt.description) + } + }) + } +} + +func TestMigrateConfigDatabaseQueryParams(t *testing.T) { + + config := []byte(` + stored_requests: + postgres: + fetcher: + query: + SELECT * FROM Table1 WHERE id in (%REQUEST_ID_LIST%) + UNION ALL + SELECT * FROM Table2 WHERE id in (%IMP_ID_LIST%) + UNION ALL + SELECT * FROM Table3 WHERE id in (%ID_LIST%) + amp_query: + SELECT * FROM Table1 WHERE id in (%REQUEST_ID_LIST%) + UNION ALL + SELECT * FROM Table2 WHERE id in (%IMP_ID_LIST%) + UNION ALL + SELECT * FROM Table3 WHERE id in (%ID_LIST%) + poll_for_updates: + query: "SELECT * FROM Table1 WHERE last_updated > $1 UNION ALL SELECT * FROM Table2 WHERE last_updated > $1" + amp_query: "SELECT * FROM Table1 WHERE last_updated > $1 UNION ALL SELECT * FROM Table2 WHERE last_updated > $1" + stored_video_req: + postgres: + fetcher: + query: + SELECT * FROM Table1 WHERE id in (%REQUEST_ID_LIST%) + UNION ALL + SELECT * FROM Table2 WHERE id in (%IMP_ID_LIST%) + UNION ALL + SELECT * FROM Table3 WHERE id in (%ID_LIST%) + amp_query: + SELECT * FROM Table1 WHERE id in (%REQUEST_ID_LIST%) + UNION ALL + SELECT * FROM Table2 WHERE id in (%IMP_ID_LIST%) + UNION ALL + SELECT * FROM Table3 WHERE id in (%ID_LIST%) + poll_for_updates: + query: "SELECT * FROM Table1 WHERE last_updated > $1 UNION ALL SELECT * FROM Table2 WHERE last_updated > $1" + amp_query: "SELECT * FROM Table1 WHERE last_updated > $1 UNION ALL SELECT * FROM Table2 WHERE last_updated > $1" + stored_responses: + postgres: + fetcher: + query: + SELECT * FROM Table1 WHERE id in (%REQUEST_ID_LIST%) + UNION ALL + SELECT * FROM Table2 WHERE id in (%IMP_ID_LIST%) + UNION ALL + SELECT * FROM Table3 WHERE id in (%ID_LIST%) + amp_query: + SELECT * FROM Table1 WHERE id in (%REQUEST_ID_LIST%) + UNION ALL + SELECT * FROM Table2 WHERE id in (%IMP_ID_LIST%) + UNION ALL + SELECT * FROM Table3 WHERE id in (%ID_LIST%) + poll_for_updates: + query: "SELECT * FROM Table1 WHERE last_updated > $1 UNION ALL SELECT * FROM Table2 WHERE last_updated > $1" + amp_query: "SELECT * FROM Table1 WHERE last_updated > $1 UNION ALL SELECT * FROM Table2 WHERE last_updated > $1" + `) + + want_queries := struct { + fetcher_query string + fetcher_amp_query string + poll_for_updates_query string + poll_for_updates_amp_query string + }{ + fetcher_query: "SELECT * FROM Table1 WHERE id in ($REQUEST_ID_LIST) " + + "UNION ALL " + + "SELECT * FROM Table2 WHERE id in ($IMP_ID_LIST) " + + "UNION ALL " + + "SELECT * FROM Table3 WHERE id in ($ID_LIST)", + fetcher_amp_query: "SELECT * FROM Table1 WHERE id in ($REQUEST_ID_LIST) " + + "UNION ALL " + + "SELECT * FROM Table2 WHERE id in ($IMP_ID_LIST) " + + "UNION ALL " + + "SELECT * FROM Table3 WHERE id in ($ID_LIST)", + poll_for_updates_query: "SELECT * FROM Table1 WHERE last_updated > $LAST_UPDATED UNION ALL SELECT * FROM Table2 WHERE last_updated > $LAST_UPDATED", + poll_for_updates_amp_query: "SELECT * FROM Table1 WHERE last_updated > $LAST_UPDATED UNION ALL SELECT * FROM Table2 WHERE last_updated > $LAST_UPDATED", + } + + v := viper.New() + v.SetConfigType("yaml") + v.ReadConfig(bytes.NewBuffer(config)) + + migrateConfigDatabaseConnection(v) + + // stored_requests queries + assert.Equal(t, want_queries.fetcher_query, v.GetString("stored_requests.database.fetcher.query")) + assert.Equal(t, want_queries.fetcher_amp_query, v.GetString("stored_requests.database.fetcher.amp_query")) + assert.Equal(t, want_queries.poll_for_updates_query, v.GetString("stored_requests.database.poll_for_updates.query")) + assert.Equal(t, want_queries.poll_for_updates_amp_query, v.GetString("stored_requests.database.poll_for_updates.amp_query")) + + // stored_video_req queries + assert.Equal(t, want_queries.fetcher_query, v.GetString("stored_video_req.database.fetcher.query")) + assert.Equal(t, want_queries.fetcher_amp_query, v.GetString("stored_video_req.database.fetcher.amp_query")) + assert.Equal(t, want_queries.poll_for_updates_query, v.GetString("stored_video_req.database.poll_for_updates.query")) + assert.Equal(t, want_queries.poll_for_updates_amp_query, v.GetString("stored_video_req.database.poll_for_updates.amp_query")) + + // stored_responses queries + assert.Equal(t, want_queries.fetcher_query, v.GetString("stored_responses.database.fetcher.query")) + assert.Equal(t, want_queries.fetcher_amp_query, v.GetString("stored_responses.database.fetcher.amp_query")) + assert.Equal(t, want_queries.poll_for_updates_query, v.GetString("stored_responses.database.poll_for_updates.query")) + assert.Equal(t, want_queries.poll_for_updates_amp_query, v.GetString("stored_responses.database.poll_for_updates.amp_query")) +} + +func TestIsConfigInfoPresent(t *testing.T) { + configPrefix1Field2Only := []byte(` + prefix1: + field2: "value2" + `) + configPrefix1Field4Only := []byte(` + prefix1: + field4: "value4" + `) + configPrefix1Field2AndField3 := []byte(` + prefix1: + field2: "value2" + field3: "value3" + `) + + tests := []struct { + description string + config []byte + keyPrefix string + fields []string + wantResult bool + }{ + { + description: "config is nil", + config: nil, + keyPrefix: "prefix1", + fields: []string{"field1", "field2", "field3"}, + wantResult: false, + }, + { + description: "config is empty", + config: []byte{}, + keyPrefix: "prefix1", + fields: []string{"field1", "field2", "field3"}, + wantResult: false, + }, + { + description: "present - one field exists in config", + config: configPrefix1Field2Only, + keyPrefix: "prefix1", + fields: []string{"field1", "field2", "field3"}, + wantResult: true, + }, + { + description: "present - many fields exist in config", + config: configPrefix1Field2AndField3, + keyPrefix: "prefix1", + fields: []string{"field1", "field2", "field3"}, + wantResult: true, + }, + { + description: "not present - field not found", + config: configPrefix1Field4Only, + keyPrefix: "prefix1", + fields: []string{"field1", "field2", "field3"}, + wantResult: false, + }, + { + description: "not present - field exists but with a different prefix", + config: configPrefix1Field2Only, + keyPrefix: "prefix2", + fields: []string{"field1", "field2", "field3"}, + wantResult: false, + }, + { + description: "not present - fields is nil", + config: configPrefix1Field2Only, + keyPrefix: "prefix1", + fields: nil, + wantResult: false, + }, + { + description: "not present - fields is empty", + config: configPrefix1Field2Only, + keyPrefix: "prefix1", + fields: []string{}, + wantResult: false, + }, + } + + for _, tt := range tests { + v := viper.New() + v.SetConfigType("yaml") + v.ReadConfig(bytes.NewBuffer(tt.config)) + + result := isConfigInfoPresent(v, tt.keyPrefix, tt.fields) + assert.Equal(t, tt.wantResult, result, tt.description) + } +} + +func TestNegativeRequestSize(t *testing.T) { + cfg, v := newDefaultConfig(t) + cfg.MaxRequestSize = -1 + assertOneError(t, cfg.validate(v), "cfg.max_request_size must be >= 0. Got -1") +} + +func TestNegativePrometheusTimeout(t *testing.T) { + cfg, v := newDefaultConfig(t) + cfg.Metrics.Prometheus.Port = 8001 + cfg.Metrics.Prometheus.TimeoutMillisRaw = 0 + 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) { + tests := []struct { + description string + vendorID int + wantErrorMsg string + }{ + { + description: "Negative GDPR.HostVendorID", + vendorID: -1, + wantErrorMsg: "gdpr.host_vendor_id must be in the range [0, 65535]. Got -1", + }, + { + description: "Overflowed GDPR.HostVendorID", + vendorID: (0xffff) + 1, + wantErrorMsg: "gdpr.host_vendor_id must be in the range [0, 65535]. Got 65536", + }, + } + + for _, tt := range tests { + cfg, v := newDefaultConfig(t) + cfg.GDPR.HostVendorID = tt.vendorID + errs := cfg.validate(v) + + assert.Equal(t, 1, len(errs), tt.description) + assert.EqualError(t, errs[0], tt.wantErrorMsg, tt.description) + } +} + +func TestInvalidAMPException(t *testing.T) { + cfg, v := newDefaultConfig(t) + cfg.GDPR.AMPException = true + assertOneError(t, cfg.validate(v), "gdpr.amp_exception has been discontinued and must be removed from your config. If you need to disable GDPR for AMP, you may do so per-account (gdpr.integration_enabled.amp) or at the host level for the default account (account_defaults.gdpr.integration_enabled.amp)") +} + +func TestInvalidGDPRDefaultValue(t *testing.T) { + cfg, v := newDefaultConfig(t) + cfg.GDPR.DefaultValue = "2" + assertOneError(t, cfg.validate(v), "gdpr.default_value must be 0 or 1") +} + +func TestMissingGDPRDefaultValue(t *testing.T) { + v := viper.New() + + cfg, _ := newDefaultConfig(t) + assertOneError(t, cfg.validate(v), "gdpr.default_value is required and must be specified") +} + +func TestInvalidEnforceAlgo(t *testing.T) { + cfg, v := newDefaultConfig(t) + cfg.GDPR.TCF2.Purpose1.EnforceAlgo = "" + cfg.GDPR.TCF2.Purpose2.EnforceAlgo = TCF2EnforceAlgoFull + cfg.GDPR.TCF2.Purpose3.EnforceAlgo = TCF2EnforceAlgoBasic + cfg.GDPR.TCF2.Purpose4.EnforceAlgo = TCF2EnforceAlgoFull + cfg.GDPR.TCF2.Purpose5.EnforceAlgo = "invalid1" + cfg.GDPR.TCF2.Purpose6.EnforceAlgo = "invalid2" + cfg.GDPR.TCF2.Purpose7.EnforceAlgo = TCF2EnforceAlgoFull + cfg.GDPR.TCF2.Purpose8.EnforceAlgo = TCF2EnforceAlgoBasic + cfg.GDPR.TCF2.Purpose9.EnforceAlgo = TCF2EnforceAlgoFull + cfg.GDPR.TCF2.Purpose10.EnforceAlgo = "invalid3" + + errs := cfg.validate(v) + + expectedErrs := []error{ + errors.New("gdpr.tcf2.purpose1.enforce_algo must be \"basic\" or \"full\". Got "), + errors.New("gdpr.tcf2.purpose5.enforce_algo must be \"basic\" or \"full\". Got invalid1"), + errors.New("gdpr.tcf2.purpose6.enforce_algo must be \"basic\" or \"full\". Got invalid2"), + errors.New("gdpr.tcf2.purpose10.enforce_algo must be \"basic\" or \"full\". Got invalid3"), + } + assert.ElementsMatch(t, errs, expectedErrs, "gdpr.tcf2.purposeX.enforce_algo should prevent invalid values but it doesn't") +} + +func TestNegativeCurrencyConverterFetchInterval(t *testing.T) { + v := viper.New() + v.Set("gdpr.default_value", "0") + + cfg := Configuration{ + CurrencyConverter: CurrencyConverter{ + FetchIntervalSeconds: -1, + }, + } + 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(v) + assert.NotNil(t, err, "cfg.currency_converter.fetch_interval_seconds prevent values over %d, but it doesn't", 0xffff) +} + +func TestLimitTimeout(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 TestCookieSizeError(t *testing.T) { + testCases := []struct { + description string + cookieSize int + expectError bool + }{ + {"MIN_COOKIE_SIZE_BYTES + 1", MIN_COOKIE_SIZE_BYTES + 1, false}, + {"MIN_COOKIE_SIZE_BYTES", MIN_COOKIE_SIZE_BYTES, false}, + {"MIN_COOKIE_SIZE_BYTES - 1", MIN_COOKIE_SIZE_BYTES - 1, true}, + {"Zero", 0, false}, {"Negative", -100, true}, } @@ -1294,14 +2741,14 @@ func TestNewCallsRequestValidation(t *testing.T) { for _, test := range testCases { v := viper.New() - SetupViper(v, "") + SetupViper(v, "", bidderInfos) v.Set("gdpr.default_value", "0") v.SetConfigType("yaml") v.ReadConfig(bytes.NewBuffer([]byte( `request_validation: ipv4_private_networks: ` + test.privateIPNetworks))) - result, resultErr := New(v) + result, resultErr := New(v, bidderInfos, mockNormalizeBidderName) if test.expectedError == "" { assert.NoError(t, resultErr, test.description+":err") @@ -1324,66 +2771,19 @@ func TestValidateAccountsConfigRestrictions(t *testing.T) { cfg, v := newDefaultConfig(t) cfg.Accounts.Files.Enabled = true cfg.Accounts.HTTP.Endpoint = "http://localhost" - cfg.Accounts.Postgres.ConnectionInfo.Database = "accounts" + cfg.Accounts.Database.ConnectionInfo.Database = "accounts" 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 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) + assert.Contains(t, errs, errors.New("accounts.database: retrieving accounts via database not available, use accounts.files")) } func newDefaultConfig(t *testing.T) (*Configuration, *viper.Viper) { v := viper.New() - SetupViper(v, "") + SetupViper(v, "", bidderInfos) v.Set("gdpr.default_value", "0") v.SetConfigType("yaml") - cfg, err := New(v) + cfg, err := New(v, bidderInfos, mockNormalizeBidderName) assert.NoError(t, err, "Setting up config should work but it doesn't") return cfg, v } @@ -1450,10 +2850,10 @@ func TestSpecialFeature1VendorExceptionMap(t *testing.T) { config := append(baseConfig, tt.configVendorExceptions...) v := viper.New() - SetupViper(v, "") + SetupViper(v, "", bidderInfos) v.SetConfigType("yaml") v.ReadConfig(bytes.NewBuffer(config)) - cfg, err := New(v) + cfg, err := New(v, bidderInfos, mockNormalizeBidderName) assert.NoError(t, err, "Setting up config error", tt.description) assert.Equal(t, tt.wantVendorExceptions, cfg.GDPR.TCF2.SpecialFeature1.VendorExceptions, tt.description) @@ -1465,8 +2865,8 @@ func TestTCF2PurposeEnforced(t *testing.T) { tests := []struct { description string givePurposeConfigNil bool - givePurpose1Enforced string - givePurpose2Enforced string + givePurpose1Enforced bool + givePurpose2Enforced bool givePurpose consentconstants.Purpose wantEnforced bool }{ @@ -1477,26 +2877,20 @@ func TestTCF2PurposeEnforced(t *testing.T) { wantEnforced: false, }, { - description: "Purpose 1 Enforced not set", - givePurpose1Enforced: "", - givePurpose: 1, - wantEnforced: false, - }, - { - description: "Purpose 1 Enforced set to full enforcement", - givePurpose1Enforced: TCF2FullEnforcement, + description: "Purpose 1 Enforced set to true", + givePurpose1Enforced: true, givePurpose: 1, wantEnforced: true, }, { - description: "Purpose 1 Enforced set to no enforcement", - givePurpose1Enforced: TCF2NoEnforcement, + description: "Purpose 1 Enforced set to false", + givePurpose1Enforced: false, givePurpose: 1, wantEnforced: false, }, { - description: "Purpose 2 Enforced set to full enforcement", - givePurpose2Enforced: TCF2FullEnforcement, + description: "Purpose 2 Enforced set to true", + givePurpose2Enforced: true, givePurpose: 2, wantEnforced: true, }, @@ -1522,6 +2916,61 @@ func TestTCF2PurposeEnforced(t *testing.T) { } } +func TestTCF2PurposeEnforcementAlgo(t *testing.T) { + tests := []struct { + description string + givePurposeConfigNil bool + givePurpose1Algo TCF2EnforcementAlgo + givePurpose2Algo TCF2EnforcementAlgo + givePurpose consentconstants.Purpose + wantAlgo TCF2EnforcementAlgo + }{ + { + description: "Purpose config is nil", + givePurposeConfigNil: true, + givePurpose: 1, + wantAlgo: TCF2FullEnforcement, + }, + { + description: "Purpose 1 enforcement algo set to basic", + givePurpose1Algo: TCF2BasicEnforcement, + givePurpose: 1, + wantAlgo: TCF2BasicEnforcement, + }, + { + description: "Purpose 1 enforcement algo set to full", + givePurpose1Algo: TCF2FullEnforcement, + givePurpose: 1, + wantAlgo: TCF2FullEnforcement, + }, + { + description: "Purpose 2 Enforcement algo set to basic", + givePurpose2Algo: TCF2BasicEnforcement, + givePurpose: 2, + wantAlgo: TCF2BasicEnforcement, + }, + } + + for _, tt := range tests { + tcf2 := TCF2{} + + if !tt.givePurposeConfigNil { + tcf2.PurposeConfigs = map[consentconstants.Purpose]*TCF2Purpose{ + 1: { + EnforceAlgoID: tt.givePurpose1Algo, + }, + 2: { + EnforceAlgoID: tt.givePurpose2Algo, + }, + } + } + + value := tcf2.PurposeEnforcementAlgo(tt.givePurpose) + + assert.Equal(t, tt.wantAlgo, value, tt.description) + } +} + func TestTCF2PurposeEnforcingVendors(t *testing.T) { tests := []struct { description string @@ -1577,65 +3026,44 @@ func TestTCF2PurposeEnforcingVendors(t *testing.T) { } } -func TestTCF2PurposeVendorException(t *testing.T) { +func TestTCF2PurposeVendorExceptions(t *testing.T) { tests := []struct { description string givePurposeConfigNil bool givePurpose1ExceptionMap map[openrtb_ext.BidderName]struct{} givePurpose2ExceptionMap map[openrtb_ext.BidderName]struct{} givePurpose consentconstants.Purpose - giveBidder openrtb_ext.BidderName - wantIsVendorException bool + wantExceptionMap map[openrtb_ext.BidderName]struct{} }{ { - description: "Purpose config is nil", - givePurposeConfigNil: true, - givePurpose: 1, - giveBidder: "appnexus", - wantIsVendorException: false, + description: "Purpose config is nil", + givePurposeConfigNil: true, + givePurpose: 1, + wantExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, { - description: "Nil - exception map not defined for purpose", - givePurpose: 1, - giveBidder: "appnexus", - wantIsVendorException: false, + description: "Nil - exception map not defined for purpose", + givePurpose: 1, + wantExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, { description: "Empty - exception map empty for purpose", givePurpose: 1, givePurpose1ExceptionMap: map[openrtb_ext.BidderName]struct{}{}, - giveBidder: "appnexus", - wantIsVendorException: false, - }, - { - description: "One - bidder found in purpose exception map containing one entry", - givePurpose: 1, - givePurpose1ExceptionMap: map[openrtb_ext.BidderName]struct{}{"appnexus": {}}, - giveBidder: "appnexus", - wantIsVendorException: true, + wantExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, { - description: "Many - bidder found in purpose exception map containing multiple entries", + description: "Nonempty - exception map with multiple entries for purpose", givePurpose: 1, givePurpose1ExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "index": {}}, - giveBidder: "appnexus", - wantIsVendorException: true, + wantExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "index": {}}, }, { - description: "Many - bidder not found in purpose exception map containing multiple entries", - givePurpose: 1, - givePurpose1ExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "index": {}}, - givePurpose2ExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "openx": {}}, - giveBidder: "openx", - wantIsVendorException: false, - }, - { - description: "Many - bidder found in different purpose exception map containing multiple entries", + description: "Nonempty - exception map with multiple entries for different purpose", givePurpose: 2, givePurpose1ExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "index": {}}, givePurpose2ExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "openx": {}}, - giveBidder: "openx", - wantIsVendorException: true, + wantExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "openx": {}}, }, } @@ -1653,9 +3081,9 @@ func TestTCF2PurposeVendorException(t *testing.T) { } } - value := tcf2.PurposeVendorException(tt.givePurpose, tt.giveBidder) + value := tcf2.PurposeVendorExceptions(tt.givePurpose) - assert.Equal(t, tt.wantIsVendorException, value, tt.description) + assert.Equal(t, tt.wantExceptionMap, value, tt.description) } } diff --git a/config/experiment.go b/config/experiment.go new file mode 100644 index 00000000000..e57825962ec --- /dev/null +++ b/config/experiment.go @@ -0,0 +1,91 @@ +package config + +import ( + "errors" + "fmt" + "net/url" +) + +var ( + ErrSignerModeIncorrect = errors.New("signer mode is not specified, specify 'off', 'inprocess' or 'remote'") + ErrInProcessSignerInvalidPrivateKey = errors.New("private key for inprocess signer cannot be empty") + + ErrMsgInProcessSignerInvalidURL = "invalid url for inprocess signer" + ErrMsgInProcessSignerInvalidDNSRenewalInterval = "invalid dns renewal interval for inprocess signer" + ErrMsgInProcessSignerInvalidDNSCheckInterval = "invalid dns check interval for inprocess signer" + ErrMsgInvalidRemoteSignerURL = "invalid url for remote signer" + ErrMsgInvalidRemoteSignerSigningTimeout = "invalid signing timeout for remote signer" +) + +const ( + AdCertsSignerModeOff = "off" + AdCertsSignerModeInprocess = "inprocess" + AdCertsSignerModeRemote = "remote" +) + +// Experiment defines if experimental features are available +type Experiment struct { + AdCerts ExperimentAdsCert `mapstructure:"adscert"` +} + +// ExperimentAdsCert configures and enables functionality to generate and send Ads Cert Auth header to bidders +type ExperimentAdsCert struct { + Mode string `mapstructure:"mode"` + InProcess AdsCertInProcess `mapstructure:"inprocess"` + Remote AdsCertRemote `mapstructure:"remote"` +} + +// AdsCertInProcess configures data to sign requests using ads certs library in core PBS logic +type AdsCertInProcess struct { + // Origin is ads.cert hostname for the originating party + Origin string `mapstructure:"origin"` + // PrivateKey is a base-64 encoded private key. + PrivateKey string `mapstructure:"key"` + // DNSCheckIntervalInSeconds specifies frequency to check origin _delivery._adscert and _adscert subdomains, used for indexing data, default: 30 + DNSCheckIntervalInSeconds int `mapstructure:"domain_check_interval_seconds"` + // DNSRenewalIntervalInSeconds specifies frequency to renew origin _delivery._adscert and _adscert subdomains, used for indexing data, default: 30 + DNSRenewalIntervalInSeconds int `mapstructure:"domain_renewal_interval_seconds"` +} + +// AdsCertRemote configures data to sign requests using remote signatory service +type AdsCertRemote struct { + // Url is the address of gRPC server that will create a call signature + Url string `mapstructure:"url"` + // SigningTimeoutMs specifies how long this client will wait for signing to finish before abandoning + SigningTimeoutMs int `mapstructure:"signing_timeout_ms"` +} + +func (cfg *Experiment) validate(errs []error) []error { + if len(cfg.AdCerts.Mode) == 0 { + return errs + } + if !(cfg.AdCerts.Mode == AdCertsSignerModeOff || + cfg.AdCerts.Mode == AdCertsSignerModeInprocess || + cfg.AdCerts.Mode == AdCertsSignerModeRemote) { + return append(errs, ErrSignerModeIncorrect) + } + if cfg.AdCerts.Mode == AdCertsSignerModeInprocess { + _, err := url.ParseRequestURI(cfg.AdCerts.InProcess.Origin) + if err != nil { + errs = append(errs, fmt.Errorf("%s: %s", ErrMsgInProcessSignerInvalidURL, cfg.AdCerts.InProcess.Origin)) + } + if len(cfg.AdCerts.InProcess.PrivateKey) == 0 { + errs = append(errs, ErrInProcessSignerInvalidPrivateKey) + } + if cfg.AdCerts.InProcess.DNSRenewalIntervalInSeconds <= 0 { + errs = append(errs, fmt.Errorf("%s: %d", ErrMsgInProcessSignerInvalidDNSRenewalInterval, cfg.AdCerts.InProcess.DNSRenewalIntervalInSeconds)) + } + if cfg.AdCerts.InProcess.DNSCheckIntervalInSeconds <= 0 { + errs = append(errs, fmt.Errorf("%s: %d", ErrMsgInProcessSignerInvalidDNSCheckInterval, cfg.AdCerts.InProcess.DNSCheckIntervalInSeconds)) + } + } else if cfg.AdCerts.Mode == AdCertsSignerModeRemote { + _, err := url.ParseRequestURI(cfg.AdCerts.Remote.Url) + if err != nil { + errs = append(errs, fmt.Errorf("%s: %s", ErrMsgInvalidRemoteSignerURL, cfg.AdCerts.Remote.Url)) + } + if cfg.AdCerts.Remote.SigningTimeoutMs <= 0 { + errs = append(errs, fmt.Errorf("%s: %d", ErrMsgInvalidRemoteSignerSigningTimeout, cfg.AdCerts.Remote.SigningTimeoutMs)) + } + } + return errs +} diff --git a/config/experiment_test.go b/config/experiment_test.go new file mode 100644 index 00000000000..e61e199a79f --- /dev/null +++ b/config/experiment_test.go @@ -0,0 +1,134 @@ +package config + +import ( + "errors" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestExperimentValidate(t *testing.T) { + testCases := []struct { + desc string + data Experiment + expectErrors bool + expectedErrors []error + }{ + { + desc: "Remote signer config: invalid remote url passed to config", + data: Experiment{ + AdCerts: ExperimentAdsCert{Mode: AdCertsSignerModeRemote, Remote: AdsCertRemote{Url: "test@com", SigningTimeoutMs: 5}}, + }, + expectErrors: true, + expectedErrors: []error{errors.New("invalid url for remote signer: test@com")}, + }, + { + desc: "Remote signer config: invalid SigningTimeoutMs passed to config", + data: Experiment{ + AdCerts: ExperimentAdsCert{Mode: AdCertsSignerModeRemote, Remote: AdsCertRemote{Url: "http://test.com", SigningTimeoutMs: 0}}, + }, + expectErrors: true, + expectedErrors: []error{errors.New("invalid signing timeout for remote signer: 0")}, + }, + { + desc: "Remote signer config: invalid URL and SigningTimeoutMs passed to config", + data: Experiment{ + AdCerts: ExperimentAdsCert{Mode: AdCertsSignerModeRemote, Remote: AdsCertRemote{Url: "test@com", SigningTimeoutMs: 0}}, + }, + expectErrors: true, + expectedErrors: []error{errors.New("invalid url for remote signer: test@com"), + errors.New("invalid signing timeout for remote signer: 0")}, + }, + { + desc: "Remote signer config: valid URL and SigningTimeoutMs passed to config", + data: Experiment{ + AdCerts: ExperimentAdsCert{Mode: AdCertsSignerModeRemote, Remote: AdsCertRemote{Url: "http://test.com", SigningTimeoutMs: 5}}, + }, + expectErrors: false, + expectedErrors: []error{}, + }, + { + desc: "Experiment config: experiment config is empty", + data: Experiment{ + AdCerts: ExperimentAdsCert{Mode: ""}, + }, + expectErrors: false, + expectedErrors: []error{}, + }, + { + desc: "Experiment config: experiment config is off", + data: Experiment{ + AdCerts: ExperimentAdsCert{Mode: AdCertsSignerModeOff}, + }, + expectErrors: false, + expectedErrors: []error{}, + }, + { + desc: "Experiment config: experiment config is init with a wrong value", + data: Experiment{ + AdCerts: ExperimentAdsCert{Mode: "test"}, + }, + expectErrors: true, + expectedErrors: []error{ErrSignerModeIncorrect}, + }, + { + desc: "Inprocess signer config: valid config", + data: Experiment{ + AdCerts: ExperimentAdsCert{Mode: AdCertsSignerModeInprocess, InProcess: AdsCertInProcess{Origin: "http://test.com", PrivateKey: "pk", DNSCheckIntervalInSeconds: 10, DNSRenewalIntervalInSeconds: 10}}, + }, + expectErrors: false, + expectedErrors: []error{}, + }, + { + desc: "Inprocess signer config: invaild origin url passed to config", + data: Experiment{ + AdCerts: ExperimentAdsCert{Mode: AdCertsSignerModeInprocess, InProcess: AdsCertInProcess{Origin: "test@com", PrivateKey: "pk", DNSCheckIntervalInSeconds: 10, DNSRenewalIntervalInSeconds: 10}}, + }, + expectErrors: true, + expectedErrors: []error{errors.New("invalid url for inprocess signer: test@com")}, + }, + { + desc: "Inprocess signer config: empty PK passed to config", + data: Experiment{ + AdCerts: ExperimentAdsCert{Mode: AdCertsSignerModeInprocess, InProcess: AdsCertInProcess{Origin: "http://test.com", PrivateKey: "", DNSCheckIntervalInSeconds: 10, DNSRenewalIntervalInSeconds: 10}}, + }, + expectErrors: true, + expectedErrors: []error{ErrInProcessSignerInvalidPrivateKey}, + }, + { + desc: "Inprocess signer config: negative dns check interval passed to config", + data: Experiment{ + AdCerts: ExperimentAdsCert{Mode: AdCertsSignerModeInprocess, InProcess: AdsCertInProcess{Origin: "http://test.com", PrivateKey: "pk", DNSCheckIntervalInSeconds: -10, DNSRenewalIntervalInSeconds: 10}}, + }, + expectErrors: true, + expectedErrors: []error{errors.New("invalid dns check interval for inprocess signer: -10")}, + }, + { + desc: "Inprocess signer config: zero dns check interval passed to config", + data: Experiment{ + AdCerts: ExperimentAdsCert{Mode: AdCertsSignerModeInprocess, InProcess: AdsCertInProcess{Origin: "http://test.com", PrivateKey: "pk", DNSCheckIntervalInSeconds: 10, DNSRenewalIntervalInSeconds: 0}}, + }, + expectErrors: true, + expectedErrors: []error{errors.New("invalid dns renewal interval for inprocess signer: 0")}, + }, + { + desc: "Inprocess signer config: all config parameters are invalid", + data: Experiment{ + AdCerts: ExperimentAdsCert{Mode: AdCertsSignerModeInprocess, InProcess: AdsCertInProcess{Origin: "test@com", PrivateKey: "", DNSCheckIntervalInSeconds: -10, DNSRenewalIntervalInSeconds: 0}}, + }, + expectErrors: true, + expectedErrors: []error{ + errors.New("invalid url for inprocess signer: test@com"), + ErrInProcessSignerInvalidPrivateKey, + errors.New("invalid dns check interval for inprocess signer: -10"), + errors.New("invalid dns renewal interval for inprocess signer: 0")}, + }, + } + for _, test := range testCases { + errs := test.data.validate([]error{}) + if test.expectErrors { + assert.ElementsMatch(t, test.expectedErrors, errs, "Test case threw unexpected errors. Desc: %s \n", test.desc) + } else { + assert.Empty(t, test.expectedErrors, "Test case should not return errors. Desc: %s \n", test.desc) + } + } +} diff --git a/config/hooks.go b/config/hooks.go new file mode 100644 index 00000000000..17431d4212e --- /dev/null +++ b/config/hooks.go @@ -0,0 +1,34 @@ +package config + +type Hooks struct { + Enabled bool `mapstructure:"enabled"` + Modules Modules `mapstructure:"modules"` + // HostExecutionPlan defined by the host company and is executed always + HostExecutionPlan HookExecutionPlan `mapstructure:"host_execution_plan"` + // DefaultAccountExecutionPlan can be replaced by the account-specific hook execution plan + DefaultAccountExecutionPlan HookExecutionPlan `mapstructure:"default_account_execution_plan"` +} + +// Modules mapping provides module specific configuration, format: map[vendor_name]map[module_name]interface{} +// actual configuration parsing performed by modules +type Modules map[string]map[string]interface{} + +type HookExecutionPlan struct { + Endpoints map[string]struct { + Stages map[string]struct { + Groups []HookExecutionGroup `mapstructure:"groups" json:"groups"` + } `mapstructure:"stages" json:"stages"` + } `mapstructure:"endpoints" json:"endpoints"` +} + +type HookExecutionGroup struct { + // Timeout specified in milliseconds. + // Zero value marks the hook execution status with the "timeout" value. + Timeout int `mapstructure:"timeout" json:"timeout"` + HookSequence []struct { + // ModuleCode is a composite value in the format: {vendor_name}.{module_name} + ModuleCode string `mapstructure:"module_code" json:"module_code"` + // HookImplCode is an arbitrary value, used to identify hook when sending metrics, debug information, etc. + HookImplCode string `mapstructure:"hook_impl_code" json:"hook_impl_code"` + } `mapstructure:"hook_sequence" json:"hook_sequence"` +} diff --git a/config/stored_requests.go b/config/stored_requests.go index 9ff66149527..6bd8a44f411 100644 --- a/config/stored_requests.go +++ b/config/stored_requests.go @@ -1,9 +1,7 @@ package config import ( - "bytes" "fmt" - "strconv" "strings" "time" @@ -19,6 +17,7 @@ const ( VideoDataType DataType = "Video" AMPRequestDataType DataType = "AMP Request" AccountDataType DataType = "Account" + ResponseDataType DataType = "Response" ) // Section returns the config section this type is defined in @@ -29,6 +28,7 @@ func (dataType DataType) Section() string { VideoDataType: "stored_video_req", AMPRequestDataType: "stored_amp_req", AccountDataType: "accounts", + ResponseDataType: "stored_responses", }[dataType] } @@ -55,10 +55,10 @@ type StoredRequests struct { // Files should be used if Stored Requests should be loaded from the filesystem. // Fetchers are in stored_requests/backends/file_system/fetcher.go Files FileFetcherConfig `mapstructure:"filesystem"` - // Postgres configures Fetchers and EventProducers which read from a Postgres DB. - // Fetchers are in stored_requests/backends/db_fetcher/postgres.go - // EventProducers are in stored_requests/events/postgres - Postgres PostgresConfig `mapstructure:"postgres"` + // Database configures Fetchers and EventProducers which read from a Database. + // Fetchers are in stored_requests/backends/db_fetcher/fetcher.go + // EventProducers are in stored_requests/events/database + Database DatabaseConfig `mapstructure:"database"` // HTTP configures an instance of stored_requests/backends/http/http_fetcher.go. // If non-nil, Stored Requests will be fetched from the endpoint described there. HTTP HTTPFetcherConfig `mapstructure:"http"` @@ -120,9 +120,9 @@ func resolvedStoredRequestsConfig(cfg *Configuration) { // Amp uses the same config but some fields get replaced by Amp* version of similar fields cfg.StoredRequestsAMP = cfg.StoredRequests - amp.Postgres.FetcherQueries.QueryTemplate = sr.Postgres.FetcherQueries.AmpQueryTemplate - amp.Postgres.CacheInitialization.Query = sr.Postgres.CacheInitialization.AmpQuery - amp.Postgres.PollUpdates.Query = sr.Postgres.PollUpdates.AmpQuery + amp.Database.FetcherQueries.QueryTemplate = sr.Database.FetcherQueries.AmpQueryTemplate + amp.Database.CacheInitialization.Query = sr.Database.CacheInitialization.AmpQuery + amp.Database.PollUpdates.Query = sr.Database.PollUpdates.AmpQuery amp.HTTP.Endpoint = sr.HTTP.AmpEndpoint amp.CacheEvents.Endpoint = "/storedrequests/amp" amp.HTTPEvents.Endpoint = sr.HTTPEvents.AmpEndpoint @@ -133,13 +133,14 @@ func resolvedStoredRequestsConfig(cfg *Configuration) { cfg.StoredVideo.dataType = VideoDataType cfg.CategoryMapping.dataType = CategoryDataType cfg.Accounts.dataType = AccountDataType + cfg.StoredResponses.dataType = ResponseDataType } func (cfg *StoredRequests) validate(errs []error) []error { - if cfg.DataType() == AccountDataType && cfg.Postgres.ConnectionInfo.Database != "" { - errs = append(errs, fmt.Errorf("%s.postgres: retrieving accounts via postgres not available, use accounts.files", cfg.Section())) + if cfg.DataType() == AccountDataType && cfg.Database.ConnectionInfo.Database != "" { + errs = append(errs, fmt.Errorf("%s.database: retrieving accounts via database not available, use accounts.files", cfg.Section())) } else { - errs = cfg.Postgres.validate(cfg.DataType(), errs) + errs = cfg.Database.validate(cfg.DataType(), errs) } // Categories do not use cache so none of the following checks apply @@ -156,27 +157,27 @@ func (cfg *StoredRequests) validate(errs []error) []error { errs = append(errs, fmt.Errorf("%s: http_events.refresh_rate_seconds must be 0 if in_memory_cache=none", cfg.Section())) } - if cfg.Postgres.PollUpdates.Query != "" { - errs = append(errs, fmt.Errorf("%s: postgres.poll_for_updates.query must be empty if in_memory_cache=none", cfg.Section())) + if cfg.Database.PollUpdates.Query != "" { + errs = append(errs, fmt.Errorf("%s: database.poll_for_updates.query must be empty if in_memory_cache=none", cfg.Section())) } - if cfg.Postgres.CacheInitialization.Query != "" { - errs = append(errs, fmt.Errorf("%s: postgres.initialize_caches.query must be empty if in_memory_cache=none", cfg.Section())) + if cfg.Database.CacheInitialization.Query != "" { + errs = append(errs, fmt.Errorf("%s: database.initialize_caches.query must be empty if in_memory_cache=none", cfg.Section())) } } errs = cfg.InMemoryCache.validate(cfg.DataType(), errs) return errs } -// PostgresConfig configures the Stored Request ecosystem to use Postgres. This must include a Fetcher, +// DatabaseConfig configures the Stored Request ecosystem to use Database. This must include a Fetcher, // and may optionally include some EventProducers to populate and refresh the caches. -type PostgresConfig struct { - ConnectionInfo PostgresConnection `mapstructure:"connection"` - FetcherQueries PostgresFetcherQueries `mapstructure:"fetcher"` - CacheInitialization PostgresCacheInitializer `mapstructure:"initialize_caches"` - PollUpdates PostgresUpdatePolling `mapstructure:"poll_for_updates"` +type DatabaseConfig struct { + ConnectionInfo DatabaseConnection `mapstructure:"connection"` + FetcherQueries DatabaseFetcherQueries `mapstructure:"fetcher"` + CacheInitialization DatabaseCacheInitializer `mapstructure:"initialize_caches"` + PollUpdates DatabaseUpdatePolling `mapstructure:"poll_for_updates"` } -func (cfg *PostgresConfig) validate(dataType DataType, errs []error) []error { +func (cfg *DatabaseConfig) validate(dataType DataType, errs []error) []error { if cfg.ConnectionInfo.Database == "" { return errs } @@ -186,9 +187,10 @@ func (cfg *PostgresConfig) validate(dataType DataType, errs []error) []error { return errs } -// PostgresConnection has options which put types to the Postgres Connection string. See: +// DatabaseConnection has options which put types to the Database Connection string. See: // https://godoc.org/github.com/lib/pq#hdr-Connection_String_Parameters -type PostgresConnection struct { +type DatabaseConnection struct { + Driver string `mapstructure:"driver"` Database string `mapstructure:"dbname"` Host string `mapstructure:"host"` Port int `mapstructure:"port"` @@ -196,55 +198,18 @@ type PostgresConnection struct { Password string `mapstructure:"password"` } -func (cfg *PostgresConnection) ConnString() string { - buffer := bytes.NewBuffer(nil) - - if cfg.Host != "" { - buffer.WriteString("host=") - buffer.WriteString(cfg.Host) - buffer.WriteString(" ") - } - - if cfg.Port > 0 { - buffer.WriteString("port=") - buffer.WriteString(strconv.Itoa(cfg.Port)) - buffer.WriteString(" ") - } - - if cfg.Username != "" { - buffer.WriteString("user=") - buffer.WriteString(cfg.Username) - buffer.WriteString(" ") - } - - if cfg.Password != "" { - buffer.WriteString("password=") - buffer.WriteString(cfg.Password) - buffer.WriteString(" ") - } - - if cfg.Database != "" { - buffer.WriteString("dbname=") - buffer.WriteString(cfg.Database) - buffer.WriteString(" ") - } - - buffer.WriteString("sslmode=disable") - return buffer.String() -} - -type PostgresFetcherQueries struct { - // QueryTemplate is the Postgres Query which can be used to fetch configs from the database. +type DatabaseFetcherQueries struct { + // QueryTemplate is the Database Query which can be used to fetch configs from the database. // It is a Template, rather than a full Query, because a single HTTP request may reference multiple Stored Requests. // // In the simplest case, this could be something like: // SELECT id, requestData, 'request' as type // FROM stored_requests - // WHERE id in %REQUEST_ID_LIST% + // WHERE id in $REQUEST_ID_LIST // UNION ALL // SELECT id, impData, 'imp' as type // FROM stored_imps - // WHERE id in %IMP_ID_LIST% + // WHERE id in $IMP_ID_LIST // // The MakeQuery function will transform this query into: // SELECT id, requestData, 'request' as type @@ -262,7 +227,7 @@ type PostgresFetcherQueries struct { AmpQueryTemplate string `mapstructure:"amp_query"` } -type PostgresCacheInitializer struct { +type DatabaseCacheInitializer struct { Timeout int `mapstructure:"timeout_ms"` // Query should be something like: // @@ -272,27 +237,27 @@ type PostgresCacheInitializer struct { // // This query will be run once on startup to fetch _all_ known Stored Request data from the database. // - // For more details on the expected format of requestData and impData, see stored_requests/events/postgres/polling.go + // For more details on the expected format of requestData and impData, see stored_requests/events/database/database.go Query string `mapstructure:"query"` // AmpQuery is just like Query, but for AMP Stored Requests AmpQuery string `mapstructure:"amp_query"` } -func (cfg *PostgresCacheInitializer) validate(dataType DataType, errs []error) []error { +func (cfg *DatabaseCacheInitializer) validate(dataType DataType, errs []error) []error { section := dataType.Section() if cfg.Query == "" { return errs } if cfg.Timeout <= 0 { - errs = append(errs, fmt.Errorf("%s: postgres.initialize_caches.timeout_ms must be positive", section)) + errs = append(errs, fmt.Errorf("%s: database.initialize_caches.timeout_ms must be positive", section)) } if strings.Contains(cfg.Query, "$") { - errs = append(errs, fmt.Errorf("%s: postgres.initialize_caches.query should not contain any wildcards (e.g. $1)", section)) + errs = append(errs, fmt.Errorf("%s: database.initialize_caches.query should not contain any wildcards denoted by $ (e.g. $LAST_UPDATED)", section)) } return errs } -type PostgresUpdatePolling struct { +type DatabaseUpdatePolling struct { // RefreshRate determines how frequently the Query and AmpQuery are run. RefreshRate int `mapstructure:"refresh_rate_seconds"` @@ -303,11 +268,11 @@ type PostgresUpdatePolling struct { // // SELECT id, requestData, 'request' AS type // FROM stored_requests - // WHERE last_updated > $1 + // WHERE last_updated > $LAST_UPDATED // UNION ALL - // SELECT id, requestData, 'imp' AS type + // SELECT id, impData, 'imp' AS type // FROM stored_imps - // WHERE last_updated > $1 + // WHERE last_updated > $LAST_UPDATED // // The code will be run periodically to fetch updates from the database. Query string `mapstructure:"query"` @@ -315,89 +280,24 @@ type PostgresUpdatePolling struct { AmpQuery string `mapstructure:"amp_query"` } -func (cfg *PostgresUpdatePolling) validate(dataType DataType, errs []error) []error { +func (cfg *DatabaseUpdatePolling) validate(dataType DataType, errs []error) []error { section := dataType.Section() if cfg.Query == "" { return errs } if cfg.RefreshRate <= 0 { - errs = append(errs, fmt.Errorf("%s: postgres.poll_for_updates.refresh_rate_seconds must be > 0", section)) + errs = append(errs, fmt.Errorf("%s: database.poll_for_updates.refresh_rate_seconds must be > 0", section)) } if cfg.Timeout <= 0 { - errs = append(errs, fmt.Errorf("%s: postgres.poll_for_updates.timeout_ms must be > 0", section)) + errs = append(errs, fmt.Errorf("%s: database.poll_for_updates.timeout_ms must be > 0", section)) } - - if !strings.Contains(cfg.Query, "$1") || strings.Contains(cfg.Query, "$2") { - errs = append(errs, fmt.Errorf("%s: postgres.poll_for_updates.query must contain exactly one wildcard", section)) + if !strings.Contains(cfg.Query, "$LAST_UPDATED") { + errs = append(errs, fmt.Errorf("%s: database.poll_for_updates.query must contain $LAST_UPDATED parameter", section)) } - return errs -} - -// MakeQuery builds a query which can fetch numReqs Stored Requests and numImps Stored Imps. -// See the docs on PostgresConfig.QueryTemplate for a description of how it works. -func (cfg *PostgresFetcherQueries) MakeQuery(numReqs int, numImps int) (query string) { - return resolve(cfg.QueryTemplate, numReqs, numImps) -} -func (cfg *PostgresFetcherQueries) MakeQueryResponses(numIds int) (query string) { - return resolveQueryResponses(cfg.QueryTemplate, numIds) -} - -func resolve(template string, numReqs int, numImps int) (query string) { - numReqs = ensureNonNegative("Request", numReqs) - numImps = ensureNonNegative("Imp", numImps) - - query = strings.Replace(template, "%REQUEST_ID_LIST%", makeIdList(0, numReqs), -1) - query = strings.Replace(query, "%IMP_ID_LIST%", makeIdList(numReqs, numImps), -1) - return -} - -func resolveQueryResponses(template string, numIds int) (query string) { - numIds = ensureNonNegative("Response", numIds) - - query = strings.Replace(template, "%ID_LIST%", makeIdList(0, numIds), -1) - return -} - -func ensureNonNegative(storedThing string, num int) int { - if num < 0 { - glog.Errorf("Can't build a SQL query for %d Stored %ss.", num, storedThing) - return 0 - } - return num -} - -func makeIdList(numSoFar int, numArgs int) string { - // Any empty list like "()" is illegal in Postgres. A (NULL) is the next best thing, - // though, since `id IN (NULL)` is valid for all "id" column types, and evaluates to an empty set. - // - // The query plan also suggests that it's basically free: - // - // explain SELECT id, requestData FROM stored_requests WHERE id in %ID_LIST%; - // - // QUERY PLAN - // ------------------------------------------- - // Result (cost=0.00..0.00 rows=0 width=16) - // One-Time Filter: false - // (2 rows) - if numArgs == 0 { - return "(NULL)" - } - - final := bytes.NewBuffer(make([]byte, 0, 2+4*numArgs)) - final.WriteString("(") - for i := numSoFar + 1; i < numSoFar+numArgs; i++ { - final.WriteString("$") - final.WriteString(strconv.Itoa(i)) - final.WriteString(", ") - } - final.WriteString("$") - final.WriteString(strconv.Itoa(numSoFar + numArgs)) - final.WriteString(")") - - return final.String() + return errs } type InMemoryCache struct { diff --git a/config/stored_requests_test.go b/config/stored_requests_test.go index ac468d2a32b..6184d19bd38 100644 --- a/config/stored_requests_test.go +++ b/config/stored_requests_test.go @@ -2,119 +2,11 @@ package config import ( "errors" - "strconv" - "strings" "testing" "github.com/stretchr/testify/assert" ) -const sampleQueryTemplate = "SELECT id, requestData, 'request' as type FROM stored_requests WHERE id in %REQUEST_ID_LIST% UNION ALL SELECT id, impData, 'imp' as type FROM stored_requests WHERE id in %IMP_ID_LIST%" -const sampleResponsesQueryTemplate = "SELECT id, responseData, 'response' as type FROM stored_responses WHERE id in %ID_LIST%" - -func TestNormalQueryMaker(t *testing.T) { - madeQuery := buildQuery(sampleQueryTemplate, 1, 3) - assertStringsEqual(t, madeQuery, "SELECT id, requestData, 'request' as type FROM stored_requests WHERE id in ($1) UNION ALL SELECT id, impData, 'imp' as type FROM stored_requests WHERE id in ($2, $3, $4)") -} - -func TestQueryMakerManyImps(t *testing.T) { - madeQuery := buildQuery(sampleQueryTemplate, 1, 11) - assertStringsEqual(t, madeQuery, "SELECT id, requestData, 'request' as type FROM stored_requests WHERE id in ($1) UNION ALL SELECT id, impData, 'imp' as type FROM stored_requests WHERE id in ($2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)") -} - -func TestQueryMakerNoRequests(t *testing.T) { - madeQuery := buildQuery(sampleQueryTemplate, 0, 3) - assertStringsEqual(t, madeQuery, "SELECT id, requestData, 'request' as type FROM stored_requests WHERE id in (NULL) UNION ALL SELECT id, impData, 'imp' as type FROM stored_requests WHERE id in ($1, $2, $3)") -} - -func TestQueryMakerNoImps(t *testing.T) { - madeQuery := buildQuery(sampleQueryTemplate, 1, 0) - assertStringsEqual(t, madeQuery, "SELECT id, requestData, 'request' as type FROM stored_requests WHERE id in ($1) UNION ALL SELECT id, impData, 'imp' as type FROM stored_requests WHERE id in (NULL)") -} - -func TestQueryMakerMultilists(t *testing.T) { - madeQuery := buildQuery("SELECT id, config FROM table WHERE id in %IMP_ID_LIST% UNION ALL SELECT id, config FROM other_table WHERE id in %IMP_ID_LIST%", 0, 3) - assertStringsEqual(t, madeQuery, "SELECT id, config FROM table WHERE id in ($1, $2, $3) UNION ALL SELECT id, config FROM other_table WHERE id in ($1, $2, $3)") -} - -func TestQueryMakerNegative(t *testing.T) { - query := buildQuery(sampleQueryTemplate, -1, -2) - expected := buildQuery(sampleQueryTemplate, 0, 0) - assertStringsEqual(t, query, expected) -} - -func TestResponseQueryMaker(t *testing.T) { - testCases := []struct { - description string - inputRespNumber int - expectedQuery string - }{ - { - description: "single response query maker", - inputRespNumber: 1, - expectedQuery: "SELECT id, responseData, 'response' as type FROM stored_responses WHERE id in ($1)", - }, - { - description: "many responses query maker", - inputRespNumber: 11, - expectedQuery: "SELECT id, responseData, 'response' as type FROM stored_responses WHERE id in ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", - }, - { - description: "no responses query maker", - inputRespNumber: 0, - expectedQuery: "SELECT id, responseData, 'response' as type FROM stored_responses WHERE id in (NULL)", - }, - { - description: "no responses query maker", - inputRespNumber: -2, - expectedQuery: "SELECT id, responseData, 'response' as type FROM stored_responses WHERE id in (NULL)", - }, - } - - for _, test := range testCases { - cfg := PostgresFetcherQueries{QueryTemplate: sampleResponsesQueryTemplate} - query := cfg.MakeQueryResponses(test.inputRespNumber) - assertStringsEqual(t, query, test.expectedQuery) - } -} - -func TestPostgressConnString(t *testing.T) { - db := "TestDB" - host := "somehost.com" - port := 20 - username := "someuser" - password := "somepassword" - - cfg := PostgresConnection{ - Database: db, - Host: host, - Port: port, - Username: username, - Password: password, - } - - dataSourceName := cfg.ConnString() - paramList := strings.Split(dataSourceName, " ") - params := make(map[string]string, len(paramList)) - for _, param := range paramList { - keyVals := strings.Split(param, "=") - if len(keyVals) != 2 { - t.Fatalf(`param "%s" must only have one equals sign`, param) - } - if _, ok := params[keyVals[0]]; ok { - t.Fatalf("found duplicate param at key %s", keyVals[0]) - } - params[keyVals[0]] = keyVals[1] - } - - assertHasValue(t, params, "dbname", db) - assertHasValue(t, params, "host", host) - assertHasValue(t, params, "port", strconv.Itoa(port)) - assertHasValue(t, params, "user", username) - assertHasValue(t, params, "password", password) - assertHasValue(t, params, "sslmode", "disable") -} - func TestInMemoryCacheValidationStoredRequests(t *testing.T) { assertNoErrs(t, (&InMemoryCache{ Type: "unbounded", @@ -211,7 +103,7 @@ func TestInMemoryCacheValidationSingleCache(t *testing.T) { }).validate(AccountDataType, nil)) } -func TestPostgresConfigValidation(t *testing.T) { +func TestDatabaseConfigValidation(t *testing.T) { tests := []struct { description string connectionStr string @@ -247,21 +139,21 @@ func TestPostgresConfigValidation(t *testing.T) { { description: "Invalid cache init query contains wildcard", connectionStr: "some-connection-string", - cacheInitQuery: "SELECT * FROM table WHERE $1", + cacheInitQuery: "SELECT * FROM table WHERE $LAST_UPDATED", cacheInitTimeout: 1, wantErrorCount: 1, }, { description: "Valid cache update query with non-zero timeout and refresh rate", connectionStr: "some-connection-string", - cacheUpdateQuery: "SELECT * FROM table WHERE $1", + cacheUpdateQuery: "SELECT * FROM table WHERE $LAST_UPDATED", cacheUpdateRefreshRate: 1, cacheUpdateTimeout: 1, }, { description: "Valid cache update query with zero timeout and non-zero refresh rate", connectionStr: "some-connection-string", - cacheUpdateQuery: "SELECT * FROM table WHERE $1", + cacheUpdateQuery: "SELECT * FROM table WHERE $LAST_UPDATED", cacheUpdateRefreshRate: 1, cacheUpdateTimeout: 0, wantErrorCount: 1, @@ -269,7 +161,7 @@ func TestPostgresConfigValidation(t *testing.T) { { description: "Valid cache update query with non-zero timeout and zero refresh rate", connectionStr: "some-connection-string", - cacheUpdateQuery: "SELECT * FROM table WHERE $1", + cacheUpdateQuery: "SELECT * FROM table WHERE $LAST_UPDATED", cacheUpdateRefreshRate: 0, cacheUpdateTimeout: 1, wantErrorCount: 1, @@ -286,29 +178,29 @@ func TestPostgresConfigValidation(t *testing.T) { description: "Multiple errors: valid queries missing timeouts and refresh rates plus existing error", connectionStr: "some-connection-string", cacheInitQuery: "SELECT * FROM table;", - cacheUpdateQuery: "SELECT * FROM table WHERE $1", + cacheUpdateQuery: "SELECT * FROM table WHERE $LAST_UPDATED", existingErrors: []error{errors.New("existing error before calling validate")}, wantErrorCount: 4, }, } for _, tt := range tests { - pgConfig := &PostgresConfig{ - ConnectionInfo: PostgresConnection{ + dbConfig := &DatabaseConfig{ + ConnectionInfo: DatabaseConnection{ Database: tt.connectionStr, }, - CacheInitialization: PostgresCacheInitializer{ + CacheInitialization: DatabaseCacheInitializer{ Query: tt.cacheInitQuery, Timeout: tt.cacheInitTimeout, }, - PollUpdates: PostgresUpdatePolling{ + PollUpdates: DatabaseUpdatePolling{ Query: tt.cacheUpdateQuery, RefreshRate: tt.cacheUpdateRefreshRate, Timeout: tt.cacheUpdateTimeout, }, } - errs := pgConfig.validate(RequestDataType, tt.existingErrors) + errs := dbConfig.validate(RequestDataType, tt.existingErrors) assert.Equal(t, tt.wantErrorCount, len(errs), tt.description) } } @@ -327,24 +219,6 @@ func assertNoErrs(t *testing.T, err []error) { } } -func assertHasValue(t *testing.T, m map[string]string, key string, val string) { - t.Helper() - realVal, ok := m[key] - if !ok { - t.Errorf("Map missing required key: %s", key) - } - if val != realVal { - t.Errorf("Unexpected value at key %s. Expected %s, Got %s", key, val, realVal) - } -} - -func buildQuery(template string, numReqs int, numImps int) string { - cfg := PostgresFetcherQueries{} - cfg.QueryTemplate = template - - return cfg.MakeQuery(numReqs, numImps) -} - func assertStringsEqual(t *testing.T, actual string, expected string) { if actual != expected { t.Errorf("Queries did not match.\n\"%s\" -- expected\n\"%s\" -- actual", expected, actual) @@ -358,21 +232,22 @@ func TestResolveConfig(t *testing.T) { Files: FileFetcherConfig{ Enabled: true, Path: "/test-path"}, - Postgres: PostgresConfig{ - ConnectionInfo: PostgresConnection{ + Database: DatabaseConfig{ + ConnectionInfo: DatabaseConnection{ + Driver: "postgres", Database: "db", Host: "pghost", Port: 5, Username: "user", Password: "pass", }, - FetcherQueries: PostgresFetcherQueries{ + FetcherQueries: DatabaseFetcherQueries{ AmpQueryTemplate: "amp-fetcher-query", }, - CacheInitialization: PostgresCacheInitializer{ + CacheInitialization: DatabaseCacheInitializer{ AmpQuery: "amp-cache-init-query", }, - PollUpdates: PostgresUpdatePolling{ + PollUpdates: DatabaseUpdatePolling{ AmpQuery: "amp-poll-query", }, }, @@ -394,9 +269,9 @@ func TestResolveConfig(t *testing.T) { }, } - cfg.StoredRequests.Postgres.FetcherQueries.QueryTemplate = "auc-fetcher-query" - cfg.StoredRequests.Postgres.CacheInitialization.Query = "auc-cache-init-query" - cfg.StoredRequests.Postgres.PollUpdates.Query = "auc-poll-query" + cfg.StoredRequests.Database.FetcherQueries.QueryTemplate = "auc-fetcher-query" + cfg.StoredRequests.Database.CacheInitialization.Query = "auc-cache-init-query" + cfg.StoredRequests.Database.PollUpdates.Query = "auc-poll-query" cfg.StoredRequests.HTTP.Endpoint = "auc-http-fetcher-endpoint" cfg.StoredRequests.HTTPEvents.Endpoint = "auc-http-events-endpoint" @@ -408,9 +283,9 @@ func TestResolveConfig(t *testing.T) { assertStringsEqual(t, auc.CacheEvents.Endpoint, "/storedrequests/openrtb2") // Amp should have the amp values in it - assertStringsEqual(t, amp.Postgres.FetcherQueries.QueryTemplate, cfg.StoredRequests.Postgres.FetcherQueries.AmpQueryTemplate) - assertStringsEqual(t, amp.Postgres.CacheInitialization.Query, cfg.StoredRequests.Postgres.CacheInitialization.AmpQuery) - assertStringsEqual(t, amp.Postgres.PollUpdates.Query, cfg.StoredRequests.Postgres.PollUpdates.AmpQuery) + assertStringsEqual(t, amp.Database.FetcherQueries.QueryTemplate, cfg.StoredRequests.Database.FetcherQueries.AmpQueryTemplate) + assertStringsEqual(t, amp.Database.CacheInitialization.Query, cfg.StoredRequests.Database.CacheInitialization.AmpQuery) + assertStringsEqual(t, amp.Database.PollUpdates.Query, cfg.StoredRequests.Database.PollUpdates.AmpQuery) assertStringsEqual(t, amp.HTTP.Endpoint, cfg.StoredRequests.HTTP.AmpEndpoint) assertStringsEqual(t, amp.HTTPEvents.Endpoint, cfg.StoredRequests.HTTPEvents.AmpEndpoint) assertStringsEqual(t, amp.CacheEvents.Endpoint, "/storedrequests/amp") diff --git a/config/test/bidder-info/someBidder.yaml b/config/test/bidder-info-valid/stroeerCore.yaml similarity index 100% rename from config/test/bidder-info/someBidder.yaml rename to config/test/bidder-info-valid/stroeerCore.yaml diff --git a/currency/rate_converter.go b/currency/rate_converter.go index 39b9cd59ca2..f28807701ae 100644 --- a/currency/rate_converter.go +++ b/currency/rate_converter.go @@ -3,7 +3,7 @@ package currency import ( "encoding/json" "fmt" - "io/ioutil" + "io" "net/http" "sync/atomic" "time" @@ -60,7 +60,7 @@ func (rc *RateConverter) fetch() (*Rates, error) { defer response.Body.Close() - bytesJSON, err := ioutil.ReadAll(response.Body) + bytesJSON, err := io.ReadAll(response.Body) if err != nil { return nil, err } diff --git a/currency/rate_converter_test.go b/currency/rate_converter_test.go index 59cb0f8c4ef..617aa02e96a 100644 --- a/currency/rate_converter_test.go +++ b/currency/rate_converter_test.go @@ -1,7 +1,7 @@ package currency import ( - "io/ioutil" + "io" "net/http" "net/http/httptest" "strings" @@ -374,6 +374,6 @@ func (m *mockHttpClient) Do(req *http.Request) (*http.Response, error) { return &http.Response{ Status: "200 OK", StatusCode: http.StatusOK, - Body: ioutil.NopCloser(strings.NewReader(m.responseBody)), + Body: io.NopCloser(strings.NewReader(m.responseBody)), }, nil } diff --git a/currency/rates.go b/currency/rates.go index f1cc19fcccb..f36eddfac81 100644 --- a/currency/rates.go +++ b/currency/rates.go @@ -21,10 +21,10 @@ func NewRates(conversions map[string]map[string]float64) *Rates { } // GetRate returns the conversion rate between two currencies or: -// - An error if one of the currency strings is not well-formed -// - An error if any of the currency strings is not a recognized currency code. -// - A ConversionNotFoundError in case the conversion rate between the two -// given currencies is not in the currencies rates map +// - An error if one of the currency strings is not well-formed +// - An error if any of the currency strings is not a recognized currency code. +// - A ConversionNotFoundError in case the conversion rate between the two +// given currencies is not in the currencies rates map func (r *Rates) GetRate(from, to string) (float64, error) { var err error fromUnit, err := currency.ParseISO(from) diff --git a/docs/adscertsigner.md b/docs/adscertsigner.md new file mode 100644 index 00000000000..b16adb6ecfe --- /dev/null +++ b/docs/adscertsigner.md @@ -0,0 +1,90 @@ +##Ads Cert + +Ads Cert is an experimental feature to support Ads.Cert 2.0 in Prebid Server. +The ads.cert protocol provides a standard method for distributing public keys so that other ads +ecosystem participants can find them and use them within these key exchange and message +authentication processes. To simplify this process, we use the domain name system (DNS) to +distribute public keys. + +Detailed Ads.Cert 2.0 specification is published on the [IAB Tech Lab ads.cert website](https://iabtechlab.com/ads-cert). + + +###General set up +According to [Ads Cert Authenticated Connections protocol](https://iabtechlab.com/wp-content/uploads/2021/09/3-ads-cert-authenticated-connections-pc.pdf) +the requested domain requires to support Call Sign Internet domain established for Public keys publishing. +In case origin URL is **bidder.com** then two subdomains has to be configured to return TXT records: + +`_adscert.bidder.com` - returns record in next format: +`v=adpf a=bidder.com` + +`_delivery._adscert.bidder.com` - returns record that looks like this: +`v=adcrtd k=x25519 h=sha256 p=w8f3160kEklY-nKuxogvn5PsZQLfkWWE0gUq_4JfFm8` + +For testing purposes please use this test domain (subscription will expire in May 2023): +`adscertdelivery.com`. To check data it returns use any online tool ([like this](https://mxtoolbox.com/SuperTool.aspx), select TXT lookup) to read TXT records: +`_delivery._adscert.adscertdelivery.com` and `_adscert.adscertdelivery.com` + +Or just run cli command: +```dig txt _delivery._adscert.adscertdelivery.com``` + +Public key returned in `_delivery._adscert.adscertdelivery.com` was generated using [OSS repository](https://github.com/IABTechLab/adscert). +From the project root compile sources and run `go run . basicinsecurekeygen`. This will return randomly generated private and public keys and the entire value for `_delivery._adscert.adscertdelivery.com` record. + +Private key for public key published under `_delivery._adscert.adscertdelivery.com`: +``` +Randomly generated key pair +Public key: HweE1-dFJPjHO4C34QXq6myhtMuyi4X0T2rUolVzQig +Private key: U6KBGSEQ5kuMn3s_ohxYbmdmG7Xoos9hR3fJ_dDOi6Q +DNS TXT Entry: "v=adcrtd k=x25519 h=sha256 p=HweE1-dFJPjHO4C34QXq6myhtMuyi4X0T2rUolVzQig" +``` + +If everything configured correctly then `X-Ads-Cert-Auth` header will be sent to bidder. Detailed information about content of the header value can be found in Ads Cert Authenticated Connections protocol specification. + +###Prebid Server set up +Current Prebid Server implementation supports in-process and remote signing approach. + +####In-Process signer +To enable AdsCerts next configurations should be specified: + +Host config, can be set using env variables or yaml config, use proper format: +```json +"experiment": { + "adscert": { + "mode": "inprocess", + "inprocess": { + "origin": "http://adscertdelivery.com", + "key": "U6KBGSEQ5kuMn3s_ohxYbmdmG7Xoos9hR3fJ_dDOi6Q", + "domain_check_interval_seconds": 30, + "domain_renewal_interval_seconds": 30 + } + } + } +``` +####Remote signer +To use this approach standalone GRPC server should be available. +One way to do this is to run in locally. For this checkout [AdsCert OSS](https://github.com/IABTechLab/adscert) and navigate to https://github.com/IABTechLab/adscert/blob/main/cmd/server/main.go file. +Modify L17, set "origin" to `adscertdelivery.com`, make sure ports 3000 and 3001 are available and run main function. +In Prebid Server configs set parameters for this server: +```json +"experiment": { + "adscert": { + "mode": "remote", + "remote": { + "url": "localhost:3000", + "signing_timeout_ms": 5 + } + } + } +``` + +####General Prebid Server set up +Workaround for bidders that don't have Call Signs support yet: in configs modify bidder URL to `http://adscertdelivery.com/openrtb2?prebid_disabled=1`. In this case this bidder will not return bids, because this endpoint doesn't exist, but it will imitate support of Call Signs. Bidder parameters still should be valid. + +Every bidder by default doesn't support AdsCert. Some bidders cannot handle unsupported headers properly. To enable this feature add next config to {bidder}.yaml file: +`experiment.adsCert.enabled: true`. With this config bidder will receive `X-Ads-Cert-Auth` header even if this is not the only bidder in request. + +Request extension should have `request.ext.prebid.experiment.adscert.enabled: true` + +###Issue to fix: +- After server start up the very first request doesn't have `X-Ads-Cert-Auth` header. But it works every time after the first request. +- Bidders that don't support CallSigns don't receive a default `X-Ads-Cert-Auth` header \ No newline at end of file diff --git a/docs/developers/stored-requests.md b/docs/developers/stored-requests.md index 9adf4ed1309..0f24391c04d 100644 --- a/docs/developers/stored-requests.md +++ b/docs/developers/stored-requests.md @@ -194,14 +194,32 @@ with different [configuration options](configuration.md). For example: ```yaml stored_requests: - postgres: - host: localhost - port: 5432 - user: db-username - dbname: database-name - query: SELECT id, requestData, 'request' as type FROM stored_requests WHERE id in %REQUEST_ID_LIST% UNION ALL SELECT id, impData, 'imp' as type FROM stored_imps WHERE id in %IMP_ID_LIST%; + database: + connection: + driver: postgres + host: localhost + port: 5432 + user: db-username + dbname: database-name + fetcher: + query: SELECT id, requestData, 'request' as type FROM stored_requests WHERE id in $REQUEST_ID_LIST UNION ALL SELECT id, impData, 'imp' as type FROM stored_imps WHERE id in $IMP_ID_LIST; ``` +### Supported Databases +- postgres +- mysql + +### Query Syntax +All database queries should be expressed using the native SQL syntax of your supported database of choice with one caveat. + +For all supported database drivers, wherever you need to specify a query parameter, you must not use the native syntax (e.g. `$1`, `%%`, `?`, etc.), but rather a PBS-specific syntax to represent the parameter which is of the format `$VARIABLE_NAME`. PBS currently supports just four query parameters, each of which pertains to particular config queries, and here is how they should be specified in your queries: +- last updated at timestamp --> `$LAST_UPDATED` +- stored request ID list --> `$REQUEST_ID_LIST` +- stored imp ID list --> `$IMP_ID_LIST` +- stored response ID list --> `$ID_LIST` + +See the query defined at `stored_requests.database.connection.fetcher.query` in the yaml config above as an example of how to mix these variables in with native SQL syntax. + ```yaml stored_requests: http: @@ -234,17 +252,20 @@ Any concrete Fetcher in the project will be composed with any Cache(s) to create EventProducer events are used to Save or Invalidate values from the Cache(s). Saves and invalidates will propagate to all Cache layers. -Here is an example `pbs.yaml` file which looks for Stored Requests first from Postgres, and then from an HTTP endpoint. +Here is an example `pbs.yaml` file which looks for Stored Requests first from Database (i.e. Postgres), and then from an HTTP endpoint. It will use an in-memory LRU cache to store data locally, and poll another HTTP endpoint to listen for updates. ```yaml stored_requests: - postgres: - host: localhost - port: 5432 - user: db-username - dbname: database-name - query: SELECT id, requestData, 'request' as type FROM stored_requests WHERE id in %REQUEST_ID_LIST% UNION ALL SELECT id, impData, 'imp' as type FROM stored_imps WHERE id in %IMP_ID_LIST%; + database: + connection: + driver: postgres + host: localhost + port: 5432 + user: db-username + dbname: database-name + fetcher: + query: SELECT id, requestData, 'request' as type FROM stored_requests WHERE id in $REQUEST_ID_LIST UNION ALL SELECT id, impData, 'imp' as type FROM stored_imps WHERE id in $IMP_ID_LIST; http: endpoint: http://stored-requests.prebid.com amp_endpoint: http://stored-requests.prebid.com?amp=true diff --git a/endpoints/cookie_sync.go b/endpoints/cookie_sync.go index 7cb1d2d75d1..c61cedbb082 100644 --- a/endpoints/cookie_sync.go +++ b/endpoints/cookie_sync.go @@ -5,7 +5,7 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" + "io" "net/http" "strconv" "strings" @@ -33,6 +33,7 @@ var ( 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") errCookieSyncAccountBlocked = errors.New("account is disabled, please reach out to the prebid server host") + errCookieSyncAccountConfigMalformed = errors.New("account config is malformed and could not be read") errCookieSyncAccountInvalid = errors.New("account must be valid if provided, please reach out to the prebid server host") ) @@ -41,7 +42,8 @@ var cookieSyncBidderFilterAllowAll = usersync.NewUniformBidderFilter(usersync.Bi func NewCookieSyncEndpoint( syncersByBidder map[string]usersync.Syncer, config *config.Configuration, - gdprPermissions gdpr.Permissions, + gdprPermsBuilder gdpr.PermissionsBuilder, + tcf2CfgBuilder gdpr.TCF2ConfigBuilder, metrics metrics.MetricsEngine, pbsAnalytics analytics.PBSAnalyticsModule, accountsFetcher stored_requests.AccountFetcher, @@ -56,10 +58,11 @@ func NewCookieSyncEndpoint( chooser: usersync.NewChooser(syncersByBidder), config: config, privacyConfig: usersyncPrivacyConfig{ - gdprConfig: config.GDPR, - gdprPermissions: gdprPermissions, - ccpaEnforce: config.CCPA.Enforce, - bidderHashSet: bidderHashSet, + gdprConfig: config.GDPR, + gdprPermissionsBuilder: gdprPermsBuilder, + tcf2ConfigBuilder: tcf2CfgBuilder, + ccpaEnforce: config.CCPA.Enforce, + bidderHashSet: bidderHashSet, }, metrics: metrics, pbsAnalytics: pbsAnalytics, @@ -103,7 +106,7 @@ func (c *cookieSyncEndpoint) Handle(w http.ResponseWriter, r *http.Request, _ ht func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, privacy.Policies, error) { defer r.Body.Close() - body, err := ioutil.ReadAll(r.Body) + body, err := io.ReadAll(r.Body) if err != nil { return usersync.Request{}, privacy.Policies{}, errCookieSyncBody } @@ -113,6 +116,14 @@ func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, pr return usersync.Request{}, privacy.Policies{}, fmt.Errorf("JSON parsing failed: %s", err.Error()) } + if request.Account == "" { + request.Account = metrics.PublisherUnknown + } + account, fetchErrs := accountService.GetAccount(context.Background(), c.config, c.accountsFetcher, request.Account) + if len(fetchErrs) > 0 { + return usersync.Request{}, privacy.Policies{}, combineErrors(fetchErrs) + } + var gdprString string if request.GDPR != nil { gdprString = strconv.Itoa(*request.GDPR) @@ -132,10 +143,8 @@ func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, pr } } - request, err = c.setLimit(request) - if err != nil { - return usersync.Request{}, privacy.Policies{}, err - } + request = c.setLimit(request, account.CookieSync) + request = c.setCooperativeSync(request, account.CookieSync) privacyPolicies := privacy.Policies{ GDPR: gdprPrivacy.Policy{ @@ -163,6 +172,14 @@ func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, pr return usersync.Request{}, privacy.Policies{}, err } + gdprRequestInfo := gdpr.RequestInfo{ + Consent: request.GDPRConsent, + GDPRSignal: gdprSignal, + } + + tcf2Cfg := c.privacyConfig.tcf2ConfigBuilder(c.privacyConfig.gdprConfig.TCF2, account.GDPR) + gdprPerms := c.privacyConfig.gdprPermissionsBuilder(tcf2Cfg, gdprRequestInfo) + rx := usersync.Request{ Bidders: request.Bidders, Cooperative: usersync.Cooperative{ @@ -171,9 +188,7 @@ func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, pr }, Limit: request.Limit, Privacy: usersyncPrivacy{ - gdprPermissions: c.privacyConfig.gdprPermissions, - gdprSignal: gdprSignal, - gdprConsent: request.GDPRConsent, + gdprPermissions: gdprPerms, ccpaParsedPolicy: ccpaParsedPolicy, }, SyncTypeFilter: syncTypeFilter, @@ -185,6 +200,8 @@ func (c *cookieSyncEndpoint) writeParseRequestErrorMetrics(err error) { switch err { case errCookieSyncAccountBlocked: c.metrics.RecordCookieSync(metrics.CookieSyncAccountBlocked) + case errCookieSyncAccountConfigMalformed: + c.metrics.RecordCookieSync(metrics.CookieSyncAccountConfigMalformed) case errCookieSyncAccountInvalid: c.metrics.RecordCookieSync(metrics.CookieSyncAccountInvalid) default: @@ -192,26 +209,26 @@ func (c *cookieSyncEndpoint) writeParseRequestErrorMetrics(err error) { } } -func (c *cookieSyncEndpoint) setLimit(request cookieSyncRequest) (cookieSyncRequest, error) { - if request.Account != "" { - accountInfo, errs := accountService.GetAccount(context.Background(), c.config, c.accountsFetcher, request.Account) - if len(errs) > 0 { - return request, combineErrors(errs) - } - if request.Limit <= 0 { - request.Limit = accountInfo.CookieSync.DefaultLimit - } - if request.Limit <= 0 || request.Limit > accountInfo.CookieSync.MaxLimit { - request.Limit = accountInfo.CookieSync.MaxLimit - } - if request.Limit < 0 { - request.Limit = 0 - } - if request.CooperativeSync == nil { - request.CooperativeSync = &accountInfo.CookieSync.DefaultCoopSync - } +func (c *cookieSyncEndpoint) setLimit(request cookieSyncRequest, cookieSyncConfig config.CookieSync) cookieSyncRequest { + if request.Limit <= 0 && cookieSyncConfig.DefaultLimit != nil { + request.Limit = *cookieSyncConfig.DefaultLimit + } + if cookieSyncConfig.MaxLimit != nil && (request.Limit <= 0 || request.Limit > *cookieSyncConfig.MaxLimit) { + request.Limit = *cookieSyncConfig.MaxLimit + } + if request.Limit < 0 { + request.Limit = 0 } - return request, nil + + return request +} + +func (c *cookieSyncEndpoint) setCooperativeSync(request cookieSyncRequest, cookieSyncConfig config.CookieSync) cookieSyncRequest { + if request.CooperativeSync == nil && cookieSyncConfig.DefaultCoopSync != nil { + request.CooperativeSync = cookieSyncConfig.DefaultCoopSync + } + + return request } func parseTypeFilter(request *cookieSyncRequestFilterSettings) (usersync.SyncTypeFilter, error) { @@ -291,6 +308,8 @@ func combineErrors(errs []error) error { return errCookieSyncAccountBlocked case errortypes.AcctRequiredErrorCode: return errCookieSyncAccountInvalid + case errortypes.MalformedAcctErrorCode: + return errCookieSyncAccountConfigMalformed } errorStrings = append(errorStrings, err.Error()) @@ -412,26 +431,25 @@ type cookieSyncResponseSync struct { } type usersyncPrivacyConfig struct { - gdprConfig config.GDPR - gdprPermissions gdpr.Permissions - ccpaEnforce bool - bidderHashSet map[string]struct{} + gdprConfig config.GDPR + gdprPermissionsBuilder gdpr.PermissionsBuilder + tcf2ConfigBuilder gdpr.TCF2ConfigBuilder + ccpaEnforce bool + bidderHashSet map[string]struct{} } type usersyncPrivacy struct { gdprPermissions gdpr.Permissions - gdprSignal gdpr.Signal - gdprConsent string ccpaParsedPolicy ccpa.ParsedPolicy } func (p usersyncPrivacy) GDPRAllowsHostCookie() bool { - allowCookie, err := p.gdprPermissions.HostCookiesAllowed(context.Background(), p.gdprSignal, p.gdprConsent) + allowCookie, err := p.gdprPermissions.HostCookiesAllowed(context.Background()) return err == nil && allowCookie } func (p usersyncPrivacy) GDPRAllowsBidderSync(bidder string) bool { - allowSync, err := p.gdprPermissions.BidderSyncAllowed(context.Background(), openrtb_ext.BidderName(bidder), p.gdprSignal, p.gdprConsent) + allowSync, err := p.gdprPermissions.BidderSyncAllowed(context.Background(), openrtb_ext.BidderName(bidder)) return err == nil && allowSync } diff --git a/endpoints/cookie_sync_test.go b/endpoints/cookie_sync_test.go index aed9ed73459..831553166c5 100644 --- a/endpoints/cookie_sync_test.go +++ b/endpoints/cookie_sync_test.go @@ -9,6 +9,7 @@ import ( "net/http/httptest" "strings" "testing" + "testing/iotest" "time" "github.com/prebid/prebid-server/analytics" @@ -28,8 +29,13 @@ import ( func TestNewCookieSyncEndpoint(t *testing.T) { var ( - syncersByBidder = map[string]usersync.Syncer{"a": &MockSyncer{}} - gdprPerms = MockGDPRPerms{} + syncersByBidder = map[string]usersync.Syncer{"a": &MockSyncer{}} + gdprPermsBuilder = fakePermissionsBuilder{ + permissions: &fakePermissions{}, + }.Builder + tcf2ConfigBuilder = fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + }.Builder configUserSync = config.UserSync{Cooperative: config.UserSyncCooperative{EnabledByDefault: true}} configHostCookie = config.HostCookie{Family: "foo"} configGDPR = config.GDPR{HostVendorID: 42} @@ -48,12 +54,14 @@ func TestNewCookieSyncEndpoint(t *testing.T) { GDPR: configGDPR, CCPA: config.CCPA{Enforce: configCCPAEnforce}, }, - &gdprPerms, + gdprPermsBuilder, + tcf2ConfigBuilder, &metrics, &analytics, &fetcher, bidders, ) + result := endpoint.(*cookieSyncEndpoint) expected := &cookieSyncEndpoint{ chooser: usersync.NewChooser(syncersByBidder), @@ -64,17 +72,28 @@ func TestNewCookieSyncEndpoint(t *testing.T) { CCPA: config.CCPA{Enforce: configCCPAEnforce}, }, privacyConfig: usersyncPrivacyConfig{ - gdprConfig: configGDPR, - gdprPermissions: &gdprPerms, - ccpaEnforce: configCCPAEnforce, - bidderHashSet: map[string]struct{}{"bidderA": {}, "bidderB": {}}, + gdprConfig: configGDPR, + gdprPermissionsBuilder: gdprPermsBuilder, + tcf2ConfigBuilder: tcf2ConfigBuilder, + ccpaEnforce: configCCPAEnforce, + bidderHashSet: map[string]struct{}{"bidderA": {}, "bidderB": {}}, }, metrics: &metrics, pbsAnalytics: &analytics, accountsFetcher: &fetcher, } - assert.Equal(t, expected, endpoint) + assert.IsType(t, &cookieSyncEndpoint{}, endpoint) + + assert.Equal(t, expected.config, result.config) + assert.Equal(t, expected.chooser, result.chooser) + assert.Equal(t, expected.metrics, result.metrics) + assert.Equal(t, expected.pbsAnalytics, result.pbsAnalytics) + assert.Equal(t, expected.accountsFetcher, result.accountsFetcher) + + assert.Equal(t, expected.privacyConfig.gdprConfig, result.privacyConfig.gdprConfig) + assert.Equal(t, expected.privacyConfig.ccpaEnforce, result.privacyConfig.ccpaEnforce) + assert.Equal(t, expected.privacyConfig.bidderHashSet, result.privacyConfig.bidderHashSet) } // usersyncPrivacy @@ -239,6 +258,15 @@ func TestCookieSyncHandle(t *testing.T) { mockAnalytics := MockAnalytics{} test.setAnalyticsExpectations(&mockAnalytics) + fakeAccountFetcher := FakeAccountsFetcher{} + + gdprPermsBuilder := fakePermissionsBuilder{ + permissions: &fakePermissions{}, + }.Builder + tcf2ConfigBuilder := fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + }.Builder + request := httptest.NewRequest("POST", "/cookiesync", test.givenBody) if test.givenCookie != nil { request.AddCookie(test.givenCookie.ToHTTPCookie(24 * time.Hour)) @@ -248,17 +276,24 @@ func TestCookieSyncHandle(t *testing.T) { endpoint := cookieSyncEndpoint{ chooser: FakeChooser{Result: test.givenChooserResult}, - config: &config.Configuration{}, + config: &config.Configuration{ + AccountDefaults: config.Account{Disabled: false}, + }, privacyConfig: usersyncPrivacyConfig{ gdprConfig: config.GDPR{ Enabled: true, DefaultValue: "0", }, - ccpaEnforce: true, + gdprPermissionsBuilder: gdprPermsBuilder, + tcf2ConfigBuilder: tcf2ConfigBuilder, + ccpaEnforce: true, }, - metrics: &mockMetrics, - pbsAnalytics: &mockAnalytics, + metrics: &mockMetrics, + pbsAnalytics: &mockAnalytics, + accountsFetcher: &fakeAccountFetcher, } + assert.NoError(t, endpoint.config.MarshalAccountDefaults()) + endpoint.Handle(writer, request, nil) assert.Equal(t, test.expectedStatusCode, writer.Code, test.description+":status_code") @@ -318,8 +353,7 @@ func TestCookieSyncParseRequest(t *testing.T) { }, Limit: 42, Privacy: usersyncPrivacy{ - gdprSignal: gdpr.SignalYes, - gdprConsent: "anyGDPRConsent", + gdprPermissions: &fakePermissions{}, ccpaParsedPolicy: expectedCCPAParsedPolicy, }, SyncTypeFilter: usersync.SyncTypeFilter{ @@ -362,8 +396,7 @@ func TestCookieSyncParseRequest(t *testing.T) { }, Limit: 42, Privacy: usersyncPrivacy{ - gdprSignal: gdpr.SignalYes, - gdprConsent: "anyGDPRConsent", + gdprPermissions: &fakePermissions{}, ccpaParsedPolicy: expectedCCPAParsedPolicy, }, SyncTypeFilter: usersync.SyncTypeFilter{ @@ -380,7 +413,7 @@ func TestCookieSyncParseRequest(t *testing.T) { expectedPrivacy: privacy.Policies{}, expectedRequest: usersync.Request{ Privacy: usersyncPrivacy{ - gdprSignal: gdpr.SignalAmbiguous, + gdprPermissions: &fakePermissions{}, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -406,7 +439,7 @@ func TestCookieSyncParseRequest(t *testing.T) { PriorityGroups: [][]string{{"a", "b", "c"}}, }, Privacy: usersyncPrivacy{ - gdprSignal: gdpr.SignalAmbiguous, + gdprPermissions: &fakePermissions{}, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -432,7 +465,7 @@ func TestCookieSyncParseRequest(t *testing.T) { PriorityGroups: [][]string{{"a", "b", "c"}}, }, Privacy: usersyncPrivacy{ - gdprSignal: gdpr.SignalAmbiguous, + gdprPermissions: &fakePermissions{}, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -458,7 +491,7 @@ func TestCookieSyncParseRequest(t *testing.T) { PriorityGroups: [][]string{{"a", "b", "c"}}, }, Privacy: usersyncPrivacy{ - gdprSignal: gdpr.SignalAmbiguous, + gdprPermissions: &fakePermissions{}, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -484,7 +517,7 @@ func TestCookieSyncParseRequest(t *testing.T) { PriorityGroups: [][]string{{"a", "b", "c"}}, }, Privacy: usersyncPrivacy{ - gdprSignal: gdpr.SignalAmbiguous, + gdprPermissions: &fakePermissions{}, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -510,7 +543,7 @@ func TestCookieSyncParseRequest(t *testing.T) { PriorityGroups: [][]string{{"a", "b", "c"}}, }, Privacy: usersyncPrivacy{ - gdprSignal: gdpr.SignalAmbiguous, + gdprPermissions: &fakePermissions{}, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -536,7 +569,7 @@ func TestCookieSyncParseRequest(t *testing.T) { PriorityGroups: [][]string{{"a", "b", "c"}}, }, Privacy: usersyncPrivacy{ - gdprSignal: gdpr.SignalAmbiguous, + gdprPermissions: &fakePermissions{}, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -552,7 +585,7 @@ func TestCookieSyncParseRequest(t *testing.T) { expectedPrivacy: privacy.Policies{}, expectedRequest: usersync.Request{ Privacy: usersyncPrivacy{ - gdprSignal: gdpr.SignalAmbiguous, + gdprPermissions: &fakePermissions{}, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -571,7 +604,7 @@ func TestCookieSyncParseRequest(t *testing.T) { }, expectedRequest: usersync.Request{ Privacy: usersyncPrivacy{ - gdprSignal: gdpr.SignalAmbiguous, + gdprPermissions: &fakePermissions{}, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -610,7 +643,7 @@ func TestCookieSyncParseRequest(t *testing.T) { }, expectedRequest: usersync.Request{ Privacy: usersyncPrivacy{ - gdprSignal: gdpr.SignalNo, + gdprPermissions: &fakePermissions{}, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -635,7 +668,7 @@ func TestCookieSyncParseRequest(t *testing.T) { }, expectedRequest: usersync.Request{ Privacy: usersyncPrivacy{ - gdprSignal: gdpr.SignalAmbiguous, + gdprPermissions: &fakePermissions{}, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -652,7 +685,7 @@ func TestCookieSyncParseRequest(t *testing.T) { }, { description: "HTTP Read Error", - givenBody: ErrReader(errors.New("anyError")), + givenBody: iotest.ErrReader(errors.New("anyError")), givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, givenCCPAEnabled: true, expectedError: "Failed to read request body", @@ -680,7 +713,7 @@ func TestCookieSyncParseRequest(t *testing.T) { }, Limit: 30, Privacy: usersyncPrivacy{ - gdprSignal: gdpr.SignalAmbiguous, + gdprPermissions: &fakePermissions{}, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -711,7 +744,7 @@ func TestCookieSyncParseRequest(t *testing.T) { }, Limit: 20, Privacy: usersyncPrivacy{ - gdprSignal: gdpr.SignalAmbiguous, + gdprPermissions: &fakePermissions{}, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -742,7 +775,7 @@ func TestCookieSyncParseRequest(t *testing.T) { }, Limit: 20, Privacy: usersyncPrivacy{ - gdprSignal: gdpr.SignalAmbiguous, + gdprPermissions: &fakePermissions{}, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -757,14 +790,23 @@ func TestCookieSyncParseRequest(t *testing.T) { for _, test := range testCases { httpRequest := httptest.NewRequest("POST", "/cookiesync", test.givenBody) + gdprPermsBuilder := fakePermissionsBuilder{ + permissions: &fakePermissions{}, + }.Builder + tcf2ConfigBuilder := fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + }.Builder + endpoint := cookieSyncEndpoint{ config: &config.Configuration{ UserSync: test.givenConfig, AccountRequired: test.givenAccountRequired, }, privacyConfig: usersyncPrivacyConfig{ - gdprConfig: test.givenGDPRConfig, - ccpaEnforce: test.givenCCPAEnabled, + gdprConfig: test.givenGDPRConfig, + gdprPermissionsBuilder: gdprPermsBuilder, + tcf2ConfigBuilder: tcf2ConfigBuilder, + ccpaEnforce: test.givenCCPAEnabled, }, accountsFetcher: FakeAccountsFetcher{AccountData: map[string]json.RawMessage{ "TestAccount": json.RawMessage(`{"cookie_sync": {"default_limit": 20, "max_limit": 30, "default_coop_sync": true}}`), @@ -787,122 +829,226 @@ func TestCookieSyncParseRequest(t *testing.T) { } func TestSetLimit(t *testing.T) { - coopSync := true + intNegative1 := -1 + int20 := 20 + int30 := 30 + int40 := 40 testCases := []struct { description string givenRequest cookieSyncRequest - givenConfig config.UserSync - expectedError string + givenAccount *config.Account expectedRequest cookieSyncRequest }{ { - description: "Test that Max Limit and Default Coop Sync are Applied", + description: "Default Limit is Applied (request limit = 0)", givenRequest: cookieSyncRequest{ - Account: "TestAccount", - Limit: 42, + Limit: 0, }, - givenConfig: config.UserSync{ - Cooperative: config.UserSyncCooperative{ - PriorityGroups: [][]string{{"a", "b", "c"}}, + givenAccount: &config.Account{ + CookieSync: config.CookieSync{ + DefaultLimit: &int20, }, }, expectedRequest: cookieSyncRequest{ - Account: "TestAccount", - Limit: 30, - CooperativeSync: &coopSync, + Limit: 20, }, - expectedError: "", }, { - description: "Test that Default Limit is Applied", + description: "Default Limit is Not Applied (default limit not set)", givenRequest: cookieSyncRequest{ - Account: "TestAccount", + Limit: 0, }, - givenConfig: config.UserSync{ - Cooperative: config.UserSyncCooperative{ - PriorityGroups: [][]string{{"a", "b", "c"}}, + givenAccount: &config.Account{ + CookieSync: config.CookieSync{ + DefaultLimit: nil, }, }, expectedRequest: cookieSyncRequest{ - Account: "TestAccount", - Limit: 20, - CooperativeSync: &coopSync, + Limit: 0, }, - expectedError: "", }, { - description: "Test that max limit is applied when given limit is zero", + description: "Default Limit is Not Applied (request limit > 0)", givenRequest: cookieSyncRequest{ - Account: "ZeroLimitAccount", + Limit: 10, }, - givenConfig: config.UserSync{ - Cooperative: config.UserSyncCooperative{ - PriorityGroups: [][]string{{"a", "b", "c"}}, + givenAccount: &config.Account{ + CookieSync: config.CookieSync{ + DefaultLimit: &int20, }, }, expectedRequest: cookieSyncRequest{ - Account: "ZeroLimitAccount", - Limit: 30, - CooperativeSync: &coopSync, + Limit: 10, }, - expectedError: "", }, { - description: "Negative Value Check", + description: "Max Limit is Applied (request limit <= 0)", givenRequest: cookieSyncRequest{ - Account: "NegativeLimitAccount", + Limit: 0, }, - givenConfig: config.UserSync{ - Cooperative: config.UserSyncCooperative{ - PriorityGroups: [][]string{{"a", "b", "c"}}, + givenAccount: &config.Account{ + CookieSync: config.CookieSync{ + MaxLimit: &int30, }, }, expectedRequest: cookieSyncRequest{ - Account: "NegativeLimitAccount", - Limit: 0, - CooperativeSync: &coopSync, + Limit: 30, }, - expectedError: "", }, { - description: "Error Handler", + description: "Max Limit is Applied (0 < max < limit)", givenRequest: cookieSyncRequest{ - Account: "DisabledAccount", + Limit: 40, }, - givenConfig: config.UserSync{ - Cooperative: config.UserSyncCooperative{ - PriorityGroups: [][]string{{"a", "b", "c"}}, + givenAccount: &config.Account{ + CookieSync: config.CookieSync{ + MaxLimit: &int30, + }, + }, + expectedRequest: cookieSyncRequest{ + Limit: 30, + }, + }, + { + description: "Max Limit is Not Applied (max not set)", + givenRequest: cookieSyncRequest{ + Limit: 10, + }, + givenAccount: &config.Account{ + CookieSync: config.CookieSync{ + MaxLimit: nil, + }, + }, + expectedRequest: cookieSyncRequest{ + Limit: 10, + }, + }, + { + description: "Max Limit is Not Applied (0 < limit < max)", + givenRequest: cookieSyncRequest{ + Limit: 10, + }, + givenAccount: &config.Account{ + CookieSync: config.CookieSync{ + MaxLimit: &int30, + }, + }, + expectedRequest: cookieSyncRequest{ + Limit: 10, + }, + }, + { + description: "Max Limit is Applied After applying the default", + givenRequest: cookieSyncRequest{ + Limit: 0, + }, + givenAccount: &config.Account{ + CookieSync: config.CookieSync{ + DefaultLimit: &int40, + MaxLimit: &int30, + }, + }, + expectedRequest: cookieSyncRequest{ + Limit: 30, + }, + }, + { + description: "Negative Value Check", + givenRequest: cookieSyncRequest{ + Limit: 0, + }, + givenAccount: &config.Account{ + CookieSync: config.CookieSync{ + DefaultLimit: &intNegative1, + MaxLimit: &intNegative1, }, }, expectedRequest: cookieSyncRequest{ - Account: "DisabledAccount", + Limit: 0, }, - expectedError: errCookieSyncAccountBlocked.Error(), }, } for _, test := range testCases { - endpoint := cookieSyncEndpoint{ - config: &config.Configuration{ - UserSync: test.givenConfig, + endpoint := cookieSyncEndpoint{} + request := endpoint.setLimit(test.givenRequest, test.givenAccount.CookieSync) + assert.Equal(t, test.expectedRequest, request, test.description) + } +} + +func TestSetCooperativeSync(t *testing.T) { + coopSyncFalse := false + coopSyncTrue := true + + testCases := []struct { + description string + givenRequest cookieSyncRequest + givenAccount *config.Account + expectedRequest cookieSyncRequest + }{ + { + description: "Request coop sync unmodified - request sync nil & default sync nil", + givenRequest: cookieSyncRequest{ + CooperativeSync: nil, }, - accountsFetcher: FakeAccountsFetcher{AccountData: map[string]json.RawMessage{ - "TestAccount": json.RawMessage(`{"cookie_sync": {"default_limit": 20, "max_limit": 30, "default_coop_sync": true}}`), - "DisabledAccount": json.RawMessage(`{"disabled":true}`), - "ZeroLimitAccount": json.RawMessage(`{"cookie_sync": {"default_limit": 0, "max_limit": 30, "default_coop_sync": true}}`), - "NegativeLimitAccount": json.RawMessage(`{"cookie_sync": {"default_limit": -1, "max_limit": -1, "default_coop_sync": true}}`), - }}, - } - assert.NoError(t, endpoint.config.MarshalAccountDefaults()) - request, err := endpoint.setLimit(test.givenRequest) + givenAccount: &config.Account{ + CookieSync: config.CookieSync{ + DefaultCoopSync: nil, + }, + }, + expectedRequest: cookieSyncRequest{ + CooperativeSync: nil, + }, + }, + { + description: "Request coop sync set to default - request sync nil & default sync not nil", + givenRequest: cookieSyncRequest{ + CooperativeSync: nil, + }, + givenAccount: &config.Account{ + CookieSync: config.CookieSync{ + DefaultCoopSync: &coopSyncTrue, + }, + }, + expectedRequest: cookieSyncRequest{ + CooperativeSync: &coopSyncTrue, + }, + }, + { + description: "Request coop sync unmodified - request sync not nil & default sync nil", + givenRequest: cookieSyncRequest{ + CooperativeSync: &coopSyncTrue, + }, + givenAccount: &config.Account{ + CookieSync: config.CookieSync{ + DefaultCoopSync: nil, + }, + }, + expectedRequest: cookieSyncRequest{ + CooperativeSync: &coopSyncTrue, + }, + }, + { + description: "Request coop sync unmodified - request sync not nil & default sync not nil", + givenRequest: cookieSyncRequest{ + CooperativeSync: &coopSyncFalse, + }, + givenAccount: &config.Account{ + CookieSync: config.CookieSync{ + DefaultCoopSync: &coopSyncTrue, + }, + }, + expectedRequest: cookieSyncRequest{ + CooperativeSync: &coopSyncFalse, + }, + }, + } - if test.expectedError == "" { - assert.NoError(t, err, test.description) - assert.Equal(t, test.expectedRequest, request, test.description) - } else { - assert.EqualError(t, err, test.expectedError, test.description) - } + for _, test := range testCases { + endpoint := cookieSyncEndpoint{} + request := endpoint.setCooperativeSync(test.givenRequest, test.givenAccount.CookieSync) + assert.Equal(t, test.expectedRequest, request, test.description) } } @@ -945,6 +1091,13 @@ func TestCookieSyncWriteParseRequestErrorMetrics(t *testing.T) { m.On("RecordCookieSync", metrics.CookieSyncAccountInvalid).Once() }, }, + { + description: "Account Malformed", + err: errCookieSyncAccountConfigMalformed, + setExpectations: func(m *metrics.MetricsEngineMock) { + m.On("RecordCookieSync", metrics.CookieSyncAccountConfigMalformed).Once() + }, + }, { description: "No Special Case", err: errors.New("any error"), @@ -1438,12 +1591,10 @@ func TestUsersyncPrivacyGDPRAllowsHostCookie(t *testing.T) { for _, test := range testCases { mockPerms := MockGDPRPerms{} - mockPerms.On("HostCookiesAllowed", mock.Anything, gdpr.SignalYes, "anyConsent").Return(test.givenResponse, test.givenError) + mockPerms.On("HostCookiesAllowed", mock.Anything).Return(test.givenResponse, test.givenError) privacy := usersyncPrivacy{ gdprPermissions: &mockPerms, - gdprSignal: gdpr.SignalYes, - gdprConsent: "anyConsent", } result := privacy.GDPRAllowsHostCookie() @@ -1486,12 +1637,10 @@ func TestUsersyncPrivacyGDPRAllowsBidderSync(t *testing.T) { for _, test := range testCases { mockPerms := MockGDPRPerms{} - mockPerms.On("BidderSyncAllowed", mock.Anything, openrtb_ext.BidderName("foo"), gdpr.SignalYes, "anyConsent").Return(test.givenResponse, test.givenError) + mockPerms.On("BidderSyncAllowed", mock.Anything, openrtb_ext.BidderName("foo")).Return(test.givenResponse, test.givenError) privacy := usersyncPrivacy{ gdprPermissions: &mockPerms, - gdprSignal: gdpr.SignalYes, - gdprConsent: "anyConsent", } result := privacy.GDPRAllowsBidderSync("foo") @@ -1565,9 +1714,14 @@ func TestCombineErrors(t *testing.T) { givenErrorList: []error{&errortypes.AcctRequired{}}, expectedError: errCookieSyncAccountInvalid, }, + { + description: "Special Case: malformed account config", + givenErrorList: []error{&errortypes.MalformedAcct{}}, + expectedError: errCookieSyncAccountConfigMalformed, + }, { description: "Special Case: multiple special cases, first one wins", - givenErrorList: []error{&errortypes.BlacklistedAcct{}, &errortypes.AcctRequired{}}, + givenErrorList: []error{&errortypes.BlacklistedAcct{}, &errortypes.AcctRequired{}, &errortypes.MalformedAcct{}}, expectedError: errCookieSyncAccountBlocked, }, } @@ -1642,18 +1796,18 @@ 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) +func (m *MockGDPRPerms) HostCookiesAllowed(ctx context.Context) (bool, error) { + args := m.Called(ctx) 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) +func (m *MockGDPRPerms) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName) (bool, error) { + args := m.Called(ctx, bidder) return args.Bool(0), args.Error(1) } -func (m *MockGDPRPerms) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string, aliasGVLIDs map[string]uint16) (permissions gdpr.AuctionPermissions, err error) { - args := m.Called(ctx, bidderCoreName, bidder, PublisherID, gdprSignal, consent, aliasGVLIDs) +func (m *MockGDPRPerms) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions gdpr.AuctionPermissions, err error) { + args := m.Called(ctx, bidderCoreName, bidder) return args.Get(0).(gdpr.AuctionPermissions), args.Error(1) } @@ -1662,22 +1816,30 @@ type FakeAccountsFetcher struct { } func (f FakeAccountsFetcher) FetchAccount(ctx context.Context, accountID string) (json.RawMessage, []error) { + defaultAccountJSON := json.RawMessage(`{"disabled":false}`) + + if accountID == metrics.PublisherUnknown { + return defaultAccountJSON, nil + } if account, ok := f.AccountData[accountID]; ok { return account, nil } return nil, []error{errors.New("Account not found")} } -// 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} +type fakePermissions struct { +} + +func (p *fakePermissions) HostCookiesAllowed(ctx context.Context) (bool, error) { + return true, nil } -type errReader struct { - err error +func (p *fakePermissions) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName) (bool, error) { + return true, nil } -func (r *errReader) Read(p []byte) (int, error) { - return 0, r.err +func (p *fakePermissions) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions gdpr.AuctionPermissions, err error) { + return gdpr.AuctionPermissions{ + AllowBidRequest: true, + }, nil } diff --git a/endpoints/events/account_test.go b/endpoints/events/account_test.go index b61f29dc5c9..592dd059d40 100644 --- a/endpoints/events/account_test.go +++ b/endpoints/events/account_test.go @@ -3,7 +3,7 @@ package events import ( "errors" "fmt" - "io/ioutil" + "io" "net/http" "net/http/httptest" "strings" @@ -11,15 +11,17 @@ import ( "github.com/julienschmidt/httprouter" "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/stored_requests" "github.com/stretchr/testify/assert" ) func TestHandleAccountServiceErrors(t *testing.T) { tests := map[string]struct { - fetcher *mockAccountsFetcher - cfg *config.Configuration - want struct { + fetcher *mockAccountsFetcher + cfg *config.Configuration + accountID string + want struct { code int response string } @@ -37,6 +39,7 @@ func TestHandleAccountServiceErrors(t *testing.T) { TimeoutMS: int64(2000), AllowUnknownBidder: false, }, }, + accountID: "testacc", want: struct { code int response string @@ -45,6 +48,26 @@ func TestHandleAccountServiceErrors(t *testing.T) { response: "Invalid request: some error\nInvalid request: Prebid-server could not verify the Account ID. Please reach out to the prebid server host.\n", }, }, + "malformedAccountConfig": { + fetcher: &mockAccountsFetcher{ + Fail: true, + Error: &errortypes.MalformedAcct{}, + }, + cfg: &config.Configuration{ + MaxRequestSize: maxSize, + VTrack: config.VTrack{ + TimeoutMS: int64(2000), AllowUnknownBidder: false, + }, + }, + accountID: "malformed_acct", + want: struct { + code int + response string + }{ + code: 500, + response: "Invalid request: The prebid-server account config for account id \"malformed_acct\" is malformed. Please reach out to the prebid server host.\n", + }, + }, "serviceUnavailable": { fetcher: &mockAccountsFetcher{ Fail: false, @@ -56,6 +79,7 @@ func TestHandleAccountServiceErrors(t *testing.T) { TimeoutMS: int64(2000), AllowUnknownBidder: false, }, }, + accountID: "testacc", want: struct { code int response string @@ -81,6 +105,7 @@ func TestHandleAccountServiceErrors(t *testing.T) { AllowUnknownBidder: false, }, }, + accountID: "testacc", want: struct { code int response string @@ -98,8 +123,8 @@ func TestHandleAccountServiceErrors(t *testing.T) { h httprouter.Handle r *http.Request }{ - vast(t, test.cfg, test.fetcher), - event(test.cfg, test.fetcher), + vast(t, test.cfg, test.fetcher, test.accountID), + event(test.cfg, test.fetcher, test.accountID), } for _, handler := range handlers { @@ -110,7 +135,7 @@ func TestHandleAccountServiceErrors(t *testing.T) { // execute handler.h(recorder, handler.r, nil) - d, err := ioutil.ReadAll(recorder.Result().Body) + d, err := io.ReadAll(recorder.Result().Body) if err != nil { t.Fatal(err) } @@ -123,7 +148,7 @@ func TestHandleAccountServiceErrors(t *testing.T) { } } -func event(cfg *config.Configuration, fetcher stored_requests.AccountFetcher) struct { +func event(cfg *config.Configuration, fetcher stored_requests.AccountFetcher, accountID string) struct { name string h httprouter.Handle r *http.Request @@ -135,11 +160,11 @@ func event(cfg *config.Configuration, fetcher stored_requests.AccountFetcher) st }{ name: "event", h: NewEventEndpoint(cfg, fetcher, nil), - r: httptest.NewRequest("GET", "/event?t=win&b=test&ts=1234&f=b&x=1&a=testacc", strings.NewReader("")), + r: httptest.NewRequest("GET", "/event?t=win&b=test&ts=1234&f=b&x=1&a="+accountID, strings.NewReader("")), } } -func vast(t *testing.T, cfg *config.Configuration, fetcher stored_requests.AccountFetcher) struct { +func vast(t *testing.T, cfg *config.Configuration, fetcher stored_requests.AccountFetcher, accountID string) struct { name string h httprouter.Handle r *http.Request @@ -156,6 +181,6 @@ func vast(t *testing.T, cfg *config.Configuration, fetcher stored_requests.Accou }{ name: "vast", h: NewVTrackEndpoint(cfg, fetcher, &vtrackMockCacheClient{}, config.BidderInfos{}), - r: httptest.NewRequest("POST", "/vtrack?a=testacc", strings.NewReader(vtrackBody)), + r: httptest.NewRequest("POST", "/vtrack?a="+accountID, strings.NewReader(vtrackBody)), } } diff --git a/endpoints/events/event.go b/endpoints/events/event.go index 8c9c252a8a0..6e7561ff632 100644 --- a/endpoints/events/event.go +++ b/endpoints/events/event.go @@ -206,7 +206,9 @@ func HandleAccountServiceErrors(errs []error) (status int, messages []string) { if errCode == errortypes.BlacklistedAppErrorCode || errCode == errortypes.BlacklistedAcctErrorCode { status = http.StatusServiceUnavailable } - + if errCode == errortypes.MalformedAcctErrorCode { + status = http.StatusInternalServerError + } if errCode == errortypes.TimeoutErrorCode && status == http.StatusBadRequest { status = http.StatusGatewayTimeout } diff --git a/endpoints/events/event_test.go b/endpoints/events/event_test.go index f700c31ef02..f172c32a7e5 100644 --- a/endpoints/events/event_test.go +++ b/endpoints/events/event_test.go @@ -5,7 +5,7 @@ import ( "encoding/base64" "encoding/json" "errors" - "io/ioutil" + "io" "net/http" "net/http/httptest" "strings" @@ -74,6 +74,7 @@ func (e *eventsMockAnalyticsModule) LogNotificationEventObject(ne *analytics.Not var mockAccountData = map[string]json.RawMessage{ "events_enabled": json.RawMessage(`{"events_enabled":true}`), "events_disabled": json.RawMessage(`{"events_enabled":false}`), + "malformed_acct": json.RawMessage(`{"events_enabled":"invalid type"}`), } type mockAccountsFetcher struct { @@ -131,7 +132,7 @@ func TestShouldReturnBadRequestWhenTypeIsMissing(t *testing.T) { // execute e(recorder, req, nil) - d, err := ioutil.ReadAll(recorder.Result().Body) + d, err := io.ReadAll(recorder.Result().Body) if err != nil { t.Fatal(err) } @@ -167,7 +168,7 @@ func TestShouldReturnBadRequestWhenTypeIsInvalid(t *testing.T) { // execute e(recorder, req, nil) - d, err := ioutil.ReadAll(recorder.Result().Body) + d, err := io.ReadAll(recorder.Result().Body) if err != nil { t.Fatal(err) } @@ -203,7 +204,7 @@ func TestShouldReturnBadRequestWhenBidIdIsMissing(t *testing.T) { // execute e(recorder, req, nil) - d, err := ioutil.ReadAll(recorder.Result().Body) + d, err := io.ReadAll(recorder.Result().Body) if err != nil { t.Fatal(err) } @@ -239,7 +240,7 @@ func TestShouldReturnBadRequestWhenTimestampIsInvalid(t *testing.T) { // execute e(recorder, req, nil) - d, err := ioutil.ReadAll(recorder.Result().Body) + d, err := io.ReadAll(recorder.Result().Body) if err != nil { t.Fatal(err) } @@ -275,7 +276,7 @@ func TestShouldReturnUnauthorizedWhenAccountIsMissing(t *testing.T) { // execute e(recorder, req, nil) - d, err := ioutil.ReadAll(recorder.Result().Body) + d, err := io.ReadAll(recorder.Result().Body) if err != nil { t.Fatal(err) } @@ -311,7 +312,7 @@ func TestShouldReturnBadRequestWhenFormatValueIsInvalid(t *testing.T) { // execute e(recorder, req, nil) - d, err := ioutil.ReadAll(recorder.Result().Body) + d, err := io.ReadAll(recorder.Result().Body) if err != nil { t.Fatal(err) } @@ -347,7 +348,7 @@ func TestShouldReturnBadRequestWhenAnalyticsValueIsInvalid(t *testing.T) { // execute e(recorder, req, nil) - d, err := ioutil.ReadAll(recorder.Result().Body) + d, err := io.ReadAll(recorder.Result().Body) if err != nil { t.Fatal(err) } @@ -385,7 +386,7 @@ func TestShouldNotPassEventToAnalyticsReporterWhenAccountNotFound(t *testing.T) // execute e(recorder, req, nil) - d, err := ioutil.ReadAll(recorder.Result().Body) + d, err := io.ReadAll(recorder.Result().Body) if err != nil { t.Fatal(err) } @@ -420,7 +421,7 @@ func TestShouldReturnBadRequestWhenIntegrationValueIsInvalid(t *testing.T) { // execute e(recorder, req, nil) - d, err := ioutil.ReadAll(recorder.Result().Body) + d, err := io.ReadAll(recorder.Result().Body) if err != nil { t.Fatal(err) } @@ -458,7 +459,7 @@ func TestShouldNotPassEventToAnalyticsReporterWhenAccountEventNotEnabled(t *test // execute e(recorder, req, nil) - d, err := ioutil.ReadAll(recorder.Result().Body) + d, err := io.ReadAll(recorder.Result().Body) if err != nil { t.Fatal(err) } @@ -565,7 +566,7 @@ func TestShouldRespondWithPixelAndContentTypeWhenRequestFormatIsImage(t *testing // execute e(recorder, req, nil) - d, err := ioutil.ReadAll(recorder.Result().Body) + d, err := io.ReadAll(recorder.Result().Body) if err != nil { t.Fatal(err) } @@ -606,7 +607,7 @@ func TestShouldRespondWithNoContentWhenRequestFormatIsNotDefined(t *testing.T) { // execute e(recorder, req, nil) - d, err := ioutil.ReadAll(recorder.Result().Body) + d, err := io.ReadAll(recorder.Result().Body) if err != nil { t.Fatal(err) } @@ -808,7 +809,7 @@ func TestShouldReturnBadRequestWhenVTypeIsInvalid(t *testing.T) { e := NewEventEndpoint(cfg, mockAccountsFetcher, mockAnalyticsModule) e(recorder, test.req, nil) - d, err := ioutil.ReadAll(recorder.Result().Body) + d, err := io.ReadAll(recorder.Result().Body) if err != nil { t.Fatal(err) } diff --git a/endpoints/events/vtrack.go b/endpoints/events/vtrack.go index 5e6a90249d1..1ec621fa9f0 100644 --- a/endpoints/events/vtrack.go +++ b/endpoints/events/vtrack.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "net/http" "strings" "time" @@ -164,7 +163,7 @@ func ParseVTrackRequest(httpRequest *http.Request, maxRequestSize int64) (req *B } defer httpRequest.Body.Close() - requestJson, err := ioutil.ReadAll(lr) + requestJson, err := io.ReadAll(lr) if err != nil { return req, err } @@ -267,7 +266,7 @@ func isAllowVastForBidder(bidder string, bidderInfos *config.BidderInfos, allowU // check if bidder is configured if b, ok := (*bidderInfos)[bidder]; bidderInfos != nil && ok { // check if bidder is enabled - return b.Enabled && b.ModifyingVastXmlAllowed + return b.IsEnabled() && b.ModifyingVastXmlAllowed } return allowUnknownBidder diff --git a/endpoints/events/vtrack_test.go b/endpoints/events/vtrack_test.go index d8905d7b443..2c40c9b41de 100644 --- a/endpoints/events/vtrack_test.go +++ b/endpoints/events/vtrack_test.go @@ -6,7 +6,7 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" + "io" "net/http" "net/http/httptest" "strings" @@ -73,7 +73,7 @@ func TestShouldRespondWithBadRequestWhenAccountParameterIsMissing(t *testing.T) // execute e.Handle(recorder, req, nil) - d, err := ioutil.ReadAll(recorder.Result().Body) + d, err := io.ReadAll(recorder.Result().Body) if err != nil { t.Fatal(err) } @@ -114,7 +114,7 @@ func TestShouldRespondWithBadRequestWhenRequestBodyIsEmpty(t *testing.T) { // execute e.Handle(recorder, req, nil) - d, err := ioutil.ReadAll(recorder.Result().Body) + d, err := io.ReadAll(recorder.Result().Body) if err != nil { t.Fatal(err) } @@ -199,7 +199,7 @@ func TestShouldRespondWithBadRequestWhenBidIdIsMissing(t *testing.T) { // execute e.Handle(recorder, req, nil) - d, err := ioutil.ReadAll(recorder.Result().Body) + d, err := io.ReadAll(recorder.Result().Body) if err != nil { t.Fatal(err) } @@ -251,7 +251,7 @@ func TestShouldRespondWithBadRequestWhenBidderIsMissing(t *testing.T) { // execute e.Handle(recorder, req, nil) - d, err := ioutil.ReadAll(recorder.Result().Body) + d, err := io.ReadAll(recorder.Result().Body) if err != nil { t.Fatal(err) } @@ -300,7 +300,7 @@ func TestShouldRespondWithInternalServerErrorWhenPbsCacheClientFails(t *testing. // execute e.Handle(recorder, req, nil) - d, err := ioutil.ReadAll(recorder.Result().Body) + d, err := io.ReadAll(recorder.Result().Body) if err != nil { t.Fatal(err) } @@ -378,11 +378,11 @@ func TestShouldSendToCacheExpectedPutsAndUpdatableBiddersWhenBidderVastNotAllowe // bidder info bidderInfos := make(config.BidderInfos) bidderInfos["bidder"] = config.BidderInfo{ - Enabled: true, + Disabled: false, ModifyingVastXmlAllowed: false, } bidderInfos["updatable_bidder"] = config.BidderInfo{ - Enabled: true, + Disabled: false, ModifyingVastXmlAllowed: true, } @@ -406,7 +406,7 @@ func TestShouldSendToCacheExpectedPutsAndUpdatableBiddersWhenBidderVastNotAllowe // execute e.Handle(recorder, req, nil) - d, err := ioutil.ReadAll(recorder.Result().Body) + d, err := io.ReadAll(recorder.Result().Body) if err != nil { t.Fatal(err) } @@ -441,11 +441,11 @@ func TestShouldSendToCacheExpectedPutsAndUpdatableBiddersWhenBidderVastAllowed(t // bidder info bidderInfos := make(config.BidderInfos) bidderInfos["bidder"] = config.BidderInfo{ - Enabled: true, + Disabled: false, ModifyingVastXmlAllowed: true, } bidderInfos["updatable_bidder"] = config.BidderInfo{ - Enabled: true, + Disabled: false, ModifyingVastXmlAllowed: true, } @@ -469,7 +469,7 @@ func TestShouldSendToCacheExpectedPutsAndUpdatableBiddersWhenBidderVastAllowed(t // execute e.Handle(recorder, req, nil) - d, err := ioutil.ReadAll(recorder.Result().Body) + d, err := io.ReadAll(recorder.Result().Body) if err != nil { t.Fatal(err) } @@ -524,7 +524,7 @@ func TestShouldSendToCacheExpectedPutsAndUpdatableUnknownBiddersWhenUnknownBidde // execute e.Handle(recorder, req, nil) - d, err := ioutil.ReadAll(recorder.Result().Body) + d, err := io.ReadAll(recorder.Result().Body) if err != nil { t.Fatal(err) } @@ -580,7 +580,7 @@ func TestShouldReturnBadRequestWhenRequestExceedsMaxRequestSize(t *testing.T) { // execute e.Handle(recorder, req, nil) - d, err := ioutil.ReadAll(recorder.Result().Body) + d, err := io.ReadAll(recorder.Result().Body) if err != nil { t.Fatal(err) } @@ -624,7 +624,7 @@ func TestShouldRespondWithInternalErrorPbsCacheIsNotConfigured(t *testing.T) { // execute e.Handle(recorder, req, nil) - d, err := ioutil.ReadAll(recorder.Result().Body) + d, err := io.ReadAll(recorder.Result().Body) if err != nil { t.Fatal(err) } diff --git a/endpoints/info/bidders.go b/endpoints/info/bidders.go index 54a9104ec81..989f70b848f 100644 --- a/endpoints/info/bidders.go +++ b/endpoints/info/bidders.go @@ -79,13 +79,13 @@ func prepareBiddersResponseEnabledOnly(bidders config.BidderInfos, aliases map[s bidderNames := make([]string, 0, len(bidders)+len(aliases)) for name, info := range bidders { - if info.Enabled { + if info.IsEnabled() { bidderNames = append(bidderNames, name) } } for name, bidder := range aliases { - if info, ok := bidders[bidder]; ok && info.Enabled { + if info, ok := bidders[bidder]; ok && info.IsEnabled() { bidderNames = append(bidderNames, name) } } diff --git a/endpoints/info/bidders_detail.go b/endpoints/info/bidders_detail.go index 04bc9b04fca..1446e3ac22a 100644 --- a/endpoints/info/bidders_detail.go +++ b/endpoints/info/bidders_detail.go @@ -18,8 +18,8 @@ const ( ) // NewBiddersDetailEndpoint builds a handler for the /info/bidders/<bidder> endpoint. -func NewBiddersDetailEndpoint(bidders config.BidderInfos, biddersConfig map[string]config.Adapter, aliases map[string]string) httprouter.Handle { - responses, err := prepareBiddersDetailResponse(bidders, biddersConfig, aliases) +func NewBiddersDetailEndpoint(bidders config.BidderInfos, aliases map[string]string) httprouter.Handle { + responses, err := prepareBiddersDetailResponse(bidders, aliases) if err != nil { glog.Fatalf("error creating /info/bidders/<bidder> endpoint response: %v", err) } @@ -38,8 +38,8 @@ func NewBiddersDetailEndpoint(bidders config.BidderInfos, biddersConfig map[stri } } -func prepareBiddersDetailResponse(bidders config.BidderInfos, biddersConfig map[string]config.Adapter, aliases map[string]string) (map[string][]byte, error) { - details, err := mapDetails(bidders, biddersConfig, aliases) +func prepareBiddersDetailResponse(bidders config.BidderInfos, aliases map[string]string) (map[string][]byte, error) { + details, err := mapDetails(bidders, aliases) if err != nil { return nil, err } @@ -58,12 +58,11 @@ func prepareBiddersDetailResponse(bidders config.BidderInfos, biddersConfig map[ return responses, nil } -func mapDetails(bidders config.BidderInfos, biddersConfig map[string]config.Adapter, aliases map[string]string) (map[string]bidderDetail, error) { +func mapDetails(bidders config.BidderInfos, aliases map[string]string) (map[string]bidderDetail, error) { details := map[string]bidderDetail{} for bidderName, bidderInfo := range bidders { - endpoint := resolveEndpoint(bidderName, biddersConfig) - details[bidderName] = mapDetailFromConfig(bidderInfo, endpoint) + details[bidderName] = mapDetailFromConfig(bidderInfo) } for aliasName, bidderName := range aliases { @@ -80,14 +79,6 @@ func mapDetails(bidders config.BidderInfos, biddersConfig map[string]config.Adap return details, nil } -func resolveEndpoint(bidder string, biddersConfig map[string]config.Adapter) string { - if c, found := biddersConfig[bidder]; found { - return c.Endpoint - } - - return "" -} - func marshalDetailsResponse(details map[string]bidderDetail) (map[string][]byte, error) { responses := map[string][]byte{} @@ -137,7 +128,7 @@ type platform struct { MediaTypes []string `json:"mediaTypes"` } -func mapDetailFromConfig(c config.BidderInfo, endpoint string) bidderDetail { +func mapDetailFromConfig(c config.BidderInfo) bidderDetail { var bidderDetail bidderDetail if c.Maintainer != nil { @@ -146,10 +137,10 @@ func mapDetailFromConfig(c config.BidderInfo, endpoint string) bidderDetail { } } - if c.Enabled { + if c.IsEnabled() { bidderDetail.Status = statusActive - usesHTTPS := strings.HasPrefix(strings.ToLower(endpoint), "https://") + usesHTTPS := strings.HasPrefix(strings.ToLower(c.Endpoint), "https://") bidderDetail.UsesHTTPS = &usesHTTPS if c.Capabilities != nil { diff --git a/endpoints/info/bidders_detail_test.go b/endpoints/info/bidders_detail_test.go index d8b6f2bf4ad..47e5a0688a8 100644 --- a/endpoints/info/bidders_detail_test.go +++ b/endpoints/info/bidders_detail_test.go @@ -2,7 +2,7 @@ package info import ( "bytes" - "io/ioutil" + "io" "net/http" "net/http/httptest" "testing" @@ -14,12 +14,10 @@ import ( ) func TestPrepareBiddersDetailResponse(t *testing.T) { - bidderAInfo := config.BidderInfo{Enabled: true, Maintainer: &config.MaintainerInfo{Email: "bidderA"}} - bidderAConfig := config.Adapter{Endpoint: "https://secureEndpoint.com"} + bidderAInfo := config.BidderInfo{Endpoint: "https://secureEndpoint.com", Disabled: false, Maintainer: &config.MaintainerInfo{Email: "bidderA"}} bidderAResponse := []byte(`{"status":"ACTIVE","usesHttps":true,"maintainer":{"email":"bidderA"}}`) - bidderBInfo := config.BidderInfo{Enabled: true, Maintainer: &config.MaintainerInfo{Email: "bidderB"}} - bidderBConfig := config.Adapter{Endpoint: "http://unsecureEndpoint.com"} + bidderBInfo := config.BidderInfo{Endpoint: "http://unsecureEndpoint.com", Disabled: false, Maintainer: &config.MaintainerInfo{Email: "bidderB"}} bidderBResponse := []byte(`{"status":"ACTIVE","usesHttps":false,"maintainer":{"email":"bidderB"}}`) allResponseBidderA := bytes.Buffer{} @@ -35,45 +33,40 @@ func TestPrepareBiddersDetailResponse(t *testing.T) { allResponseBidderAB.WriteString(`}`) var testCases = []struct { - description string - givenBidders config.BidderInfos - givenBiddersConfig map[string]config.Adapter - givenAliases map[string]string - expectedResponses map[string][]byte - expectedError string + description string + givenBidders config.BidderInfos + givenAliases map[string]string + expectedResponses map[string][]byte + expectedError string }{ { - description: "None", - givenBidders: config.BidderInfos{}, - givenBiddersConfig: map[string]config.Adapter{}, - givenAliases: map[string]string{}, - expectedResponses: map[string][]byte{"all": []byte(`{}`)}, + description: "None", + givenBidders: config.BidderInfos{}, + givenAliases: map[string]string{}, + expectedResponses: map[string][]byte{"all": []byte(`{}`)}, }, { - description: "One", - givenBidders: config.BidderInfos{"a": bidderAInfo}, - givenBiddersConfig: map[string]config.Adapter{"a": bidderAConfig}, - givenAliases: map[string]string{}, - expectedResponses: map[string][]byte{"a": bidderAResponse, "all": allResponseBidderA.Bytes()}, + description: "One", + givenBidders: config.BidderInfos{"a": bidderAInfo}, + givenAliases: map[string]string{}, + expectedResponses: map[string][]byte{"a": bidderAResponse, "all": allResponseBidderA.Bytes()}, }, { - description: "Many", - givenBidders: config.BidderInfos{"a": bidderAInfo, "b": bidderBInfo}, - givenBiddersConfig: map[string]config.Adapter{"a": bidderAConfig, "b": bidderBConfig}, - givenAliases: map[string]string{}, - expectedResponses: map[string][]byte{"a": bidderAResponse, "b": bidderBResponse, "all": allResponseBidderAB.Bytes()}, + description: "Many", + givenBidders: config.BidderInfos{"a": bidderAInfo, "b": bidderBInfo}, + givenAliases: map[string]string{}, + expectedResponses: map[string][]byte{"a": bidderAResponse, "b": bidderBResponse, "all": allResponseBidderAB.Bytes()}, }, { - description: "Error - Map Details", // Returns error due to invalid alias. - givenBidders: config.BidderInfos{"a": bidderAInfo}, - givenBiddersConfig: map[string]config.Adapter{"a": bidderAConfig}, - givenAliases: map[string]string{"zAlias": "z"}, - expectedError: "base adapter z for alias zAlias not found", + description: "Error - Map Details", // Returns error due to invalid alias. + givenBidders: config.BidderInfos{"a": bidderAInfo}, + givenAliases: map[string]string{"zAlias": "z"}, + expectedError: "base adapter z for alias zAlias not found", }, } for _, test := range testCases { - responses, err := prepareBiddersDetailResponse(test.givenBidders, test.givenBiddersConfig, test.givenAliases) + responses, err := prepareBiddersDetailResponse(test.givenBidders, test.givenAliases) if test.expectedError == "" { assert.Equal(t, test.expectedResponses, responses, test.description+":responses") @@ -89,77 +82,67 @@ func TestMapDetails(t *testing.T) { trueValue := true falseValue := false - bidderAInfo := config.BidderInfo{Enabled: true, Maintainer: &config.MaintainerInfo{Email: "bidderA"}} - bidderAConfig := config.Adapter{Endpoint: "https://secureEndpoint.com"} + bidderAInfo := config.BidderInfo{Endpoint: "https://secureEndpoint.com", Disabled: false, Maintainer: &config.MaintainerInfo{Email: "bidderA"}} bidderADetail := bidderDetail{Status: "ACTIVE", UsesHTTPS: &trueValue, Maintainer: &maintainer{Email: "bidderA"}} aliasADetail := bidderDetail{Status: "ACTIVE", UsesHTTPS: &trueValue, Maintainer: &maintainer{Email: "bidderA"}, AliasOf: "a"} - bidderBInfo := config.BidderInfo{Enabled: true, Maintainer: &config.MaintainerInfo{Email: "bidderB"}} - bidderBConfig := config.Adapter{Endpoint: "http://unsecureEndpoint.com"} + bidderBInfo := config.BidderInfo{Endpoint: "http://unsecureEndpoint.com", Disabled: false, Maintainer: &config.MaintainerInfo{Email: "bidderB"}} bidderBDetail := bidderDetail{Status: "ACTIVE", UsesHTTPS: &falseValue, Maintainer: &maintainer{Email: "bidderB"}} aliasBDetail := bidderDetail{Status: "ACTIVE", UsesHTTPS: &falseValue, Maintainer: &maintainer{Email: "bidderB"}, AliasOf: "b"} var testCases = []struct { - description string - givenBidders config.BidderInfos - givenBiddersConfig map[string]config.Adapter - givenAliases map[string]string - expectedDetails map[string]bidderDetail - expectedError string + description string + givenBidders config.BidderInfos + givenAliases map[string]string + expectedDetails map[string]bidderDetail + expectedError string }{ { - description: "None", - givenBidders: config.BidderInfos{}, - givenBiddersConfig: map[string]config.Adapter{}, - givenAliases: map[string]string{}, - expectedDetails: map[string]bidderDetail{}, + description: "None", + givenBidders: config.BidderInfos{}, + givenAliases: map[string]string{}, + expectedDetails: map[string]bidderDetail{}, }, { - description: "One Core Bidder", - givenBidders: config.BidderInfos{"a": bidderAInfo}, - givenBiddersConfig: map[string]config.Adapter{"a": bidderAConfig}, - givenAliases: map[string]string{}, - expectedDetails: map[string]bidderDetail{"a": bidderADetail}, + description: "One Core Bidder", + givenBidders: config.BidderInfos{"a": bidderAInfo}, + givenAliases: map[string]string{}, + expectedDetails: map[string]bidderDetail{"a": bidderADetail}, }, { - description: "Many Core Bidders", - givenBidders: config.BidderInfos{"a": bidderAInfo, "b": bidderBInfo}, - givenBiddersConfig: map[string]config.Adapter{"a": bidderAConfig, "b": bidderBConfig}, - givenAliases: map[string]string{}, - expectedDetails: map[string]bidderDetail{"a": bidderADetail, "b": bidderBDetail}, + description: "Many Core Bidders", + givenBidders: config.BidderInfos{"a": bidderAInfo, "b": bidderBInfo}, + givenAliases: map[string]string{}, + expectedDetails: map[string]bidderDetail{"a": bidderADetail, "b": bidderBDetail}, }, { - description: "One Alias", - givenBidders: config.BidderInfos{"a": bidderAInfo}, - givenBiddersConfig: map[string]config.Adapter{"a": bidderAConfig}, - givenAliases: map[string]string{"aAlias": "a"}, - expectedDetails: map[string]bidderDetail{"a": bidderADetail, "aAlias": aliasADetail}, + description: "One Alias", + givenBidders: config.BidderInfos{"a": bidderAInfo}, + givenAliases: map[string]string{"aAlias": "a"}, + expectedDetails: map[string]bidderDetail{"a": bidderADetail, "aAlias": aliasADetail}, }, { - description: "Many Aliases - Same Core Bidder", - givenBidders: config.BidderInfos{"a": bidderAInfo}, - givenBiddersConfig: map[string]config.Adapter{"a": bidderAConfig}, - givenAliases: map[string]string{"aAlias1": "a", "aAlias2": "a"}, - expectedDetails: map[string]bidderDetail{"a": bidderADetail, "aAlias1": aliasADetail, "aAlias2": aliasADetail}, + description: "Many Aliases - Same Core Bidder", + givenBidders: config.BidderInfos{"a": bidderAInfo}, + givenAliases: map[string]string{"aAlias1": "a", "aAlias2": "a"}, + expectedDetails: map[string]bidderDetail{"a": bidderADetail, "aAlias1": aliasADetail, "aAlias2": aliasADetail}, }, { - description: "Many Aliases - Different Core Bidders", - givenBidders: config.BidderInfos{"a": bidderAInfo, "b": bidderBInfo}, - givenBiddersConfig: map[string]config.Adapter{"a": bidderAConfig, "b": bidderBConfig}, - givenAliases: map[string]string{"aAlias": "a", "bAlias": "b"}, - expectedDetails: map[string]bidderDetail{"a": bidderADetail, "b": bidderBDetail, "aAlias": aliasADetail, "bAlias": aliasBDetail}, + description: "Many Aliases - Different Core Bidders", + givenBidders: config.BidderInfos{"a": bidderAInfo, "b": bidderBInfo}, + givenAliases: map[string]string{"aAlias": "a", "bAlias": "b"}, + expectedDetails: map[string]bidderDetail{"a": bidderADetail, "b": bidderBDetail, "aAlias": aliasADetail, "bAlias": aliasBDetail}, }, { - description: "Error - Alias Without Core Bidder", - givenBidders: config.BidderInfos{"a": bidderAInfo}, - givenBiddersConfig: map[string]config.Adapter{"a": bidderAConfig}, - givenAliases: map[string]string{"zAlias": "z"}, - expectedError: "base adapter z for alias zAlias not found", + description: "Error - Alias Without Core Bidder", + givenBidders: config.BidderInfos{"a": bidderAInfo}, + givenAliases: map[string]string{"zAlias": "z"}, + expectedError: "base adapter z for alias zAlias not found", }, } for _, test := range testCases { - details, err := mapDetails(test.givenBidders, test.givenBiddersConfig, test.givenAliases) + details, err := mapDetails(test.givenBidders, test.givenAliases) if test.expectedError == "" { assert.Equal(t, test.expectedDetails, details, test.description+":details") @@ -171,33 +154,6 @@ func TestMapDetails(t *testing.T) { } } -func TestResolveEndpoint(t *testing.T) { - var testCases = []struct { - description string - givenBidder string - givenBiddersConfig map[string]config.Adapter - expectedEndpoint string - }{ - { - description: "Bidder Found - Uses Config Value", - givenBidder: "a", - givenBiddersConfig: map[string]config.Adapter{"a": {Endpoint: "anyEndpoint"}}, - expectedEndpoint: "anyEndpoint", - }, - { - description: "Bidder Not Found - Returns Empty", - givenBidder: "hasNoConfig", - givenBiddersConfig: map[string]config.Adapter{"a": {Endpoint: "anyEndpoint"}}, - expectedEndpoint: "", - }, - } - - for _, test := range testCases { - result := resolveEndpoint(test.givenBidder, test.givenBiddersConfig) - assert.Equal(t, test.expectedEndpoint, result, test.description) - } -} - func TestMarshalDetailsResponse(t *testing.T) { // Verifies omitempty is working correctly for bidderDetail, maintainer, capabilities, and aliasOf. bidderDetailA := bidderDetail{Status: "ACTIVE", Maintainer: &maintainer{Email: "bidderA"}} @@ -256,13 +212,13 @@ func TestMapDetailFromConfig(t *testing.T) { var testCases = []struct { description string givenBidderInfo config.BidderInfo - givenEndpoint string expected bidderDetail }{ { description: "Enabled - All Values Present", givenBidderInfo: config.BidderInfo{ - Enabled: true, + Endpoint: "http://anyEndpoint", + Disabled: false, Maintainer: &config.MaintainerInfo{ Email: "foo@bar.com", }, @@ -271,7 +227,6 @@ func TestMapDetailFromConfig(t *testing.T) { Site: &config.PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeVideo}}, }, }, - givenEndpoint: "http://amyEndpoint", expected: bidderDetail{ Status: "ACTIVE", UsesHTTPS: &falseValue, @@ -288,7 +243,8 @@ func TestMapDetailFromConfig(t *testing.T) { { description: "Disabled - All Values Present", givenBidderInfo: config.BidderInfo{ - Enabled: false, + Endpoint: "http://anyEndpoint", + Disabled: true, Maintainer: &config.MaintainerInfo{ Email: "foo@bar.com", }, @@ -297,7 +253,6 @@ func TestMapDetailFromConfig(t *testing.T) { Site: &config.PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeVideo}}, }, }, - givenEndpoint: "http://amyEndpoint", expected: bidderDetail{ Status: "DISABLED", UsesHTTPS: nil, @@ -311,9 +266,9 @@ func TestMapDetailFromConfig(t *testing.T) { { description: "Enabled - No Values Present", givenBidderInfo: config.BidderInfo{ - Enabled: true, + Endpoint: "http://amyEndpoint", + Disabled: false, }, - givenEndpoint: "http://amyEndpoint", expected: bidderDetail{ Status: "ACTIVE", UsesHTTPS: &falseValue, @@ -322,9 +277,9 @@ func TestMapDetailFromConfig(t *testing.T) { { description: "Enabled - Protocol - HTTP", givenBidderInfo: config.BidderInfo{ - Enabled: true, + Endpoint: "http://amyEndpoint", + Disabled: false, }, - givenEndpoint: "http://amyEndpoint", expected: bidderDetail{ Status: "ACTIVE", UsesHTTPS: &falseValue, @@ -333,9 +288,9 @@ func TestMapDetailFromConfig(t *testing.T) { { description: "Enabled - Protocol - HTTPS", givenBidderInfo: config.BidderInfo{ - Enabled: true, + Endpoint: "https://amyEndpoint", + Disabled: false, }, - givenEndpoint: "https://amyEndpoint", expected: bidderDetail{ Status: "ACTIVE", UsesHTTPS: &trueValue, @@ -344,9 +299,9 @@ func TestMapDetailFromConfig(t *testing.T) { { description: "Enabled - Protocol - HTTPS - Case Insensitive", givenBidderInfo: config.BidderInfo{ - Enabled: true, + Disabled: false, + Endpoint: "https://amyEndpoint", }, - givenEndpoint: "https://amyEndpoint", expected: bidderDetail{ Status: "ACTIVE", UsesHTTPS: &trueValue, @@ -355,9 +310,9 @@ func TestMapDetailFromConfig(t *testing.T) { { description: "Enabled - Protocol - Unknown", givenBidderInfo: config.BidderInfo{ - Enabled: true, + Endpoint: "endpointWithoutProtocol", + Disabled: false, }, - givenEndpoint: "endpointWithoutProtocol", expected: bidderDetail{ Status: "ACTIVE", UsesHTTPS: &falseValue, @@ -366,7 +321,7 @@ func TestMapDetailFromConfig(t *testing.T) { } for _, test := range testCases { - result := mapDetailFromConfig(test.givenBidderInfo, test.givenEndpoint) + result := mapDetailFromConfig(test.givenBidderInfo) assert.Equal(t, test.expected, result, test.description) } } @@ -406,13 +361,11 @@ func TestMapMediaTypes(t *testing.T) { } func TestBiddersDetailHandler(t *testing.T) { - bidderAInfo := config.BidderInfo{Enabled: true, Maintainer: &config.MaintainerInfo{Email: "bidderA"}} - bidderAConfig := config.Adapter{Endpoint: "https://secureEndpoint.com"} + bidderAInfo := config.BidderInfo{Endpoint: "https://secureEndpoint.com", Disabled: false, Maintainer: &config.MaintainerInfo{Email: "bidderA"}} bidderAResponse := []byte(`{"status":"ACTIVE","usesHttps":true,"maintainer":{"email":"bidderA"}}`) aliasAResponse := []byte(`{"status":"ACTIVE","usesHttps":true,"maintainer":{"email":"bidderA"},"aliasOf":"a"}`) - bidderBInfo := config.BidderInfo{Enabled: true, Maintainer: &config.MaintainerInfo{Email: "bidderB"}} - bidderBConfig := config.Adapter{Endpoint: "http://unsecureEndpoint.com"} + bidderBInfo := config.BidderInfo{Endpoint: "http://unsecureEndpoint.com", Disabled: false, Maintainer: &config.MaintainerInfo{Email: "bidderB"}} bidderBResponse := []byte(`{"status":"ACTIVE","usesHttps":false,"maintainer":{"email":"bidderB"}}`) allResponse := bytes.Buffer{} @@ -425,10 +378,9 @@ func TestBiddersDetailHandler(t *testing.T) { allResponse.WriteString(`}`) bidders := config.BidderInfos{"a": bidderAInfo, "b": bidderBInfo} - biddersConfig := map[string]config.Adapter{"a": bidderAConfig, "b": bidderBConfig} aliases := map[string]string{"aAlias": "a"} - handler := NewBiddersDetailEndpoint(bidders, biddersConfig, aliases) + handler := NewBiddersDetailEndpoint(bidders, aliases) var testCases = []struct { description string @@ -488,7 +440,7 @@ func TestBiddersDetailHandler(t *testing.T) { result := responseRecorder.Result() assert.Equal(t, result.StatusCode, test.expectedStatus, test.description+":statuscode") - resultBody, _ := ioutil.ReadAll(result.Body) + resultBody, _ := io.ReadAll(result.Body) assert.Equal(t, test.expectedResponse, resultBody, test.description+":body") resultHeaders := result.Header diff --git a/endpoints/info/bidders_test.go b/endpoints/info/bidders_test.go index 9be25f44c4f..8aec0972f74 100644 --- a/endpoints/info/bidders_test.go +++ b/endpoints/info/bidders_test.go @@ -1,7 +1,7 @@ package info import ( - "io/ioutil" + "io" "net/http" "net/http/httptest" "testing" @@ -12,8 +12,8 @@ import ( func TestPrepareBiddersResponseAll(t *testing.T) { var ( - enabled = config.BidderInfo{Enabled: true} - disabled = config.BidderInfo{Enabled: false} + enabled = config.BidderInfo{Disabled: false} + disabled = config.BidderInfo{Disabled: true} ) testCases := []struct { @@ -88,8 +88,8 @@ func TestPrepareBiddersResponseAll(t *testing.T) { func TestPrepareBiddersResponseEnabledOnly(t *testing.T) { var ( - enabled = config.BidderInfo{Enabled: true} - disabled = config.BidderInfo{Enabled: false} + enabled = config.BidderInfo{Disabled: false} + disabled = config.BidderInfo{Disabled: true} ) testCases := []struct { @@ -164,8 +164,8 @@ func TestPrepareBiddersResponseEnabledOnly(t *testing.T) { func TestBiddersHandler(t *testing.T) { var ( - enabled = config.BidderInfo{Enabled: true} - disabled = config.BidderInfo{Enabled: false} + enabled = config.BidderInfo{Disabled: false} + disabled = config.BidderInfo{Disabled: true} ) bidders := config.BidderInfos{"a": enabled, "b": disabled} @@ -240,7 +240,7 @@ func TestBiddersHandler(t *testing.T) { result := responseRecorder.Result() assert.Equal(t, result.StatusCode, test.expectedStatus) - resultBody, _ := ioutil.ReadAll(result.Body) + resultBody, _ := io.ReadAll(result.Body) assert.Equal(t, []byte(test.expectedBody), resultBody) resultHeaders := result.Header diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index 1d5f8a5e57a..90acbc6d15c 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -11,34 +11,36 @@ import ( "strings" "time" + "github.com/buger/jsonparser" + "github.com/golang/glog" + "github.com/julienschmidt/httprouter" + "github.com/prebid/openrtb/v17/openrtb2" + "github.com/prebid/openrtb/v17/openrtb3" + "github.com/prebid/prebid-server/hooks/hookexecution" + "github.com/prebid/prebid-server/util/uuidutil" + jsonpatch "gopkg.in/evanphx/json-patch.v4" + accountService "github.com/prebid/prebid-server/account" "github.com/prebid/prebid-server/amp" "github.com/prebid/prebid-server/analytics" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/exchange" - aggregatedGDPR "github.com/prebid/prebid-server/gdpr" + "github.com/prebid/prebid-server/hooks" "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" - "github.com/prebid/prebid-server/privacy/gdpr" "github.com/prebid/prebid-server/stored_requests" "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" "github.com/prebid/prebid-server/stored_responses" "github.com/prebid/prebid-server/usersync" "github.com/prebid/prebid-server/util/iputil" "github.com/prebid/prebid-server/version" - - "github.com/buger/jsonparser" - "github.com/golang/glog" - "github.com/julienschmidt/httprouter" - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/util/uuidutil" ) const defaultAmpRequestTimeoutMillis = 900 +var nilBody []byte = nil + type AmpResponse struct { Targeting map[string]string `json:"targeting"` Debug *openrtb_ext.ExtResponseDebug `json:"debug,omitempty"` @@ -55,15 +57,16 @@ func NewAmpEndpoint( requestsById stored_requests.Fetcher, accounts stored_requests.AccountFetcher, cfg *config.Configuration, - met metrics.MetricsEngine, + metricsEngine metrics.MetricsEngine, pbsAnalytics analytics.PBSAnalyticsModule, disabledBidders map[string]string, defReqJSON []byte, bidderMap map[string]openrtb_ext.BidderName, storedRespFetcher stored_requests.Fetcher, + hookExecutionPlanBuilder hooks.ExecutionPlanBuilder, ) (httprouter.Handle, error) { - if ex == nil || validator == nil || requestsById == nil || accounts == nil || cfg == nil || met == nil { + if ex == nil || validator == nil || requestsById == nil || accounts == nil || cfg == nil || metricsEngine == nil { return nil, errors.New("NewAmpEndpoint requires non-nil arguments.") } @@ -74,6 +77,8 @@ func NewAmpEndpoint( IPv6PrivateNetworks: cfg.RequestValidation.IPv6PrivateNetworksParsed, } + hookExecutor := hookexecution.NewHookExecutor(hookExecutionPlanBuilder, hookexecution.EndpointAmp, metricsEngine) + return httprouter.Handle((&endpointDeps{ uuidGenerator, ex, @@ -82,7 +87,7 @@ func NewAmpEndpoint( empty_fetcher.EmptyFetcher{}, accounts, cfg, - met, + metricsEngine, pbsAnalytics, disabledBidders, defRequest, @@ -91,7 +96,8 @@ func NewAmpEndpoint( nil, nil, ipValidator, - storedRespFetcher}).AmpAuction), nil + storedRespFetcher, + hookExecutor}).AmpAuction), nil } @@ -119,6 +125,7 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h CookieFlag: metrics.CookieFlagUnknown, RequestStatus: metrics.RequestStatusOK, } + defer func() { deps.metricsEngine.RecordRequest(labels) deps.metricsEngine.RecordRequestTime(labels, time.Since(start)) @@ -139,24 +146,30 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h w.Header().Set("Access-Control-Expose-Headers", "AMP-Access-Control-Allow-Source-Origin") w.Header().Set("X-Prebid", version.BuildXPrebidHeader(version.Ver)) - req, storedAuctionResponses, storedBidResponses, errL := deps.parseAmpRequest(r) + // There is no body for AMP requests, so we pass a nil body and ignore the return value. + if _, rejectErr := deps.hookExecutor.ExecuteEntrypointStage(r, nilBody); rejectErr != nil { + labels, ao = rejectAmpRequest(*rejectErr, w, nil, labels, ao, nil) + return + } + + reqWrapper, storedAuctionResponses, storedBidResponses, bidderImpReplaceImp, errL := deps.parseAmpRequest(r) ao.Errors = append(ao.Errors, errL...) if errortypes.ContainsFatalError(errL) { w.WriteHeader(http.StatusBadRequest) for _, err := range errortypes.FatalOnly(errL) { - w.Write([]byte(fmt.Sprintf("Invalid request format: %s\n", err.Error()))) + w.Write([]byte(fmt.Sprintf("Invalid request: %s\n", err.Error()))) } labels.RequestStatus = metrics.RequestStatusBadInput return } - ao.Request = req + ao.Request = reqWrapper.BidRequest ctx := context.Background() var cancel context.CancelFunc - if req.TMax > 0 { - ctx, cancel = context.WithDeadline(ctx, start.Add(time.Duration(req.TMax)*time.Millisecond)) + if reqWrapper.TMax > 0 { + ctx, cancel = context.WithDeadline(ctx, start.Add(time.Duration(reqWrapper.TMax)*time.Millisecond)) } else { ctx, cancel = context.WithDeadline(ctx, start.Add(time.Duration(defaultAmpRequestTimeoutMillis)*time.Millisecond)) } @@ -168,10 +181,14 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h } else { labels.CookieFlag = metrics.CookieFlagNo } - labels.PubID = getAccountID(req.Site.Publisher) + labels.PubID = getAccountID(reqWrapper.Site.Publisher) // Look up account now that we have resolved the pubID value account, acctIDErrs := accountService.GetAccount(ctx, deps.cfg, deps.accounts, labels.PubID) if len(acctIDErrs) > 0 { + // best attempt to rebuild the request for analytics. we're already in an error state, so ignoring a + // potential error from this call + reqWrapper.RebuildRequest() + errL = append(errL, acctIDErrs...) httpStatus := http.StatusBadRequest metricsStatus := metrics.RequestStatusBadInput @@ -182,11 +199,16 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h metricsStatus = metrics.RequestStatusBlacklisted break } + if errCode == errortypes.MalformedAcctErrorCode { + httpStatus = http.StatusInternalServerError + metricsStatus = metrics.RequestStatusAccountConfigErr + break + } } w.WriteHeader(httpStatus) labels.RequestStatus = metricsStatus for _, err := range errortypes.FatalOnly(errL) { - w.Write([]byte(fmt.Sprintf("Invalid request format: %s\n", err.Error()))) + w.Write([]byte(fmt.Sprintf("Invalid request: %s\n", err.Error()))) } ao.Errors = append(ao.Errors, acctIDErrs...) return @@ -195,23 +217,35 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h secGPC := r.Header.Get("Sec-GPC") auctionRequest := exchange.AuctionRequest{ - BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: req}, + BidRequestWrapper: reqWrapper, Account: *account, UserSyncs: usersyncs, RequestType: labels.RType, StartTime: start, LegacyLabels: labels, GlobalPrivacyControlHeader: secGPC, - TCF2ConfigBuilder: aggregatedGDPR.NewTCF2Config, - GDPRPermissionsBuilder: aggregatedGDPR.NewPermissions, StoredAuctionResponses: storedAuctionResponses, StoredBidResponses: storedBidResponses, + BidderImpReplaceImpID: bidderImpReplaceImp, + PubID: labels.PubID, + HookExecutor: deps.hookExecutor, } response, err := deps.ex.HoldAuction(ctx, auctionRequest, nil) ao.AuctionResponse = response + rejectErr, isRejectErr := hookexecution.CastRejectErr(err) + if err != nil && !isRejectErr { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Critical error while running the auction: %v", err) + glog.Errorf("/openrtb2/amp Critical error: %v", err) + ao.Status = http.StatusInternalServerError + ao.Errors = append(ao.Errors, err) + return + } - if err != nil { + // hold auction rebuilds the request wrapper first thing, so there is likely + // no work to do here, but added a rebuild just in case this behavior changes. + if err := reqWrapper.RebuildRequest(); err != nil { w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, "Critical error while running the auction: %v", err) glog.Errorf("/openrtb2/amp Critical error: %v", err) @@ -220,6 +254,37 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h return } + if isRejectErr { + labels, ao = rejectAmpRequest(*rejectErr, w, reqWrapper, labels, ao, errL) + return + } + + labels, ao = sendAmpResponse(w, response, reqWrapper, labels, ao, errL) +} + +func rejectAmpRequest( + rejectErr hookexecution.RejectError, + w http.ResponseWriter, + reqWrapper *openrtb_ext.RequestWrapper, + labels metrics.Labels, + ao analytics.AmpObject, + errs []error, +) (metrics.Labels, analytics.AmpObject) { + response := &openrtb2.BidResponse{NBR: openrtb3.NoBidReason(rejectErr.NBR).Ptr()} + ao.AuctionResponse = response + ao.Errors = append(ao.Errors, rejectErr) + + return sendAmpResponse(w, response, reqWrapper, labels, ao, errs) +} + +func sendAmpResponse( + w http.ResponseWriter, + response *openrtb2.BidResponse, + reqWrapper *openrtb_ext.RequestWrapper, + labels metrics.Labels, + ao analytics.AmpObject, + errs []error, +) (metrics.Labels, analytics.AmpObject) { // Need to extract the targeting parameters from the response, as those are all that // go in the AMP response targets := map[string]string{} @@ -240,7 +305,7 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h glog.Errorf("/openrtb2/amp Critical error unpacking targets: %v", err) ao.Errors = append(ao.Errors, fmt.Errorf("Critical error while unpacking AMP targets: %v", err)) ao.Status = http.StatusInternalServerError - return + return labels, ao } for key, value := range bidExt.Prebid.Targeting { targets[key] = value @@ -260,7 +325,7 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h if warnings == nil { warnings = make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage) } - for _, v := range errortypes.WarningOnly(errL) { + for _, v := range errortypes.WarningOnly(errs) { bidderErr := openrtb_ext.ExtBidderMessage{ Code: errortypes.ReadCode(v), Message: v.Error(), @@ -278,12 +343,12 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h ao.AmpTargetingValues = targets // add debug information if requested - if req.Test == 1 && eRErr == nil { + if reqWrapper != nil && reqWrapper.Test == 1 && eRErr == nil { if extResponse.Debug != nil { ampResponse.Debug = extResponse.Debug } else { - glog.Errorf("Test set on request but debug not present in response: %v", err) - ao.Errors = append(ao.Errors, fmt.Errorf("Test set on request but debug not present in response: %v", err)) + glog.Errorf("Test set on request but debug not present in response.") + ao.Errors = append(ao.Errors, fmt.Errorf("test set on request but debug not present in response")) } } @@ -298,6 +363,8 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h labels.RequestStatus = metrics.RequestStatusNetworkErr ao.Errors = append(ao.Errors, fmt.Errorf("/openrtb2/amp Failed to send response: %v", err)) } + + return labels, ao } // parseRequest turns the HTTP request into an OpenRTB request. @@ -306,13 +373,16 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h // 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) parseAmpRequest(httpRequest *http.Request) (req *openrtb2.BidRequest, storedAuctionResponses stored_responses.ImpsWithBidResponses, storedBidResponses stored_responses.ImpBidderStoredResp, errs []error) { +func (deps *endpointDeps) parseAmpRequest(httpRequest *http.Request) (req *openrtb_ext.RequestWrapper, storedAuctionResponses stored_responses.ImpsWithBidResponses, storedBidResponses stored_responses.ImpBidderStoredResp, bidderImpReplaceImp stored_responses.BidderImpReplaceImpID, errs []error) { // Load the stored request for the AMP ID. - req, storedAuctionResponses, storedBidResponses, e := deps.loadRequestJSONForAmp(httpRequest) + reqNormal, storedAuctionResponses, storedBidResponses, bidderImpReplaceImp, e := deps.loadRequestJSONForAmp(httpRequest) if errs = append(errs, e...); errortypes.ContainsFatalError(errs) { return } + // move to using the request wrapper + req = &openrtb_ext.RequestWrapper{BidRequest: reqNormal} + // Populate any "missing" OpenRTB fields with info from other sources, (e.g. HTTP request headers). deps.setFieldsImplicitly(httpRequest, req) @@ -323,23 +393,21 @@ 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 - var hasStoredResponses bool - if len(storedAuctionResponses) > 0 { - hasStoredResponses = true - } - e = deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: req}, true, hasStoredResponses, storedBidResponses) + hasStoredResponses := len(storedAuctionResponses) > 0 + + e = deps.validateRequest(req, true, hasStoredResponses, storedBidResponses) errs = append(errs, e...) return } // Load the stored OpenRTB request for an incoming AMP request, or return the errors found. -func (deps *endpointDeps) loadRequestJSONForAmp(httpRequest *http.Request) (req *openrtb2.BidRequest, storedAuctionResponses stored_responses.ImpsWithBidResponses, storedBidResponses stored_responses.ImpBidderStoredResp, errs []error) { +func (deps *endpointDeps) loadRequestJSONForAmp(httpRequest *http.Request) (req *openrtb2.BidRequest, storedAuctionResponses stored_responses.ImpsWithBidResponses, storedBidResponses stored_responses.ImpBidderStoredResp, bidderImpReplaceImp stored_responses.BidderImpReplaceImpID, errs []error) { req = &openrtb2.BidRequest{} errs = nil ampParams, err := amp.ParseParams(httpRequest) if err != nil { - return nil, nil, nil, []error{err} + return nil, nil, nil, nil, []error{err} } ctx, cancel := context.WithTimeout(context.Background(), time.Duration(storedRequestTimeoutMillis)*time.Millisecond) @@ -347,7 +415,7 @@ func (deps *endpointDeps) loadRequestJSONForAmp(httpRequest *http.Request) (req storedRequests, _, errs := deps.storedReqFetcher.FetchRequests(ctx, []string{ampParams.StoredRequestID}, nil) if len(errs) > 0 { - return nil, nil, nil, errs + return nil, nil, nil, nil, errs } if len(storedRequests) == 0 { errs = []error{fmt.Errorf("No AMP config found for tag_id '%s'", ampParams.StoredRequestID)} @@ -361,7 +429,7 @@ func (deps *endpointDeps) loadRequestJSONForAmp(httpRequest *http.Request) (req return } - storedAuctionResponses, storedBidResponses, errs = stored_responses.ProcessStoredResponses(ctx, requestJSON, deps.storedRespFetcher, deps.bidderMap) + storedAuctionResponses, storedBidResponses, bidderImpReplaceImp, errs = stored_responses.ProcessStoredResponses(ctx, requestJSON, deps.storedRespFetcher, deps.bidderMap) if err != nil { errs = []error{err} return @@ -435,7 +503,7 @@ func (deps *endpointDeps) overrideWithParams(ampParams amp.Params, req *openrtb2 } } - setAmpExt(req.Site, "1") + setAmpExtDirect(req.Site, "1") setEffectiveAmpPubID(req, ampParams.Account) @@ -443,7 +511,11 @@ func (deps *endpointDeps) overrideWithParams(ampParams amp.Params, req *openrtb2 req.Imp[0].TagID = ampParams.Slot } - policyWriter, policyWriterErr := readPolicy(ampParams.Consent) + if err := setConsentedProviders(req, ampParams); err != nil { + return []error{err} + } + + policyWriter, policyWriterErr := amp.ReadPolicy(ampParams, deps.cfg.GDPR.Enabled) if policyWriterErr != nil { return []error{policyWriterErr} } @@ -455,6 +527,71 @@ func (deps *endpointDeps) overrideWithParams(ampParams amp.Params, req *openrtb2 req.TMax = int64(*ampParams.Timeout) - deps.cfg.AMPTimeoutAdjustment } + if err := setTargeting(req, ampParams.Targeting); err != nil { + return []error{err} + } + + return nil +} + +// setConsentedProviders sets the addtl_consent value to user.ext.ConsentedProvidersSettings.consented_providers +// in its orginal Google Additional Consent string format and user.ext.consented_providers_settings.consented_providers +// that is an array of ints that contains the elements found in addtl_consent +func setConsentedProviders(req *openrtb2.BidRequest, ampParams amp.Params) error { + if len(ampParams.AdditionalConsent) > 0 { + reqWrap := &openrtb_ext.RequestWrapper{BidRequest: req} + + userExt, err := reqWrap.GetUserExt() + if err != nil { + return err + } + + // Parse addtl_consent, that is supposed to come formatted as a Google Additional Consent string, into array of ints + consentedProvidersList := openrtb_ext.ParseConsentedProvidersString(ampParams.AdditionalConsent) + + // Set user.ext.consented_providers_settings.consented_providers if elements where found + if len(consentedProvidersList) > 0 { + cps := userExt.GetConsentedProvidersSettingsOut() + if cps == nil { + cps = &openrtb_ext.ConsentedProvidersSettingsOut{} + } + cps.ConsentedProvidersList = append(cps.ConsentedProvidersList, consentedProvidersList...) + userExt.SetConsentedProvidersSettingsOut(cps) + } + + // Copy addtl_consent into user.ext.ConsentedProvidersSettings.consented_providers as is + cps := userExt.GetConsentedProvidersSettingsIn() + if cps == nil { + cps = &openrtb_ext.ConsentedProvidersSettingsIn{} + } + cps.ConsentedProvidersString = ampParams.AdditionalConsent + userExt.SetConsentedProvidersSettingsIn(cps) + + if err := reqWrap.RebuildRequest(); err != nil { + return err + } + } + return nil +} + +// setTargeting merges "targeting" to imp[0].ext.data +func setTargeting(req *openrtb2.BidRequest, targeting string) error { + if len(targeting) == 0 { + return nil + } + + targetingData := exchange.WrapJSONInData([]byte(targeting)) + + if len(req.Imp[0].Ext) > 0 { + newImpExt, err := jsonpatch.MergePatch(req.Imp[0].Ext, targetingData) + if err != nil { + return fmt.Errorf("unable to merge imp.ext with targeting data, check targeting data is correct: %s", err.Error()) + } + req.Imp[0].Ext = newImpExt + return nil + } + + req.Imp[0].Ext = targetingData return nil } @@ -499,49 +636,47 @@ func setHeights(formats []openrtb2.Format, height int64) { // AMP won't function unless ext.prebid.targeting and ext.prebid.cache.bids are defined. // If the user didn't include them, default those here. -func defaultRequestExt(req *openrtb2.BidRequest) (errs []error) { - errs = nil - extRequest := &openrtb_ext.ExtRequest{} - if req.Ext != nil && len(req.Ext) > 0 { - if err := json.Unmarshal(req.Ext, extRequest); err != nil { - errs = []error{err} - return - } +func defaultRequestExt(req *openrtb_ext.RequestWrapper) []error { + extRequest, err := req.GetRequestExt() + if err != nil { + return []error{err} + } + + prebid := extRequest.GetPrebid() + prebidModified := false + + // create prebid object if missing from request + if prebid == nil { + prebid = &openrtb_ext.ExtRequestPrebid{} } - setDefaults := false // Ensure Targeting and caching is on - if extRequest.Prebid.Targeting == nil { - setDefaults = true - extRequest.Prebid.Targeting = &openrtb_ext.ExtRequestTargeting{ - // Fixes #452 + if prebid.Targeting == nil { + prebid.Targeting = &openrtb_ext.ExtRequestTargeting{ IncludeWinners: true, IncludeBidderKeys: true, PriceGranularity: openrtb_ext.PriceGranularityFromString("med"), } + prebidModified = true } - if extRequest.Prebid.Cache == nil { - setDefaults = true - extRequest.Prebid.Cache = &openrtb_ext.ExtRequestPrebidCache{ + + if prebid.Cache == nil { + prebid.Cache = &openrtb_ext.ExtRequestPrebidCache{ Bids: &openrtb_ext.ExtRequestPrebidCacheBids{}, } - } else if extRequest.Prebid.Cache.Bids == nil { - setDefaults = true - extRequest.Prebid.Cache.Bids = &openrtb_ext.ExtRequestPrebidCacheBids{} - } - if setDefaults { - newExt, err := json.Marshal(extRequest) - if err == nil { - req.Ext = newExt - } else { - errs = []error{err} - } + prebidModified = true + } else if prebid.Cache.Bids == nil { + prebid.Cache.Bids = &openrtb_ext.ExtRequestPrebidCacheBids{} + prebidModified = true } - return + if prebidModified { + extRequest.SetPrebid(prebid) + } + return nil } -func setAmpExt(site *openrtb2.Site, value string) { +func setAmpExtDirect(site *openrtb2.Site, value string) { if len(site.Ext) > 0 { if _, dataType, _, _ := jsonparser.Get(site.Ext, "amp"); dataType == jsonparser.NotExist { if val, err := jsonparser.Set(site.Ext, []byte(value), "amp"); err == nil { @@ -553,44 +688,27 @@ func setAmpExt(site *openrtb2.Site, value string) { } } -func readPolicy(consent string) (privacy.PolicyWriter, error) { - if len(consent) == 0 { - return privacy.NilPolicyWriter{}, nil - } - - if gdpr.ValidateConsent(consent) { - return gdpr.ConsentWriter{consent}, nil - } - - if ccpa.ValidateConsent(consent) { - return ccpa.ConsentWriter{consent}, nil - } - - return privacy.NilPolicyWriter{}, &errortypes.Warning{ - Message: fmt.Sprintf("Consent '%s' is not recognized as either CCPA or GDPR TCF.", consent), - WarningCode: errortypes.InvalidPrivacyConsentWarningCode, - } -} - // Sets the effective publisher ID for amp request func setEffectiveAmpPubID(req *openrtb2.BidRequest, account string) { + // ACCOUNT_ID is the unresolved macro name and should be ignored. + if account == "" || account == "ACCOUNT_ID" { + return + } + var pub *openrtb2.Publisher if req.App != nil { if req.App.Publisher == nil { - req.App.Publisher = new(openrtb2.Publisher) + req.App.Publisher = &openrtb2.Publisher{} } pub = req.App.Publisher } else if req.Site != nil { if req.Site.Publisher == nil { - req.Site.Publisher = new(openrtb2.Publisher) + req.Site.Publisher = &openrtb2.Publisher{} } pub = req.Site.Publisher } if pub.ID == "" { - // ACCOUNT_ID is the unresolved macro name and should be ignored. - if account != "" && account != "ACCOUNT_ID" { - pub.ID = account - } + pub.ID = account } } diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go index a25efab07f1..ad604df8472 100644 --- a/endpoints/openrtb2/amp_auction_test.go +++ b/endpoints/openrtb2/amp_auction_test.go @@ -4,20 +4,23 @@ import ( "context" "encoding/json" "fmt" - "io/ioutil" "net/http" "net/http/httptest" "net/url" + "os" "reflect" "strconv" "testing" "github.com/julienschmidt/httprouter" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" + "github.com/prebid/prebid-server/amp" "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/hooks" + "github.com/prebid/prebid-server/hooks/hookstage" 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" @@ -27,68 +30,164 @@ import ( // TestGoodRequests makes sure that the auction runs properly-formatted stored bids correctly. func TestGoodAmpRequests(t *testing.T) { - testCases := []struct { + testGroups := []struct { + desc string + dir string + testFiles []string + }{ + { + desc: "Valid supplementary, tag_id param only", + dir: "sample-requests/amp/valid-supplementary/", + testFiles: []string{ + "aliased-buyeruids.json", + "aliases.json", + "imp-with-stored-resp.json", + "gdpr-no-consentstring.json", + "gdpr.json", + }, + }, + { + desc: "Valid, consent handling in query", + dir: "sample-requests/amp/consent-through-query/", + testFiles: []string{ + "addtl-consent-through-query.json", + "gdpr-tcf1-consent-through-query.json", + "gdpr-tcf2-consent-through-query.json", + "gdpr-legacy-tcf2-consent-through-query.json", + "gdpr-ccpa-through-query.json", + }, + }, + } + + for _, tgroup := range testGroups { + for _, filename := range tgroup.testFiles { + // Read test case and unmarshal + fileJsonData, err := os.ReadFile(tgroup.dir + filename) + if !assert.NoError(t, err, "Failed to fetch a valid request: %v. Test file: %s", err, filename) { + continue + } + + test := testCase{} + if !assert.NoError(t, json.Unmarshal(fileJsonData, &test), "Failed to unmarshal data from file: %s. Error: %v", filename, err) { + continue + } + + // build http request + request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?%s", test.Query), nil) + recorder := httptest.NewRecorder() + + // build the stored requests and configure endpoint conf + query := request.URL.Query() + tagID := query.Get("tag_id") + if !assert.Greater(t, len(tagID), 0, "AMP test %s file is missing tag_id field", filename) { + continue + } + + test.storedRequest = map[string]json.RawMessage{tagID: test.BidRequest} + test.endpointType = AMP_ENDPOINT + + cfg := &config.Configuration{ + MaxRequestSize: maxSize, + GDPR: config.GDPR{Enabled: true}, + } + if test.Config != nil { + cfg.BlacklistedApps = test.Config.BlacklistedApps + cfg.BlacklistedAppMap = test.Config.getBlacklistedAppMap() + cfg.BlacklistedAccts = test.Config.BlacklistedAccounts + cfg.BlacklistedAcctMap = test.Config.getBlackListedAccountMap() + cfg.AccountRequired = test.Config.AccountRequired + } + + // Set test up + ampEndpoint, ex, mockBidServers, mockCurrencyRatesServer, err := buildTestEndpoint(test, cfg) + if !assert.NoError(t, err) { + continue + } + + // runTestCase + ampEndpoint(recorder, request, nil) + + // Close servers + for _, mockBidServer := range mockBidServers { + mockBidServer.Close() + } + mockCurrencyRatesServer.Close() + + // Assertions + if assert.Equal(t, test.ExpectedReturnCode, recorder.Code, "Expected status %d. Got %d. Amp test file: %s", http.StatusOK, recorder.Code, filename) { + if test.ExpectedReturnCode == http.StatusOK { + var ampResponse AmpResponse + assert.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &Response), "Error unmarshalling ampResponse: %v", err) + assert.Equal(t, test.ExpectedAmpResponse, ampResponse, "Not the expected response. Test file: %s", filename) + } else { + assert.Equal(t, test.ExpectedErrorMessage, recorder.Body.String(), filename) + } + } + if test.ExpectedValidatedBidReq != nil { + // compare as json to ignore whitespace and ext field ordering + actualJson, err := json.Marshal(ex.actualValidatedBidReq) + if assert.NoError(t, err, "Error converting actual bid request to json. Test file: %s", filename) { + assert.JSONEq(t, string(test.ExpectedValidatedBidReq), string(actualJson), "Not the expected validated request. Test file: %s", filename) + } + } + } + } +} + +func TestAccountErrors(t *testing.T) { + tests := []struct { + description string storedReqID string filename string }{ - {"1", "aliased-buyeruids.json"}, - {"2", "aliases.json"}, - {"3", "imp-with-stored-resp.json"}, - {"5", "gdpr-no-consentstring.json"}, - {"6", "gdpr.json"}, + { + description: "Malformed account config", + storedReqID: "1", + filename: "account-malformed/malformed-acct.json", + }, + { + description: "Blocked account", + storedReqID: "1", + filename: "blacklisted/blacklisted-site-publisher.json", + }, } - for _, tc := range testCases { - // Read test case and unmarshal - fileJsonData, err := ioutil.ReadFile("sample-requests/valid-whole/supplementary/" + tc.filename) - if !assert.NoError(t, err, "Failed to fetch a valid request: %v. Test file: %s", err, tc.filename) { + for _, tt := range tests { + fileJsonData, err := os.ReadFile("sample-requests/" + tt.filename) + if !assert.NoError(t, err, "Failed to fetch a valid request: %v. Test file: %s", err, tt.filename) { continue } test := testCase{} - if !assert.NoError(t, json.Unmarshal(fileJsonData, &test), "Failed to unmarshal data from file: %s. Error: %v", tc.filename, err) { + if !assert.NoError(t, json.Unmarshal(fileJsonData, &test), "Failed to unmarshal data from file: %s. Error: %v", tt.filename, err) { continue } - test.storedRequest = map[string]json.RawMessage{tc.storedReqID: test.BidRequest} + test.storedRequest = map[string]json.RawMessage{tt.storedReqID: test.BidRequest} test.endpointType = AMP_ENDPOINT - cfg := &config.Configuration{MaxRequestSize: maxSize} - if test.Config != nil { - cfg.BlacklistedApps = test.Config.BlacklistedApps - cfg.BlacklistedAppMap = test.Config.getBlacklistedAppMap() - cfg.BlacklistedAccts = test.Config.BlacklistedAccounts - cfg.BlacklistedAcctMap = test.Config.getBlackListedAccountMap() - cfg.AccountRequired = test.Config.AccountRequired + cfg := &config.Configuration{ + BlacklistedAccts: []string{"bad_acct"}, + BlacklistedAcctMap: map[string]bool{"bad_acct": true}, + MaxRequestSize: maxSize, } + cfg.MarshalAccountDefaults() - // Set test up - ampEndpoint, mockBidServers, mockCurrencyRatesServer, err := buildTestEndpoint(test, cfg) + ampEndpoint, _, mockBidServers, mockCurrencyRatesServer, err := buildTestEndpoint(test, cfg) if !assert.NoError(t, err) { continue } - request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=%s", tc.storedReqID), nil) + request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=%s&", tt.storedReqID), nil) recorder := httptest.NewRecorder() - - // runTestCase ampEndpoint(recorder, request, nil) - // Close servers for _, mockBidServer := range mockBidServers { mockBidServer.Close() } mockCurrencyRatesServer.Close() - // Assertions - if assert.Equal(t, test.ExpectedReturnCode, recorder.Code, "Expected status %d. Got %d. Amp test file: %s", http.StatusOK, recorder.Code, tc.filename) { - if test.ExpectedReturnCode == http.StatusOK { - var ampResponse AmpResponse - assert.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &Response), "Error unmarshalling ampResponse: %v", err) - assert.Equal(t, test.ExpectedAmpResponse, ampResponse, "Test file: %s", tc.filename) - } else { - assert.Equal(t, test.ExpectedErrorMessage, recorder.Body.String(), tc.filename) - } - } + assert.Equal(t, test.ExpectedReturnCode, recorder.Code, "%s: %s", tt.description, tt.filename) + assert.Equal(t, test.ExpectedErrorMessage, recorder.Body.String(), "%s: %s", tt.description, tt.filename) } } @@ -113,6 +212,7 @@ func TestAMPPageInfo(t *testing.T) { []byte{}, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, + hooks.EmptyPlanBuilder{}, ) request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=1&curl=%s", url.QueryEscape(page)), nil) recorder := httptest.NewRecorder() @@ -129,7 +229,7 @@ func TestAMPPageInfo(t *testing.T) { } func TestGDPRConsent(t *testing.T) { - consent := "BOu5On0Ou5On0ADACHENAO7pqzAAppY" + consent := "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA" existingConsent := "BONV8oqONXwgmADACHENAO7pqzAAppY" testCases := []struct { @@ -205,17 +305,21 @@ func TestGDPRConsent(t *testing.T) { newParamsValidator(t), &mockAmpStoredReqFetcher{stored}, empty_fetcher.EmptyFetcher{}, - &config.Configuration{MaxRequestSize: maxSize}, + &config.Configuration{ + MaxRequestSize: maxSize, + GDPR: config.GDPR{Enabled: true}, + }, &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, + hooks.EmptyPlanBuilder{}, ) // Invoke Endpoint - request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=1&consent_string=%s", test.consent), nil) + request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=1&consent_type=2&consent_string=%s", test.consent), nil) responseRecorder := httptest.NewRecorder() endpoint(responseRecorder, request, nil) @@ -246,7 +350,7 @@ func TestGDPRConsent(t *testing.T) { assert.Empty(t, response.Warnings, test.description+":warnings") // Invoke Endpoint With Legacy Param - requestLegacy := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=1&gdpr_consent=%s", test.consent), nil) + requestLegacy := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=1&consent_type=2&gdpr_consent=%s", test.consent), nil) responseRecorderLegacy := httptest.NewRecorder() endpoint(responseRecorderLegacy, requestLegacy, nil) @@ -278,6 +382,237 @@ func TestGDPRConsent(t *testing.T) { } } +func TestOverrideWithParams(t *testing.T) { + e := &endpointDeps{ + cfg: &config.Configuration{ + GDPR: config.GDPR{ + Enabled: true, + }, + }, + } + + type testInput struct { + ampParams amp.Params + bidRequest *openrtb2.BidRequest + } + type testOutput struct { + bidRequest *openrtb2.BidRequest + errorMsgs []string + } + testCases := []struct { + desc string + given testInput + expected testOutput + }{ + { + desc: "bid request with no Site field - amp.Params empty - expect Site to be added", + given: testInput{ + ampParams: amp.Params{}, + bidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}}, + }, + }, + expected: testOutput{ + bidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}}, + Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)}, + }, + errorMsgs: nil, + }, + }, + { + desc: "amp.Params with Size field - expect Site and Banner format fields to be added", + given: testInput{ + ampParams: amp.Params{ + Size: amp.Size{ + Width: 480, + Height: 320, + }, + }, + bidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}}, + }, + }, + expected: testOutput{ + bidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + { + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ + { + W: 480, + H: 320, + }, + }, + }, + }, + }, + Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)}, + }, + errorMsgs: nil, + }, + }, + { + desc: "amp.Params with CanonicalURL field - expect Site to be aded with Page and Domain fields", + given: testInput{ + ampParams: amp.Params{CanonicalURL: "http://www.foobar.com"}, + bidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}}}, + }, + expected: testOutput{ + bidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}}, + Site: &openrtb2.Site{ + Page: "http://www.foobar.com", + Domain: "www.foobar.com", + Ext: json.RawMessage(`{"amp":1}`), + }, + }, + errorMsgs: nil, + }, + }, + { + desc: "bid request with malformed User.Ext - amp.Params with AdditionalConsent - expect error", + given: testInput{ + ampParams: amp.Params{AdditionalConsent: "1~X.X.X.X"}, + bidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}}, + User: &openrtb2.User{Ext: json.RawMessage(`malformed`)}, + }, + }, + expected: testOutput{ + bidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}}, + Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)}, + User: &openrtb2.User{Ext: json.RawMessage(`malformed`)}, + }, + errorMsgs: []string{"invalid character 'm' looking for beginning of value"}, + }, + }, + { + desc: "bid request with valid imp[0].ext - amp.Params with malformed targeting value - expect error because imp[0].ext won't be unable to get merged with targeting values", + given: testInput{ + ampParams: amp.Params{Targeting: "{123,}"}, + bidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + { + Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}, + Ext: []byte(`{"appnexus":{"placementId":123}}`), + }, + }, + }, + }, + expected: testOutput{ + bidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + { + Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}, + Ext: json.RawMessage(`{"appnexus":{"placementId":123}}`), + }, + }, + Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)}, + }, + errorMsgs: []string{"unable to merge imp.ext with targeting data, check targeting data is correct: Invalid JSON Patch"}, + }, + }, + { + desc: "bid request with malformed user.ext.prebid - amp.Params with GDPR consent values - expect policy writer to return error", + given: testInput{ + ampParams: amp.Params{ + ConsentType: amp.ConsentTCF2, + Consent: "CPdECS0PdECS0ACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", + }, + bidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}}, + User: &openrtb2.User{Ext: json.RawMessage(`{"prebid":{malformed}}`)}, + }, + }, + expected: testOutput{ + bidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}}, + User: &openrtb2.User{Ext: json.RawMessage(`{"prebid":{malformed}}`)}, + Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)}, + }, + errorMsgs: []string{"invalid character 'm' looking for beginning of object key string"}, + }, + }, + } + + for _, test := range testCases { + errs := e.overrideWithParams(test.given.ampParams, test.given.bidRequest) + + assert.Equal(t, test.expected.bidRequest, test.given.bidRequest, test.desc) + assert.Len(t, errs, len(test.expected.errorMsgs), test.desc) + if len(test.expected.errorMsgs) > 0 { + assert.Equal(t, test.expected.errorMsgs[0], errs[0].Error(), test.desc) + } + } +} + +func TestSetConsentedProviders(t *testing.T) { + + sampleBidRequest := &openrtb2.BidRequest{} + + testCases := []struct { + description string + givenAdditionalConsent string + givenBidRequest *openrtb2.BidRequest + expectedBidRequest *openrtb2.BidRequest + expectedError bool + }{ + { + description: "empty additional consent bid request unmodified", + givenAdditionalConsent: "", + givenBidRequest: sampleBidRequest, + expectedBidRequest: sampleBidRequest, + expectedError: false, + }, + { + description: "nil bid request, expect error", + givenAdditionalConsent: "ADDITIONAL_CONSENT_STRING", + givenBidRequest: nil, + expectedBidRequest: nil, + expectedError: true, + }, + { + description: "malformed user.ext, expect error", + givenAdditionalConsent: "ADDITIONAL_CONSENT_STRING", + givenBidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ + Ext: json.RawMessage(`malformed`), + }, + }, + expectedBidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ + Ext: json.RawMessage(`malformed`), + }, + }, + expectedError: true, + }, + { + description: "non-empty additional consent bid request will carry this value in user.ext.ConsentedProvidersSettings.consented_providers", + givenAdditionalConsent: "ADDITIONAL_CONSENT_STRING", + givenBidRequest: sampleBidRequest, + expectedBidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ + Ext: json.RawMessage(`{"ConsentedProvidersSettings":{"consented_providers":"ADDITIONAL_CONSENT_STRING"}}`), + }, + }, + expectedError: false, + }, + } + + for _, test := range testCases { + err := setConsentedProviders(test.givenBidRequest, amp.Params{AdditionalConsent: test.givenAdditionalConsent}) + + if test.expectedError { + assert.Error(t, err, test.description) + } else { + assert.NoError(t, err, test.description) + } + assert.Equal(t, test.expectedBidRequest, test.givenBidRequest, test.description) + } +} + func TestCCPAConsent(t *testing.T) { consent := "1NYN" existingConsent := "1NNN" @@ -366,10 +701,11 @@ func TestCCPAConsent(t *testing.T) { []byte{}, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, + hooks.EmptyPlanBuilder{}, ) // Invoke Endpoint - request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=1&consent_string=%s", test.consent), nil) + request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=1&consent_type=3&consent_string=%s", test.consent), nil) responseRecorder := httptest.NewRecorder() endpoint(responseRecorder, request, nil) @@ -415,7 +751,7 @@ func TestConsentWarnings(t *testing.T) { } invalidCCPAWarning := openrtb_ext.ExtBidderMessage{ Code: 10001, - Message: "Consent '" + invalidConsent + "' is not recognized as either CCPA or GDPR TCF.", + Message: "Consent string '" + invalidConsent + "' is not a valid CCPA consent string.", } invalidConsentWarning := openrtb_ext.ExtBidderMessage{ Code: 10001, @@ -478,13 +814,14 @@ func TestConsentWarnings(t *testing.T) { []byte{}, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, + hooks.EmptyPlanBuilder{}, ) // Invoke Endpoint var request *http.Request if testCase.invalidConsentURL { - request = httptest.NewRequest("GET", "/openrtb2/auction/amp?tag_id=1&consent_string="+invalidConsent, nil) + request = httptest.NewRequest("GET", "/openrtb2/auction/amp?tag_id=1&consent_type=3&consent_string="+invalidConsent, nil) } else { request = httptest.NewRequest("GET", "/openrtb2/auction/amp?tag_id=1", nil) @@ -519,8 +856,8 @@ func TestConsentWarnings(t *testing.T) { } func TestNewAndLegacyConsentBothProvided(t *testing.T) { - validConsentGDPR1 := "BOu5On0Ou5On0ADACHENAO7pqzAAppY" - validConsentGDPR2 := "BONV8oqONXwgmADACHENAO7pqzAAppY" + validConsentGDPR1 := "COwGVJOOwGVJOADACHENAOCAAO6as_-AAAhoAFNLAAoAAAA" + validConsentGDPR2 := "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA" testCases := []struct { description string @@ -565,17 +902,21 @@ func TestNewAndLegacyConsentBothProvided(t *testing.T) { newParamsValidator(t), &mockAmpStoredReqFetcher{stored}, empty_fetcher.EmptyFetcher{}, - &config.Configuration{MaxRequestSize: maxSize}, + &config.Configuration{ + MaxRequestSize: maxSize, + GDPR: config.GDPR{Enabled: true}, + }, &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, + hooks.EmptyPlanBuilder{}, ) // Invoke Endpoint - request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=1&consent_string=%s&gdpr_consent=%s", test.consent, test.consentLegacy), nil) + request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=1&consent_type=2&consent_string=%s&gdpr_consent=%s", test.consent, test.consentLegacy), nil) responseRecorder := httptest.NewRecorder() endpoint(responseRecorder, request, nil) @@ -625,6 +966,7 @@ func TestAMPSiteExt(t *testing.T) { nil, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, + hooks.EmptyPlanBuilder{}, ) request, err := http.NewRequest("GET", "/openrtb2/auction/amp?tag_id=1", nil) if !assert.NoError(t, err) { @@ -645,7 +987,7 @@ func TestAMPSiteExt(t *testing.T) { // TestBadRequests makes sure we return 400's on bad requests. func TestAmpBadRequests(t *testing.T) { dir := "sample-requests/invalid-whole" - files, err := ioutil.ReadDir(dir) + files, err := os.ReadDir(dir) assert.NoError(t, err, "Failed to read folder: %s", dir) badRequests := make(map[string]json.RawMessage, len(files)) @@ -666,6 +1008,7 @@ func TestAmpBadRequests(t *testing.T) { []byte{}, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, + hooks.EmptyPlanBuilder{}, ) for requestID := range badRequests { request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=%s", requestID), nil) @@ -698,6 +1041,7 @@ func TestAmpDebug(t *testing.T) { []byte{}, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, + hooks.EmptyPlanBuilder{}, ) for requestID := range requests { @@ -731,11 +1075,13 @@ func TestAmpDebug(t *testing.T) { // Prevents #452 func TestAmpTargetingDefaults(t *testing.T) { - req := &openrtb2.BidRequest{} - if errs := defaultRequestExt(req); len(errs) != 0 { - t.Fatalf("Unexpected error defaulting request.ext for AMP: %v", errs) + req := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{}} + if err := defaultRequestExt(req); err != nil { + t.Fatalf("Unexpected error defaulting request.ext for AMP: %v", err) } + assert.NoError(t, req.RebuildRequest()) + var extRequest openrtb_ext.ExtRequest if err := json.Unmarshal(req.Ext, &extRequest); err != nil { t.Fatalf("Unexpected error unmarshalling defaulted request.ext for AMP: %v", err) @@ -772,6 +1118,7 @@ func TestQueryParamOverrides(t *testing.T) { []byte{}, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, + hooks.EmptyPlanBuilder{}, ) requestID := "1" @@ -928,6 +1275,7 @@ func (s formatOverrideSpec) execute(t *testing.T) { []byte{}, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, + hooks.EmptyPlanBuilder{}, ) url := fmt.Sprintf("/openrtb2/auction/amp?tag_id=1&debug=1&w=%d&h=%d&ow=%d&oh=%d&ms=%s&account=%s", s.width, s.height, s.overrideWidth, s.overrideHeight, s.multisize, s.account) @@ -1155,39 +1503,47 @@ func TestSetEffectiveAmpPubID(t *testing.T) { for _, test := range testCases { setEffectiveAmpPubID(test.req, test.account) if test.req.Site != nil { - assert.Equal(t, test.expectedPubID, test.req.Site.Publisher.ID, - "should return the expected Publisher ID for test case: %s", test.description) + if test.req.Site.Publisher == nil { + assert.Empty(t, test.expectedPubID, + "should return the expected Publisher ID for test case: %s", test.description) + } else { + assert.Equal(t, test.expectedPubID, test.req.Site.Publisher.ID, + "should return the expected Publisher ID for test case: %s", test.description) + } } else { - assert.Equal(t, test.expectedPubID, test.req.App.Publisher.ID, - "should return the expected Publisher ID for test case: %s", test.description) + if test.req.App.Publisher == nil { + assert.Empty(t, test.expectedPubID, + "should return the expected Publisher ID for test case: %s", test.description) + } else { + assert.Equal(t, test.expectedPubID, test.req.App.Publisher.ID, + "should return the expected Publisher ID for test case: %s", test.description) + } } } } type mockLogger struct { - ampObject *analytics.AmpObject + ampObject *analytics.AmpObject + auctionObject *analytics.AuctionObject } -func newMockLogger(ao *analytics.AmpObject) analytics.PBSAnalyticsModule { +func newMockLogger(ao *analytics.AmpObject, aucObj *analytics.AuctionObject) analytics.PBSAnalyticsModule { return &mockLogger{ - ampObject: ao, + ampObject: ao, + auctionObject: aucObj, } } func (logger mockLogger) LogAuctionObject(ao *analytics.AuctionObject) { - return + *logger.auctionObject = *ao } func (logger mockLogger) LogVideoObject(vo *analytics.VideoObject) { - return } func (logger mockLogger) LogCookieSyncObject(cookieObject *analytics.CookieSyncObject) { - return } func (logger mockLogger) LogSetUIDObject(uuidObj *analytics.SetUIDObject) { - return } func (logger mockLogger) LogNotificationEventObject(uuidObj *analytics.NotificationEvent) { - return } func (logger mockLogger) LogAmpObject(ao *analytics.AmpObject) { *logger.ampObject = *ao @@ -1221,7 +1577,7 @@ func TestBuildAmpObject(t *testing.T) { { description: "Wrong tag_id, error gets logged", inTagId: "unknown", - inStoredRequest: json.RawMessage(`{"id":"some-request-id","site":{"page":"prebid.org"},"imp":[{"id":"some-impression-id","banner":{"format":[{"w":300,"h":250}]},"ext":{"appnexus":{"placementId":12883451}}}],"tmax":500}`), + inStoredRequest: json.RawMessage(`{"id":"some-request-id","site":{"page":"prebid.org"},"imp":[{"id":"some-impression-id","banner":{"format":[{"w":300,"h":250}]},"ext":{"prebid":{"bidder":{"appnexus":{"placementId":12883451}}}}}],"tmax":500}`), expectedAmpObject: &analytics.AmpObject{ Status: http.StatusOK, Errors: []error{fmt.Errorf("unexpected end of JSON input")}, @@ -1230,7 +1586,7 @@ func TestBuildAmpObject(t *testing.T) { { description: "Valid stored Amp request, correct tag_id, a valid response should be logged", inTagId: "test", - inStoredRequest: json.RawMessage(`{"id":"some-request-id","site":{"page":"prebid.org"},"imp":[{"id":"some-impression-id","banner":{"format":[{"w":300,"h":250}]},"ext":{"appnexus":{"placementId":12883451}}}],"tmax":500}`), + inStoredRequest: json.RawMessage(`{"id":"some-request-id","site":{"page":"prebid.org"},"imp":[{"id":"some-impression-id","banner":{"format":[{"w":300,"h":250}]},"ext":{"prebid":{"bidder":{"appnexus":{"placementId":12883451}}}}}],"tmax":500}`), expectedAmpObject: &analytics.AmpObject{ Status: http.StatusOK, Errors: nil, @@ -1240,9 +1596,8 @@ func TestBuildAmpObject(t *testing.T) { IP: "192.0.2.1", }, Site: &openrtb2.Site{ - Page: "prebid.org", - Publisher: &openrtb2.Publisher{}, - Ext: json.RawMessage(`{"amp":1}`), + Page: "prebid.org", + Ext: json.RawMessage(`{"amp":1}`), }, Imp: []openrtb2.Imp{ { @@ -1256,7 +1611,7 @@ func TestBuildAmpObject(t *testing.T) { }, }, Secure: func(val int8) *int8 { return &val }(1), //(*int8)(1), - Ext: json.RawMessage(`{"appnexus":{"placementId":12883451}}`), + Ext: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placementId":12883451}}}}`), }, }, AT: 1, @@ -1362,7 +1717,7 @@ func TestIdGeneration(t *testing.T) { func ampObjectTestSetup(t *testing.T, inTagId string, inStoredRequest json.RawMessage, generateRequestID bool) (*analytics.AmpObject, httprouter.Handle) { actualAmpObject := analytics.AmpObject{} - logger := newMockLogger(&actualAmpObject) + logger := newMockLogger(&actualAmpObject, nil) mockAmpFetcher := &mockAmpStoredReqFetcher{ data: map[string]json.RawMessage{ @@ -1383,6 +1738,7 @@ func ampObjectTestSetup(t *testing.T, inTagId string, inStoredRequest json.RawMe []byte{}, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, + hooks.EmptyPlanBuilder{}, ) return &actualAmpObject, endpoint } @@ -1434,6 +1790,7 @@ func TestAmpAuctionResponseHeaders(t *testing.T) { []byte{}, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, + hooks.EmptyPlanBuilder{}, ) for _, test := range testCases { @@ -1449,3 +1806,197 @@ func TestAmpAuctionResponseHeaders(t *testing.T) { assert.Equal(t, expectedHeaders, recorder.Result().Header, test.description+":statuscode") } } + +func TestRequestWithTargeting(t *testing.T) { + stored := map[string]json.RawMessage{ + "1": json.RawMessage(validRequest(t, "site.json")), + } + exchange := &mockAmpExchange{} + endpoint, _ := NewAmpEndpoint( + fakeUUIDGenerator{}, + exchange, + newParamsValidator(t), + &mockAmpStoredReqFetcher{stored}, + empty_fetcher.EmptyFetcher{}, + &config.Configuration{MaxRequestSize: maxSize}, + &metricsConfig.NilMetricsEngine{}, + analyticsConf.NewPBSAnalytics(&config.Analytics{}), + nil, + nil, + openrtb_ext.BuildBidderMap(), + empty_fetcher.EmptyFetcher{}, + hooks.EmptyPlanBuilder{}, + ) + url, err := url.Parse("/openrtb2/auction/amp") + assert.NoError(t, err, "unexpected error received while parsing url") + values := url.Query() + values.Add("targeting", `{"gam-key1":"val1", "gam-key2":"val2"}`) + values.Add("tag_id", "1") + url.RawQuery = values.Encode() + + request, err := http.NewRequest("GET", url.String(), nil) + if !assert.NoError(t, err) { + return + } + recorder := httptest.NewRecorder() + endpoint(recorder, request, nil) + + if assert.NotNil(t, exchange.lastRequest, "Endpoint responded with %d: %s", recorder.Code, recorder.Body.String()) { + assert.JSONEq(t, `{"prebid":{"bidder":{"appnexus":{"placementId":12883451}}}, "data":{"gam-key1":"val1", "gam-key2":"val2"}}`, string(exchange.lastRequest.Imp[0].Ext)) + } +} + +func TestSetTargeting(t *testing.T) { + tests := []struct { + description string + bidRequest openrtb2.BidRequest + targeting string + expectedImpExt string + wantError bool + errorMessage string + }{ + { + description: "valid imp ext, valid targeting data", + bidRequest: openrtb2.BidRequest{Imp: []openrtb2.Imp{{Ext: []byte(`{"appnexus":{"placementId":123}}`)}}}, + targeting: `{"gam-key1":"val1", "gam-key2":"val2"}`, + expectedImpExt: `{"appnexus":{"placementId":123}, "data": {"gam-key1":"val1", "gam-key2":"val2"}}`, + wantError: false, + errorMessage: "", + }, + { + description: "valid imp ext, empty targeting data", + bidRequest: openrtb2.BidRequest{Imp: []openrtb2.Imp{{Ext: []byte(`{"appnexus":{"placementId":123}}`)}}}, + targeting: ``, + expectedImpExt: `{"appnexus":{"placementId":123}}`, + wantError: false, + errorMessage: "", + }, + { + description: "empty imp ext, valid targeting data", + bidRequest: openrtb2.BidRequest{Imp: []openrtb2.Imp{{Ext: []byte(`{}`)}}}, + targeting: `{"gam-key1":"val1", "gam-key2":"val2"}`, + expectedImpExt: `{"data": {"gam-key1":"val1", "gam-key2":"val2"}}`, + wantError: false, + errorMessage: "", + }, + { + description: "nil imp ext, valid targeting data", + bidRequest: openrtb2.BidRequest{Imp: []openrtb2.Imp{{Ext: nil}}}, + targeting: `{"gam-key1":"val1", "gam-key2":"val2"}`, + expectedImpExt: `{"data": {"gam-key1":"val1", "gam-key2":"val2"}}`, + wantError: false, + errorMessage: "", + }, + { + description: "imp ext has data, valid targeting data", + bidRequest: openrtb2.BidRequest{Imp: []openrtb2.Imp{{Ext: []byte(`{"data":{"placementId":123}}`)}}}, + targeting: `{"gam-key1":"val1", "gam-key2":"val2"}`, + expectedImpExt: `{"data": {"gam-key1":"val1", "gam-key2":"val2", "placementId":123}}`, + wantError: false, + errorMessage: "", + }, + { + description: "imp ext has data and other fields, valid targeting data", + bidRequest: openrtb2.BidRequest{Imp: []openrtb2.Imp{{Ext: []byte(`{"data":{"placementId":123}, "prebid": 123}`)}}}, + targeting: `{"gam-key1":"val1", "gam-key2":"val2"}`, + expectedImpExt: `{"data": {"gam-key1":"val1", "gam-key2":"val2", "placementId":123}, "prebid":123}`, + wantError: false, + errorMessage: "", + }, + { + description: "imp ext has invalid format, valid targeting data", + bidRequest: openrtb2.BidRequest{Imp: []openrtb2.Imp{{Ext: []byte(`{123:{}`)}}}, + targeting: `{"gam-key1":"val1", "gam-key2":"val2"}`, + expectedImpExt: ``, + wantError: true, + errorMessage: "unable to merge imp.ext with targeting data, check targeting data is correct: Invalid JSON Document", + }, + { + description: "valid imp ext, invalid targeting data", + bidRequest: openrtb2.BidRequest{Imp: []openrtb2.Imp{{Ext: []byte(`{"appnexus":{"placementId":123}}`)}}}, + targeting: `{123,}`, + expectedImpExt: ``, + wantError: true, + errorMessage: "unable to merge imp.ext with targeting data, check targeting data is correct: Invalid JSON Patch", + }, + } + + for _, test := range tests { + req := &test.bidRequest + err := setTargeting(req, test.targeting) + if test.wantError { + assert.EqualErrorf(t, err, test.errorMessage, "error is incorrect for test case: %s", test.description) + } else { + assert.NoError(t, err, "error should be nil for test case: %s", test.description) + assert.JSONEq(t, test.expectedImpExt, string(req.Imp[0].Ext), "incorrect impression extension returned for test %s", test.description) + } + + } +} + +func TestValidAmpResponseWhenRequestStagesRejected(t *testing.T) { + const nbr int = 123 + const file string = "sample-requests/amp/valid-supplementary/aliased-buyeruids.json" + + var test testCase + fileData, err := os.ReadFile(file) + assert.NoError(t, err, "Failed to read test file.") + if err := json.Unmarshal(fileData, &test); err != nil { + t.Fatal("Failed to unmarshal test file.") + } + + testCases := []struct { + description string + file string + planBuilder hooks.ExecutionPlanBuilder + expectedAmpResponse AmpResponse + }{ + { + description: "Assert correct BidResponse when request rejected at entrypoint stage", + file: file, + planBuilder: mockPlanBuilder{entrypointPlan: makeRejectPlan[hookstage.Entrypoint](mockRejectionHook{nbr})}, + expectedAmpResponse: AmpResponse{Targeting: map[string]string{}}, + }, + { + description: "Assert correct BidResponse when request rejected at raw-auction stage", + file: file, + planBuilder: mockPlanBuilder{rawAuctionPlan: makeRejectPlan[hookstage.RawAuctionRequest](mockRejectionHook{nbr})}, + expectedAmpResponse: test.ExpectedAmpResponse, + }, + { + description: "Assert correct AmpResponse when request rejected at processed-auction stage", + file: file, + planBuilder: mockPlanBuilder{processedAuctionPlan: makeRejectPlan[hookstage.ProcessedAuctionRequest](mockRejectionHook{nbr})}, + expectedAmpResponse: AmpResponse{Targeting: map[string]string{}}, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?%s", test.Query), nil) + recorder := httptest.NewRecorder() + query := request.URL.Query() + tagID := query.Get("tag_id") + + test.storedRequest = map[string]json.RawMessage{tagID: test.BidRequest} + test.planBuilder = tc.planBuilder + test.endpointType = AMP_ENDPOINT + + ampEndpointHandler, _, mockBidServers, mockCurrencyRatesServer, err := buildTestEndpoint(test, &config.Configuration{MaxRequestSize: maxSize}) + assert.NoError(t, err, "Failed to build test endpoint.") + + ampEndpointHandler(recorder, request, nil) + assert.Equal(t, recorder.Code, http.StatusOK, "Endpoint should return 200 OK.") + + var actualResp AmpResponse + assert.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &actualResp), "Unable to unmarshal actual BidResponse.") + assert.Equal(t, tc.expectedAmpResponse, actualResp, "Invalid AMP Response.") + + // Close servers regardless if the test case was run or not + for _, mockBidServer := range mockBidServers { + mockBidServer.Close() + } + mockCurrencyRatesServer.Close() + }) + } +} diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 05a020a8f5f..fe6422654ab 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -5,32 +5,35 @@ import ( "encoding/json" "errors" "fmt" - "github.com/prebid/prebid-server/gdpr" "io" - "io/ioutil" "net/http" "net/url" "regexp" "strconv" + "strings" "time" "github.com/buger/jsonparser" "github.com/gofrs/uuid" "github.com/golang/glog" "github.com/julienschmidt/httprouter" - "github.com/mxmCherry/openrtb/v15/native1" - nativeRequests "github.com/mxmCherry/openrtb/v15/native1/request" - "github.com/mxmCherry/openrtb/v15/openrtb2" + gpplib "github.com/prebid/go-gpp" + "github.com/prebid/openrtb/v17/adcom1" + "github.com/prebid/openrtb/v17/native1" + nativeRequests "github.com/prebid/openrtb/v17/native1/request" + "github.com/prebid/openrtb/v17/openrtb2" + "github.com/prebid/openrtb/v17/openrtb3" + "github.com/prebid/prebid-server/hooks" "golang.org/x/net/publicsuffix" jsonpatch "gopkg.in/evanphx/json-patch.v4" 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/currency" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/exchange" + "github.com/prebid/prebid-server/hooks/hookexecution" "github.com/prebid/prebid-server/metrics" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/prebid_cache_client" @@ -55,8 +58,19 @@ var ( dntKey string = http.CanonicalHeaderKey("DNT") dntDisabled int8 = 0 dntEnabled int8 = 1 + notAmp int8 = 0 ) +var accountIdSearchPath = [...]struct { + isApp bool + key []string +}{ + {true, []string{"app", "publisher", "ext", openrtb_ext.PrebidExtKey, "parentAccount"}}, + {true, []string{"app", "publisher", "id"}}, + {false, []string{"site", "publisher", "ext", openrtb_ext.PrebidExtKey, "parentAccount"}}, + {false, []string{"site", "publisher", "id"}}, +} + func NewEndpoint( uuidGenerator uuidutil.UUIDGenerator, ex exchange.Exchange, @@ -64,14 +78,15 @@ func NewEndpoint( requestsById stored_requests.Fetcher, accounts stored_requests.AccountFetcher, cfg *config.Configuration, - met metrics.MetricsEngine, + metricsEngine metrics.MetricsEngine, pbsAnalytics analytics.PBSAnalyticsModule, disabledBidders map[string]string, defReqJSON []byte, bidderMap map[string]openrtb_ext.BidderName, storedRespFetcher stored_requests.Fetcher, + hookExecutionPlanBuilder hooks.ExecutionPlanBuilder, ) (httprouter.Handle, error) { - if ex == nil || validator == nil || requestsById == nil || accounts == nil || cfg == nil || met == nil { + if ex == nil || validator == nil || requestsById == nil || accounts == nil || cfg == nil || metricsEngine == nil { return nil, errors.New("NewEndpoint requires non-nil arguments.") } @@ -82,6 +97,8 @@ func NewEndpoint( IPv6PrivateNetworks: cfg.RequestValidation.IPv6PrivateNetworksParsed, } + hookExecutor := hookexecution.NewHookExecutor(hookExecutionPlanBuilder, hookexecution.EndpointAuction, metricsEngine) + return httprouter.Handle((&endpointDeps{ uuidGenerator, ex, @@ -90,7 +107,7 @@ func NewEndpoint( empty_fetcher.EmptyFetcher{}, accounts, cfg, - met, + metricsEngine, pbsAnalytics, disabledBidders, defRequest, @@ -99,7 +116,8 @@ func NewEndpoint( nil, nil, ipValidator, - storedRespFetcher}).Auction), nil + storedRespFetcher, + hookExecutor}).Auction), nil } type endpointDeps struct { @@ -120,6 +138,7 @@ type endpointDeps struct { debugLogRegexp *regexp.Regexp privateNetworkIPValidator iputil.IPValidator storedRespFetcher stored_requests.Fetcher + hookExecutor hookexecution.HookStageExecutor } func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { @@ -152,11 +171,16 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http w.Header().Set("X-Prebid", version.BuildXPrebidHeader(version.Ver)) - req, impExtInfoMap, storedAuctionResponses, storedBidResponses, errL := deps.parseRequest(r) + req, impExtInfoMap, storedAuctionResponses, storedBidResponses, bidderImpReplaceImp, account, errL := deps.parseRequest(r, &labels) if errortypes.ContainsFatalError(errL) && writeError(errL, w, &labels) { return } + if rejectErr := hookexecution.FindFirstRejectOrNil(errL); rejectErr != nil { + labels, ao = rejectAuctionRequest(*rejectErr, w, req.BidRequest, labels, ao) + return + } + ctx := context.Background() timeout := deps.cfg.AuctionTimeouts.LimitAuctionTimeout(time.Duration(req.TMax) * time.Millisecond) @@ -167,26 +191,12 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http } 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 req.Site != nil { 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 - account, acctIDErrs := accountService.GetAccount(ctx, deps.cfg, deps.accounts, labels.PubID) - if len(acctIDErrs) > 0 { - errL = append(errL, acctIDErrs...) - writeError(errL, w, &labels) - return } // Set Integration Information @@ -212,15 +222,16 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http ImpExtInfoMap: impExtInfoMap, StoredAuctionResponses: storedAuctionResponses, StoredBidResponses: storedBidResponses, - TCF2ConfigBuilder: gdpr.NewTCF2Config, - GDPRPermissionsBuilder: gdpr.NewPermissions, + BidderImpReplaceImpID: bidderImpReplaceImp, + PubID: labels.PubID, + HookExecutor: deps.hookExecutor, } - response, err := deps.ex.HoldAuction(ctx, auctionRequest, nil) ao.Request = req.BidRequest ao.Response = response ao.Account = account - if err != nil { + rejectErr, isRejectErr := hookexecution.CastRejectErr(err) + if err != nil && !isRejectErr { if errortypes.ReadCode(err) == errortypes.BadInputErrorCode { writeError([]error{err}, w, &labels) return @@ -232,8 +243,38 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http ao.Status = http.StatusInternalServerError ao.Errors = append(ao.Errors, err) return + } else if isRejectErr { + labels, ao = rejectAuctionRequest(*rejectErr, w, req.BidRequest, labels, ao) + return + } + + labels, ao = sendAuctionResponse(w, response, labels, ao) +} + +func rejectAuctionRequest( + rejectErr hookexecution.RejectError, + w http.ResponseWriter, + request *openrtb2.BidRequest, + labels metrics.Labels, + ao analytics.AuctionObject, +) (metrics.Labels, analytics.AuctionObject) { + response := &openrtb2.BidResponse{NBR: openrtb3.NoBidReason(rejectErr.NBR).Ptr()} + if request != nil { + response.ID = request.ID } + ao.Response = response + ao.Errors = append(ao.Errors, rejectErr) + + return sendAuctionResponse(w, response, labels, ao) +} + +func sendAuctionResponse( + w http.ResponseWriter, + response *openrtb2.BidResponse, + labels metrics.Labels, + ao analytics.AuctionObject, +) (metrics.Labels, analytics.AuctionObject) { // Fixes #231 enc := json.NewEncoder(w) enc.SetEscapeHTML(false) @@ -247,6 +288,8 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http labels.RequestStatus = metrics.RequestStatusNetworkErr ao.Errors = append(ao.Errors, fmt.Errorf("/openrtb2/auction Failed to send response: %v", err)) } + + return labels, ao } // parseRequest turns the HTTP request into an OpenRTB request. This is guaranteed to return: @@ -259,7 +302,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, impExtInfoMap map[string]exchange.ImpExtInfo, storedAuctionResponses stored_responses.ImpsWithBidResponses, storedBidResponses stored_responses.ImpBidderStoredResp, errs []error) { +func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metrics.Labels) (req *openrtb_ext.RequestWrapper, impExtInfoMap map[string]exchange.ImpExtInfo, storedAuctionResponses stored_responses.ImpsWithBidResponses, storedBidResponses stored_responses.ImpBidderStoredResp, bidderImpReplaceImpId stored_responses.BidderImpReplaceImpID, account *config.Account, errs []error) { req = &openrtb_ext.RequestWrapper{} req.BidRequest = &openrtb2.BidRequest{} errs = nil @@ -269,37 +312,93 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request) (req *openrtb_ R: httpRequest.Body, N: deps.cfg.MaxRequestSize, } - requestJson, err := ioutil.ReadAll(lr) + requestJson, err := io.ReadAll(lr) if err != nil { errs = []error{err} return } // If the request size was too large, read through the rest of the request body so that the connection can be reused. if lr.N <= 0 { - if written, err := io.Copy(ioutil.Discard, httpRequest.Body); written > 0 || err != nil { + if written, err := io.Copy(io.Discard, httpRequest.Body); written > 0 || err != nil { errs = []error{fmt.Errorf("Request size exceeded max size of %d bytes.", deps.cfg.MaxRequestSize)} return } } + requestJson, rejectErr := deps.hookExecutor.ExecuteEntrypointStage(httpRequest, requestJson) + if rejectErr != nil { + errs = []error{rejectErr} + if err = json.Unmarshal(requestJson, req.BidRequest); err != nil { + glog.Errorf("Failed to unmarshal BidRequest during entrypoint rejection: %s", err) + } + return + } + timeout := parseTimeout(requestJson, time.Duration(storedRequestTimeoutMillis)*time.Millisecond) ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() impInfo, errs := parseImpInfo(requestJson) if len(errs) > 0 { - return nil, nil, nil, nil, errs + return nil, nil, nil, nil, nil, nil, errs + } + + storedBidRequestId, hasStoredBidRequest, storedRequests, storedImps, errs := deps.getStoredRequests(ctx, requestJson, impInfo) + if len(errs) > 0 { + return + } + + accountId, isAppReq, errs := getAccountIdFromRawRequest(hasStoredBidRequest, storedRequests[storedBidRequestId], requestJson) + // fill labels here in order to pass correct metrics in case of errors + if isAppReq { + labels.Source = metrics.DemandApp + labels.RType = metrics.ReqTypeORTB2App + labels.PubID = accountId + } else { // is Site request + labels.Source = metrics.DemandWeb + labels.PubID = accountId + } + if errs != nil { + return + } + + // Look up account + account, errs = accountService.GetAccount(ctx, deps.cfg, deps.accounts, accountId) + if len(errs) > 0 { + return + } + + deps.hookExecutor.SetAccount(account) + requestJson, rejectErr = deps.hookExecutor.ExecuteRawAuctionStage(requestJson) + if rejectErr != nil { + errs = []error{rejectErr} + if err = json.Unmarshal(requestJson, req.BidRequest); err != nil { + glog.Errorf("Failed to unmarshal BidRequest during raw auction stage rejection: %s", err) + } + return + } + + // retrieve storedRequests and storedImps once more in case stored data was changed by the raw auction hook + if hasPayloadUpdatesAt(hooks.StageRawAuctionRequest.String(), deps.hookExecutor.GetOutcomes()) { + impInfo, errs = parseImpInfo(requestJson) + if len(errs) > 0 { + return nil, nil, nil, nil, nil, nil, errs + } + storedBidRequestId, hasStoredBidRequest, storedRequests, storedImps, errs = deps.getStoredRequests(ctx, requestJson, impInfo) + if len(errs) > 0 { + return + } } // Fetch the Stored Request data and merge it into the HTTP request. - if requestJson, impExtInfoMap, errs = deps.processStoredRequests(ctx, requestJson, impInfo); len(errs) > 0 { + if requestJson, impExtInfoMap, errs = deps.processStoredRequests(requestJson, impInfo, storedRequests, storedImps, storedBidRequestId, hasStoredBidRequest); len(errs) > 0 { return } //Stored auction responses should be processed after stored requests due to possible impression modification - storedAuctionResponses, storedBidResponses, errs = stored_responses.ProcessStoredResponses(ctx, requestJson, deps.storedRespFetcher, deps.bidderMap) + storedAuctionResponses, storedBidResponses, bidderImpReplaceImpId, errs = stored_responses.ProcessStoredResponses(ctx, requestJson, deps.storedRespFetcher, deps.bidderMap) if len(errs) > 0 { - return nil, nil, nil, nil, errs + return nil, nil, nil, nil, nil, nil, errs } if err := json.Unmarshal(requestJson, req.BidRequest); err != nil { @@ -313,7 +412,7 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request) (req *openrtb_ } // Populate any "missing" OpenRTB fields with info from other sources, (e.g. HTTP request headers). - deps.setFieldsImplicitly(httpRequest, req.BidRequest) + deps.setFieldsImplicitly(httpRequest, req) if err := processInterstitials(req); err != nil { errs = []error{err} @@ -334,6 +433,26 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request) (req *openrtb_ return } +// hasPayloadUpdatesAt checks if there are any successful payload updates at given stage +func hasPayloadUpdatesAt(stageName string, outcomes []hookexecution.StageOutcome) bool { + for _, outcome := range outcomes { + if stageName != outcome.Stage { + continue + } + + for _, group := range outcome.Groups { + for _, invocationResult := range group.InvocationResults { + if invocationResult.Status == hookexecution.StatusSuccess && + invocationResult.Action == hookexecution.ActionUpdate { + return true + } + } + } + } + + return false +} + // parseTimeout returns parses tmax from the requestJson, or returns the default if it doesn't exist. // // requestJson should be the content of the POST body. @@ -352,135 +471,143 @@ func parseTimeout(requestJson []byte, defaultTimeout time.Duration) time.Duratio // mergeBidderParams merges bidder parameters in req.ext down to the imp[].ext level, with // priority given to imp[].ext in case of a conflict. No validation of bidder parameters or // of the ext json is performed. Unmarshal errors are not expected since the ext json was -// validated during the bid request unmarshal. If there is an unmarshal error for some reason, -// we rely on downstream validation to return the error. +// validated during the bid request unmarshal. func mergeBidderParams(req *openrtb_ext.RequestWrapper) error { - reqBidderParams, err := adapters.ExtractReqExtBidderParamsEmbeddedMap(req.BidRequest) - if err != nil || len(reqBidderParams) == 0 { + reqExt, err := req.GetRequestExt() + if err != nil { return nil } - imps := make([]openrtb2.Imp, 0, len(req.BidRequest.Imp)) - for impIndex, imp := range req.BidRequest.Imp { - updatedImp := imp + prebid := reqExt.GetPrebid() + if prebid == nil { + return nil + } - if len(imp.Ext) == 0 { - imps = append(imps, updatedImp) - continue - } + bidderParamsJson := prebid.BidderParams + if len(bidderParamsJson) == 0 { + return nil + } - var impExt map[string]json.RawMessage - if err := json.Unmarshal(imp.Ext, &impExt); err != nil { - // imp.ext should be parsable, but we haven't validated yet so there may be another issue. ignore it here - // and let the validation catch it later on. - imps = append(imps, updatedImp) - continue - } + bidderParams := map[string]map[string]json.RawMessage{} + if err := json.Unmarshal(bidderParamsJson, &bidderParams); err != nil { + return nil + } - //merges bidder parameters passed at req.ext level with imp[].ext level. - impExtModified, err := addMissingReqExtParamsInImpExt(impExt, reqBidderParams) + for i, imp := range req.GetImp() { + impExt, err := imp.GetImpExt() if err != nil { - return fmt.Errorf("error processing bidder parameters for imp[%d]: %s", impIndex, err.Error()) + continue } - //merges bidder parameters passed at req.ext level with imp[].ext.prebid.bidder level. - impExtPrebidModified, err := addMissingReqExtParamsInImpExtPrebid(impExt, reqBidderParams) - if err != nil { - return fmt.Errorf("error processing bidder parameters for imp[%d]: %s", impIndex, err.Error()) + // merges bidder parameters passed at req.ext level with imp[].ext.BIDDER level + if err := mergeBidderParamsImpExt(impExt, bidderParams); err != nil { + return fmt.Errorf("error processing bidder parameters for imp[%d]: %s", i, err.Error()) } - if impExtModified || impExtPrebidModified { - updatedImpExt, err := json.Marshal(impExt) - if err != nil { - return fmt.Errorf("error processing bidder parameters for imp[%d]: error marshalling ext:%s", impIndex, err.Error()) - } - updatedImp.Ext = updatedImpExt + // merges bidder parameters passed at req.ext level with imp[].ext.prebid.bidder.BIDDER level + if err := mergeBidderParamsImpExtPrebid(impExt, bidderParams); err != nil { + return fmt.Errorf("error processing bidder parameters for imp[%d]: %s", i, err.Error()) } - - imps = append(imps, updatedImp) } - req.BidRequest.Imp = imps return nil } -// addMissingReqExtParamsInImpExtPrebid merges bidder parameters passed at req.ext level with imp[].ext.prebid.bidder level. -func addMissingReqExtParamsInImpExtPrebid(impExtByKey map[string]json.RawMessage, reqExtParams map[string]map[string]json.RawMessage) (bool, error) { - if _, prebidSectionExists := impExtByKey["prebid"]; !prebidSectionExists { - return false, nil - } +// mergeBidderParamsImpExt merges bidder parameters in req.ext down to the imp[].ext.BIDDER +// level, giving priority to imp[].ext.BIDDER in case of a conflict. Unmarshal errors are not +// expected since the ext json was validated during the bid request unmarshal. +func mergeBidderParamsImpExt(impExt *openrtb_ext.ImpExt, reqExtParams map[string]map[string]json.RawMessage) error { + extMap := impExt.GetExt() + extMapModified := false - var prebidByKey map[string]json.RawMessage - if err := json.Unmarshal(impExtByKey["prebid"], &prebidByKey); err != nil { - // prebid should be parsable, but we haven't validated yet so there may be another issue. ignore it here - // and let the validation catch it later on. - return false, nil - } - - if _, prebidBidderSectionExists := prebidByKey["bidder"]; !prebidBidderSectionExists { - return false, nil - } + for bidder, params := range reqExtParams { + if !isPossibleBidder(bidder) { + continue + } - var prebidBidderByBidder map[string]json.RawMessage - if err := json.Unmarshal(prebidByKey["bidder"], &prebidBidderByBidder); err != nil { - // added for completeness, but not possible. would be caught by the earlier unmarshal call. - return false, nil - } + impExtBidder, impExtBidderExists := extMap[bidder] + if !impExtBidderExists || impExtBidder == nil { + continue + } - anyModified, err := addMissingReqExtParamsInImpExt(prebidBidderByBidder, reqExtParams) + impExtBidderMap := map[string]json.RawMessage{} + if len(impExtBidder) > 0 { + if err := json.Unmarshal(impExtBidder, &impExtBidderMap); err != nil { + continue + } + } - if anyModified && err == nil { - // marshal imp.ext.prebid.bidder - prebidBidderJSON, err := json.Marshal(prebidBidderByBidder) - if err != nil { - return anyModified, err + modified := false + for key, value := range params { + if _, present := impExtBidderMap[key]; !present { + impExtBidderMap[key] = value + modified = true + } } - prebidByKey["bidder"] = prebidBidderJSON - // marshal imp.ext.prebid - prebidJSON, err := json.Marshal(prebidByKey) - if err != nil { - return anyModified, err + if modified { + impExtBidderJson, err := json.Marshal(impExtBidderMap) + if err != nil { + return fmt.Errorf("error marshalling ext.BIDDER: %s", err.Error()) + } + extMap[bidder] = impExtBidderJson + extMapModified = true } - impExtByKey["prebid"] = prebidJSON } - return anyModified, err + if extMapModified { + impExt.SetExt(extMap) + } + + return nil } -// addMissingReqExtParamsInImpExt merges bidder parameters passed at req.ext level with imp[].ext level. -func addMissingReqExtParamsInImpExt(impExtByBidder map[string]json.RawMessage, reqExtParams map[string]map[string]json.RawMessage) (bool, error) { - anyModified := false - for bidder, bidderExt := range impExtByBidder { - if !isBidderToValidate(bidder) { +// mergeBidderParamsImpExtPrebid merges bidder parameters in req.ext down to the imp[].ext.prebid.bidder.BIDDER +// level, giving priority to imp[].ext.prebid.bidder.BIDDER in case of a conflict. +func mergeBidderParamsImpExtPrebid(impExt *openrtb_ext.ImpExt, reqExtParams map[string]map[string]json.RawMessage) error { + prebid := impExt.GetPrebid() + prebidModified := false + + if prebid == nil || len(prebid.Bidder) == 0 { + return nil + } + + for bidder, params := range reqExtParams { + impExtPrebidBidder, impExtPrebidBidderExists := prebid.Bidder[bidder] + if !impExtPrebidBidderExists || impExtPrebidBidder == nil { continue } - var params map[string]json.RawMessage - if err := json.Unmarshal(bidderExt, ¶ms); err != nil { - // bidder should be parsable, but we haven't validated yet so there may be another issue. ignore it here - // and let the validation later on catch it. - continue + impExtPrebidBidderMap := map[string]json.RawMessage{} + if len(impExtPrebidBidder) > 0 { + if err := json.Unmarshal(impExtPrebidBidder, &impExtPrebidBidderMap); err != nil { + continue + } } modified := false - for key, value := range reqExtParams[bidder] { - if _, present := params[key]; !present { - params[key] = value + for key, value := range params { + if _, present := impExtPrebidBidderMap[key]; !present { + impExtPrebidBidderMap[key] = value modified = true } } if modified { - paramsJson, err := json.Marshal(params) + impExtPrebidBidderJson, err := json.Marshal(impExtPrebidBidderMap) if err != nil { - return anyModified, fmt.Errorf("error marshalling ext.prebid.bidder: %s", err.Error()) + return fmt.Errorf("error marshalling ext.prebid.bidder.BIDDER: %s", err.Error()) } - impExtByBidder[bidder] = paramsJson - anyModified = true + prebid.Bidder[bidder] = impExtPrebidBidderJson + prebidModified = true } } - return anyModified, nil + + if prebidModified { + impExt.SetPrebid(prebid) + } + + return nil } func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp bool, hasStoredResponses bool, storedBidResp stored_responses.ImpBidderStoredResp) []error { @@ -493,7 +620,7 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp return []error{fmt.Errorf("request.tmax must be nonnegative. Got %d", req.TMax)} } - if len(req.Imp) < 1 { + if req.LenImp() < 1 { return []error{errors.New("request.imp must contain at least one element.")} } @@ -515,6 +642,7 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp 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} @@ -558,6 +686,10 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp return append(errL, errors.New("request.site or request.app must be defined, but not both.")) } + if err := validateRequestExt(req); err != nil { + return append(errL, err) + } + if err := deps.validateSite(req); err != nil { return append(errL, err) } @@ -578,7 +710,17 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp return append(errL, err) } - if ccpaPolicy, err := ccpa.ReadFromRequestWrapper(req); err != nil { + var gpp gpplib.GppContainer + if req.BidRequest.Regs != nil && len(req.BidRequest.Regs.GPP) > 0 { + gpp, err = gpplib.Parse(req.BidRequest.Regs.GPP) + if err != nil { + errL = append(errL, &errortypes.Warning{ + Message: fmt.Sprintf("GPP consent string is invalid and will be ignored. (%v)", err), + WarningCode: errortypes.InvalidPrivacyConsentWarningCode}) + } + } + + if ccpaPolicy, err := ccpa.ReadFromRequestWrapper(req, gpp); err != nil { return append(errL, err) } else if _, err := ccpaPolicy.Parse(exchange.GetValidBidders(aliases)); err != nil { if _, invalidConsent := err.(*errortypes.Warning); invalidConsent { @@ -595,14 +737,15 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp } } - impIDs := make(map[string]int, len(req.Imp)) - for index := range req.Imp { - imp := &req.Imp[index] + impIDs := make(map[string]int, req.LenImp()) + for i, imp := range req.GetImp() { + // check for unique imp id if firstIndex, ok := impIDs[imp.ID]; ok { - errL = append(errL, fmt.Errorf(`request.imp[%d].id and request.imp[%d].id are both "%s". Imp IDs must be unique.`, firstIndex, index, imp.ID)) + errL = append(errL, fmt.Errorf(`request.imp[%d].id and request.imp[%d].id are both "%s". Imp IDs must be unique.`, firstIndex, i, imp.ID)) } - impIDs[imp.ID] = index - errs := deps.validateImp(imp, aliases, index, hasStoredResponses, storedBidResp) + impIDs[imp.ID] = i + + errs := deps.validateImp(imp, aliases, i, hasStoredResponses, storedBidResp) if len(errs) > 0 { errL = append(errL, errs...) } @@ -718,7 +861,7 @@ func validateBidders(bidders []string, knownBidders map[string]openrtb_ext.Bidde return nil } -func (deps *endpointDeps) validateImp(imp *openrtb2.Imp, aliases map[string]string, index int, hasStoredResponses bool, storedBidResp stored_responses.ImpBidderStoredResp) []error { +func (deps *endpointDeps) validateImp(imp *openrtb_ext.ImpWrapper, aliases map[string]string, index int, hasStoredResponses bool, storedBidResp stored_responses.ImpBidderStoredResp) []error { if imp.ID == "" { return []error{fmt.Errorf("request.imp[%d] missing required field: \"id\"", index)} } @@ -759,7 +902,7 @@ func (deps *endpointDeps) validateImp(imp *openrtb2.Imp, aliases map[string]stri return nil } -func isInterstitial(imp *openrtb2.Imp) bool { +func isInterstitial(imp *openrtb_ext.ImpWrapper) bool { return imp.Instl == 1 } @@ -1101,7 +1244,7 @@ func validateNativeAssetData(data *nativeRequests.Data, impIndex int, assetIndex return nil } -func validateNativeVideoProtocols(protocols []native1.Protocol, impIndex int, assetIndex int) error { +func validateNativeVideoProtocols(protocols []adcom1.MediaCreativeSubtype, impIndex int, assetIndex int) error { if len(protocols) < 1 { return fmt.Errorf("request.imp[%d].native.request.assets[%d].video.protocols must be an array with at least one element", impIndex, assetIndex) } @@ -1113,8 +1256,8 @@ func validateNativeVideoProtocols(protocols []native1.Protocol, impIndex int, as return nil } -func validateNativeVideoProtocol(protocol native1.Protocol, impIndex int, assetIndex int, protocolIndex int) error { - if protocol < native1.ProtocolVAST10 || protocol > native1.ProtocolDAAST10Wrapper { +func validateNativeVideoProtocol(protocol adcom1.MediaCreativeSubtype, impIndex int, assetIndex int, protocolIndex int) error { + if protocol < adcom1.CreativeVAST10 || protocol > adcom1.CreativeDAAST10Wrapper { return fmt.Errorf("request.imp[%d].native.request.assets[%d].video.protocols[%d] is invalid. See Section 5.8: https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-API-Specification-Version-2-5-FINAL.pdf#page=52", impIndex, assetIndex, protocolIndex) } return nil @@ -1170,87 +1313,88 @@ func validatePmp(pmp *openrtb2.PMP, impIndex int) error { return nil } -func (deps *endpointDeps) validateImpExt(imp *openrtb2.Imp, aliases map[string]string, impIndex int, hasStoredResponses bool, storedBidResp stored_responses.ImpBidderStoredResp) []error { - errL := []error{} +func (deps *endpointDeps) validateImpExt(imp *openrtb_ext.ImpWrapper, aliases map[string]string, impIndex int, hasStoredResponses bool, storedBidResp stored_responses.ImpBidderStoredResp) []error { if len(imp.Ext) == 0 { return []error{fmt.Errorf("request.imp[%d].ext is required", impIndex)} } - var bidderExts map[string]json.RawMessage - if err := json.Unmarshal(imp.Ext, &bidderExts); err != nil { + impExt, err := imp.GetImpExt() + if err != nil { return []error{err} } - // Prefer bidder params from request.imp.ext.prebid.bidder.BIDDER over request.imp.ext.BIDDER - // to avoid confusion beteween prebid specific adapter config and other ext protocols. - var extPrebid openrtb_ext.ExtImpPrebid - if extPrebidJSON, ok := bidderExts[openrtb_ext.PrebidExtKey]; ok { - if err := json.Unmarshal(extPrebidJSON, &extPrebid); err == nil && extPrebid.Bidder != nil { - for bidder, ext := range extPrebid.Bidder { - if ext == nil { - continue - } - bidderExts[bidder] = ext + prebid := impExt.GetOrCreatePrebid() + prebidModified := false + + if prebid.Bidder == nil { + prebid.Bidder = make(map[string]json.RawMessage) + } + + ext := impExt.GetExt() + extModified := false + + // promote imp[].ext.BIDDER to newer imp[].ext.prebid.bidder.BIDDER location, with the later taking precedence + for k, v := range ext { + if isPossibleBidder(k) { + if _, exists := prebid.Bidder[k]; !exists { + prebid.Bidder[k] = v + prebidModified = true } + delete(ext, k) + extModified = true } } - if hasStoredResponses && extPrebid.StoredAuctionResponse == nil { + + if hasStoredResponses && prebid.StoredAuctionResponse == nil { return []error{fmt.Errorf("request validation failed. The StoredAuctionResponse.ID field must be completely present with, or completely absent from, all impressions in request. No StoredAuctionResponse data found for request.imp[%d].ext.prebid \n", impIndex)} } if len(storedBidResp) > 0 { - if err := validateStoredBidRespAndImpExtBidders(bidderExts, storedBidResp, imp.ID); len(err) > 0 { - return err + if err := validateStoredBidRespAndImpExtBidders(prebid.Bidder, storedBidResp, imp.ID); err != nil { + return []error{err} } } - /* Process all the bidder exts in the request */ - disabledBidders := []string{} - otherExtElements := 0 - for bidder, ext := range bidderExts { - if isBidderToValidate(bidder) { - coreBidder := bidder - if tmp, isAlias := aliases[bidder]; isAlias { - coreBidder = tmp + errL := []error{} + + for bidder, ext := range prebid.Bidder { + coreBidder := bidder + if tmp, isAlias := aliases[bidder]; isAlias { + coreBidder = tmp + } + + if coreBidderNormalized, isValid := deps.bidderMap[coreBidder]; isValid { + if err := deps.paramsValidator.Validate(coreBidderNormalized, ext); err != nil { + return []error{fmt.Errorf("request.imp[%d].ext.prebid.bidder.%s failed validation.\n%v", impIndex, bidder, err)} } - if bidderName, isValid := deps.bidderMap[coreBidder]; isValid { - if err := deps.paramsValidator.Validate(bidderName, ext); err != nil { - return []error{fmt.Errorf("request.imp[%d].ext.%s failed validation.\n%v", impIndex, coreBidder, err)} - } + } else { + if msg, isDisabled := deps.disabledBidders[bidder]; isDisabled { + errL = append(errL, &errortypes.BidderTemporarilyDisabled{Message: msg}) + delete(prebid.Bidder, bidder) + prebidModified = true } else { - if msg, isDisabled := deps.disabledBidders[bidder]; isDisabled { - errL = append(errL, &errortypes.BidderTemporarilyDisabled{Message: msg}) - disabledBidders = append(disabledBidders, bidder) - } else { - return []error{fmt.Errorf("request.imp[%d].ext contains unknown bidder: %s. Did you forget an alias in request.ext.prebid.aliases?", impIndex, bidder)} - } + return []error{fmt.Errorf("request.imp[%d].ext.prebid.bidder contains unknown bidder: %s. Did you forget an alias in request.ext.prebid.aliases?", impIndex, bidder)} } - } else { - otherExtElements++ } } - // defer deleting disabled bidders so we don't disrupt the loop - if len(disabledBidders) > 0 { - for _, bidder := range disabledBidders { - delete(bidderExts, bidder) - } - extJSON, err := json.Marshal(bidderExts) - if err != nil { - return []error{err} - } - imp.Ext = extJSON + if len(prebid.Bidder) == 0 { + errL = append(errL, fmt.Errorf("request.imp[%d].ext.prebid.bidder must contain at least one bidder", impIndex)) + return errL } - if len(bidderExts)-otherExtElements == 0 { - errL = append(errL, fmt.Errorf("request.imp[%d].ext must contain at least one bidder", impIndex)) + if prebidModified { + impExt.SetPrebid(prebid) + } + if extModified { + impExt.SetExt(ext) } return errL } -// isBidderToValidate determines if the bidder name in request.imp[].prebid should be validated. -func isBidderToValidate(bidder string) bool { +// isPossibleBidder determines if a bidder name is a potential real bidder. +func isPossibleBidder(bidder string) bool { switch openrtb_ext.BidderName(bidder) { case openrtb_ext.BidderReservedContext: return false @@ -1262,6 +1406,8 @@ func isBidderToValidate(bidder string) bool { return false case openrtb_ext.BidderReservedSKAdN: return false + case openrtb_ext.BidderReservedTID: + return false default: return true } @@ -1305,6 +1451,20 @@ func (deps *endpointDeps) validateAliasesGVLIDs(aliasesGVLIDs map[string]uint16, return nil } +func validateRequestExt(req *openrtb_ext.RequestWrapper) error { + reqExt, err := req.GetRequestExt() + if err != nil { + return err + } + + prebid := reqExt.GetPrebid() + if prebid != nil && prebid.Cache != nil && (prebid.Cache.Bids == nil && prebid.Cache.VastXML == nil) { + return errors.New(`request.ext is invalid: request.ext.prebid.cache requires one of the "bids" or "vastxml" properties`) + } + + return nil +} + func (deps *endpointDeps) validateSite(req *openrtb_ext.RequestWrapper) error { if req.Site == nil { return nil @@ -1318,7 +1478,7 @@ func (deps *endpointDeps) validateSite(req *openrtb_ext.RequestWrapper) error { return err } siteAmp := siteExt.GetAmp() - if siteAmp < 0 || siteAmp > 1 { + if siteAmp != nil && (*siteAmp < 0 || *siteAmp > 1) { return errors.New(`request.site.ext.amp must be either 1, 0, or undefined`) } @@ -1383,17 +1543,13 @@ func (deps *endpointDeps) validateUser(req *openrtb_ext.RequestWrapper, aliases } 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 len(eid.UIDs) == 0 { + return fmt.Errorf("request.user.ext.eids[%d].uids must contain at least one element or be undefined", 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) - } + + 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) } } } @@ -1407,11 +1563,12 @@ func validateRegs(req *openrtb_ext.RequestWrapper) error { 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.") + + gdpr := regsExt.GetGDPR() + if gdpr != nil && *gdpr != 0 && *gdpr != 1 { + return errors.New("request.regs.ext.gdpr must be either 0 or 1") } + return nil } @@ -1478,7 +1635,7 @@ func fillChannel(reqWrapper *openrtb_ext.RequestWrapper, isAmp bool) error { } -func sanitizeRequest(r *openrtb2.BidRequest, ipValidator iputil.IPValidator) { +func sanitizeRequest(r *openrtb_ext.RequestWrapper, ipValidator iputil.IPValidator) { if r.Device != nil { if ip, ver := iputil.ParseIP(r.Device.IP); ip == nil || ver != iputil.IPv4 || !ipValidator.IsValid(ip, ver) { r.Device.IP = "" @@ -1495,64 +1652,85 @@ func sanitizeRequest(r *openrtb2.BidRequest, ipValidator iputil.IPValidator) { // OpenRTB properties from the headers and other implicit info. // // This function _should not_ override any fields which were defined explicitly by the caller in the request. -func (deps *endpointDeps) setFieldsImplicitly(httpReq *http.Request, bidReq *openrtb2.BidRequest) { - sanitizeRequest(bidReq, deps.privateNetworkIPValidator) +func (deps *endpointDeps) setFieldsImplicitly(httpReq *http.Request, r *openrtb_ext.RequestWrapper) { + sanitizeRequest(r, deps.privateNetworkIPValidator) - setDeviceImplicitly(httpReq, bidReq, deps.privateNetworkIPValidator) + setDeviceImplicitly(httpReq, r, deps.privateNetworkIPValidator) - // Per the OpenRTB spec: A bid request must not contain both a Site and an App object. - if bidReq.App == nil { - setSiteImplicitly(httpReq, bidReq) + // Per the OpenRTB spec: A bid request must not contain both a Site and an App object. If neither are + // present, we'll assume it's a site request. + if r.App == nil { + setSiteImplicitly(httpReq, r) } - setImpsImplicitly(httpReq, bidReq.Imp) + setImpsImplicitly(httpReq, r.GetImp()) - setAuctionTypeImplicitly(bidReq) + setAuctionTypeImplicitly(r) } // setDeviceImplicitly uses implicit info from httpReq to populate bidReq.Device -func setDeviceImplicitly(httpReq *http.Request, bidReq *openrtb2.BidRequest, ipValidtor iputil.IPValidator) { - setIPImplicitly(httpReq, bidReq, ipValidtor) - setUAImplicitly(httpReq, bidReq) - setDoNotTrackImplicitly(httpReq, bidReq) +func setDeviceImplicitly(httpReq *http.Request, r *openrtb_ext.RequestWrapper, ipValidtor iputil.IPValidator) { + setIPImplicitly(httpReq, r, ipValidtor) + setUAImplicitly(httpReq, r) + setDoNotTrackImplicitly(httpReq, r) } // setAuctionTypeImplicitly sets the auction type to 1 if it wasn't on the request, // since header bidding is generally a first-price auction. -func setAuctionTypeImplicitly(bidReq *openrtb2.BidRequest) { - if bidReq.AT == 0 { - bidReq.AT = 1 +func setAuctionTypeImplicitly(r *openrtb_ext.RequestWrapper) { + if r.AT == 0 { + r.AT = 1 } - return } -// setSiteImplicitly uses implicit info from httpReq to populate bidReq.Site -func setSiteImplicitly(httpReq *http.Request, bidReq *openrtb2.BidRequest) { - if bidReq.Site == nil || bidReq.Site.Page == "" || bidReq.Site.Domain == "" { - referrerCandidate := httpReq.Referer() - if parsedUrl, err := url.Parse(referrerCandidate); err == nil { - if domain, err := publicsuffix.EffectiveTLDPlusOne(parsedUrl.Host); err == nil { - if bidReq.Site == nil { - bidReq.Site = &openrtb2.Site{} - } - if bidReq.Site.Domain == "" { - bidReq.Site.Domain = domain - } +func setSiteImplicitly(httpReq *http.Request, r *openrtb_ext.RequestWrapper) { + referrerCandidate := httpReq.Referer() + if referrerCandidate == "" && r.Site != nil && r.Site.Page != "" { + referrerCandidate = r.Site.Page // If http referer is disabled and thus has empty value - use site.page instead + } - // This looks weird... but is not a bug. The site which called prebid-server (the "referer"), is - // (almost certainly) the page where the ad will be hosted. In the OpenRTB spec, this is *page*, not *ref*. - if bidReq.Site.Page == "" { - bidReq.Site.Page = referrerCandidate - } + if referrerCandidate != "" { + setSitePageIfEmpty(r.Site, referrerCandidate) + if parsedUrl, err := url.Parse(referrerCandidate); err == nil { + setSiteDomainIfEmpty(r.Site, parsedUrl.Host) + if publisherDomain, err := publicsuffix.EffectiveTLDPlusOne(parsedUrl.Host); err == nil { + setSitePublisherDomainIfEmpty(r.Site, publisherDomain) } } } - if bidReq.Site != nil { - setAmpExt(bidReq.Site, "0") + + if r.Site != nil { + if siteExt, err := r.GetSiteExt(); err == nil && siteExt.GetAmp() == nil { + siteExt.SetAmp(¬Amp) + } } } -func setImpsImplicitly(httpReq *http.Request, imps []openrtb2.Imp) { +func setSitePageIfEmpty(site *openrtb2.Site, sitePage string) { + if site == nil { + site = &openrtb2.Site{} + } + if site.Page == "" { + site.Page = sitePage + } +} + +func setSiteDomainIfEmpty(site *openrtb2.Site, siteDomain string) { + if site.Domain == "" { + site.Domain = siteDomain + } +} + +func setSitePublisherDomainIfEmpty(site *openrtb2.Site, publisherDomain string) { + if site.Publisher == nil { + site.Publisher = &openrtb2.Publisher{} + } + if site.Publisher.Domain == "" { + site.Publisher.Domain = publisherDomain + } +} + +func setImpsImplicitly(httpReq *http.Request, imps []*openrtb_ext.ImpWrapper) { secure := int8(1) for i := 0; i < len(imps); i++ { if imps[i].Secure == nil && httputil.IsSecure(httpReq) { @@ -1578,11 +1756,11 @@ func getJsonSyntaxError(testJSON []byte) (bool, string) { return false, "" } -func (deps *endpointDeps) processStoredRequests(ctx context.Context, requestJson []byte, impInfo []ImpExtPrebidData) ([]byte, map[string]exchange.ImpExtInfo, []error) { +func (deps *endpointDeps) getStoredRequests(ctx context.Context, requestJson []byte, impInfo []ImpExtPrebidData) (string, bool, map[string]json.RawMessage, map[string]json.RawMessage, []error) { // Parse the Stored Request IDs from the BidRequest and Imps. storedBidRequestId, hasStoredBidRequest, err := getStoredRequestId(requestJson) if err != nil { - return nil, nil, []error{err} + return "", false, nil, nil, []error{err} } // Fetch the Stored Request data @@ -1604,10 +1782,14 @@ 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 + return "", false, nil, nil, errs } + + return storedBidRequestId, hasStoredBidRequest, storedRequests, storedImps, errs +} + +func (deps *endpointDeps) processStoredRequests(requestJson []byte, impInfo []ImpExtPrebidData, storedRequests map[string]json.RawMessage, storedImps map[string]json.RawMessage, storedBidRequestId string, hasStoredBidRequest bool) ([]byte, map[string]exchange.ImpExtInfo, []error) { bidRequestID, err := getBidRequestID(storedRequests[storedBidRequestId]) if err != nil { return nil, nil, []error{err} @@ -1685,7 +1867,6 @@ func (deps *endpointDeps) processStoredRequests(ctx context.Context, requestJson return nil, nil, []error{err} } resolvedImps = append(resolvedImps, resolvedImp) - impId, err := jsonparser.GetString(resolvedImp, "id") if err != nil { return nil, nil, []error{err} @@ -1695,12 +1876,25 @@ func (deps *endpointDeps) processStoredRequests(ctx context.Context, requestJson if impData.ImpExtPrebid.Options != nil { echoVideoAttributes = impData.ImpExtPrebid.Options.EchoVideoAttrs } - impExtInfoMap[impId] = exchange.ImpExtInfo{EchoVideoAttrs: echoVideoAttributes, StoredImp: storedImps[impData.ImpExtPrebid.StoredRequest.ID]} + + // Extract Passthrough from Merged Imp + passthrough, _, _, err := jsonparser.Get(resolvedImp, "ext", "prebid", "passthrough") + if err != nil && err != jsonparser.KeyPathNotFoundError { + return nil, nil, []error{err} + } + impExtInfoMap[impId] = exchange.ImpExtInfo{EchoVideoAttrs: echoVideoAttributes, StoredImp: storedImps[impData.ImpExtPrebid.StoredRequest.ID], Passthrough: passthrough} } else { resolvedImps = append(resolvedImps, impData.Imp) + impId, err := jsonparser.GetString(impData.Imp, "id") + if err != nil { + if err == jsonparser.KeyPathNotFoundError { + err = fmt.Errorf("request.imp[%d] missing required field: \"id\"\n", i) + } + return nil, nil, []error{err} + } + impExtInfoMap[impId] = exchange.ImpExtInfo{Passthrough: impData.ImpExtPrebid.Passthrough} } - } if len(resolvedImps) > 0 { newImpJson, err := json.Marshal(resolvedImps) @@ -1718,7 +1912,6 @@ func (deps *endpointDeps) processStoredRequests(ctx context.Context, requestJson // 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 { _, err = jsonparser.ArrayEach(impArray, func(imp []byte, _ jsonparser.ValueType, _ int, err error) { impExtData, _, _, err := jsonparser.Get(imp, "ext", "prebid") @@ -1771,50 +1964,50 @@ func getBidRequestID(data json.RawMessage) (string, error) { } // 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 == "") { +func setIPImplicitly(httpReq *http.Request, r *openrtb_ext.RequestWrapper, ipValidator iputil.IPValidator) { + if r.Device == nil || (r.Device.IP == "" && r.Device.IPv6 == "") { if ip, ver := httputil.FindIP(httpReq, ipValidator); ip != nil { switch ver { case iputil.IPv4: - if bidReq.Device == nil { - bidReq.Device = &openrtb2.Device{} + if r.Device == nil { + r.Device = &openrtb2.Device{} } - bidReq.Device.IP = ip.String() + r.Device.IP = ip.String() case iputil.IPv6: - if bidReq.Device == nil { - bidReq.Device = &openrtb2.Device{} + if r.Device == nil { + r.Device = &openrtb2.Device{} } - bidReq.Device.IPv6 = ip.String() + r.Device.IPv6 = ip.String() } } } } // setUAImplicitly sets the User Agent on bidReq, if it's not explicitly defined and it's defined on the request. -func setUAImplicitly(httpReq *http.Request, bidReq *openrtb2.BidRequest) { - if bidReq.Device == nil || bidReq.Device.UA == "" { +func setUAImplicitly(httpReq *http.Request, r *openrtb_ext.RequestWrapper) { + if r.Device == nil || r.Device.UA == "" { if ua := httpReq.UserAgent(); ua != "" { - if bidReq.Device == nil { - bidReq.Device = &openrtb2.Device{} + if r.Device == nil { + r.Device = &openrtb2.Device{} } - bidReq.Device.UA = ua + r.Device.UA = ua } } } -func setDoNotTrackImplicitly(httpReq *http.Request, bidReq *openrtb2.BidRequest) { - if bidReq.Device == nil || bidReq.Device.DNT == nil { +func setDoNotTrackImplicitly(httpReq *http.Request, r *openrtb_ext.RequestWrapper) { + if r.Device == nil || r.Device.DNT == nil { dnt := httpReq.Header.Get(dntKey) if dnt == "0" || dnt == "1" { - if bidReq.Device == nil { - bidReq.Device = &openrtb2.Device{} + if r.Device == nil { + r.Device = &openrtb2.Device{} } switch dnt { case "0": - bidReq.Device.DNT = &dntDisabled + r.Device.DNT = &dntDisabled case "1": - bidReq.Device.DNT = &dntEnabled + r.Device.DNT = &dntEnabled } } } @@ -1832,6 +2025,10 @@ func writeError(errs []error, w http.ResponseWriter, labels *metrics.Labels) boo httpStatus = http.StatusServiceUnavailable metricsStatus = metrics.RequestStatusBlacklisted break + } else if erVal == errortypes.MalformedAcctErrorCode { + httpStatus = http.StatusInternalServerError + metricsStatus = metrics.RequestStatusAccountConfigErr + break } } w.WriteHeader(httpStatus) @@ -1861,6 +2058,59 @@ func getAccountID(pub *openrtb2.Publisher) string { return metrics.PublisherUnknown } +func getAccountIdFromRawRequest(hasStoredRequest bool, storedRequest json.RawMessage, originalRequest []byte) (string, bool, []error) { + request := originalRequest + if hasStoredRequest { + request = storedRequest + } + + accountId, isAppReq, err := searchAccountId(request) + if err != nil { + return "", isAppReq, []error{err} + } + + // In case the stored request did not have account data we specifically search it in the original request + if accountId == "" && hasStoredRequest { + accountId, _, err = searchAccountId(originalRequest) + if err != nil { + return "", isAppReq, []error{err} + } + } + + if accountId == "" { + return metrics.PublisherUnknown, isAppReq, nil + } + + return accountId, isAppReq, nil +} + +func searchAccountId(request []byte) (string, bool, error) { + for _, path := range accountIdSearchPath { + accountId, exists, err := getStringValueFromRequest(request, path.key) + if err != nil { + return "", path.isApp, err + } + if exists { + return accountId, path.isApp, nil + } + } + return "", false, nil +} + +func getStringValueFromRequest(request []byte, key []string) (string, bool, error) { + val, dataType, _, err := jsonparser.Get(request, key...) + if dataType == jsonparser.NotExist { + return "", false, nil + } + if err != nil { + return "", false, err + } + if dataType != jsonparser.String { + return "", true, fmt.Errorf("%s must be a string", strings.Join(key, ".")) + } + return string(val), true, nil +} + 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)} @@ -1913,17 +2163,21 @@ func checkIfAppRequest(request []byte) (bool, error) { return false, nil } -func validateStoredBidRespAndImpExtBidders(bidderExts map[string]json.RawMessage, storedBidResp stored_responses.ImpBidderStoredResp, impId string) []error { +func validateStoredBidRespAndImpExtBidders(bidderExts map[string]json.RawMessage, storedBidResp stored_responses.ImpBidderStoredResp, impId string) error { if bidResponses, ok := storedBidResp[impId]; ok { - storedBidRespValidationError := []error{fmt.Errorf("request validation failed. Stored bid responses are specified for imp %s. Bidders specified in imp.ext should match with bidders specified in imp.ext.prebid.storedbidresponse", impId)} - if len(bidResponses) < len(bidderExts)-1 { // do not count "prebid" part of imp.ext. It is always present in imp.ext if stored bid responses are specified. There are can be other properties except of bidders and "prebid" - return storedBidRespValidationError + if len(bidResponses) != len(bidderExts) { + return generateStoredBidResponseValidationError(impId) } + for bidderName := range bidResponses { if _, present := bidderExts[bidderName]; !present { - return storedBidRespValidationError + return generateStoredBidResponseValidationError(impId) } } } return nil } + +func generateStoredBidResponseValidationError(impID string) error { + return fmt.Errorf("request validation failed. Stored bid responses are specified for imp %s. Bidders specified in imp.ext should match with bidders specified in imp.ext.prebid.storedbidresponse", impID) +} diff --git a/endpoints/openrtb2/auction_benchmark_test.go b/endpoints/openrtb2/auction_benchmark_test.go index eb50b57e585..15d70efa5a9 100644 --- a/endpoints/openrtb2/auction_benchmark_test.go +++ b/endpoints/openrtb2/auction_benchmark_test.go @@ -2,20 +2,21 @@ package openrtb2 import ( "bytes" - "context" "fmt" - "io/ioutil" "net/http" "net/http/httptest" + "os" "strings" "testing" "time" - "github.com/prebid/go-gdpr/vendorlist" 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/experiment/adscert" + "github.com/prebid/prebid-server/gdpr" + "github.com/prebid/prebid-server/hooks" 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" @@ -78,6 +79,13 @@ func BenchmarkOpenrtbEndpoint(b *testing.B) { b.Fatal("unable to build adapters") } + gdprPermsBuilder := fakePermissionsBuilder{ + permissions: &fakePermissions{}, + }.Builder + tcf2ConfigBuilder := fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + }.Builder + exchange := exchange.NewExchange( adapters, nil, @@ -85,9 +93,11 @@ func BenchmarkOpenrtbEndpoint(b *testing.B) { map[string]usersync.Syncer{}, nilMetrics, infos, - func(ctx context.Context, id uint16) (vendorlist.VendorList, error) { return nil, nil }, + gdprPermsBuilder, + tcf2ConfigBuilder, currency.NewRateConverter(&http.Client{}, "", time.Duration(0)), empty_fetcher.EmptyFetcher{}, + &adscert.NilSigner{}, ) endpoint, _ := NewEndpoint( @@ -103,6 +113,7 @@ func BenchmarkOpenrtbEndpoint(b *testing.B) { []byte{}, nil, empty_fetcher.EmptyFetcher{}, + hooks.EmptyPlanBuilder{}, ) b.ResetTimer() @@ -127,7 +138,7 @@ func BenchmarkValidWholeExemplary(b *testing.B) { b.Run(fmt.Sprintf("input_file_%s", testFile), func(b *testing.B) { b.StopTimer() // Set up - fileData, err := ioutil.ReadFile(testFile) + fileData, err := os.ReadFile(testFile) if err != nil { b.Fatalf("unable to read file %s", testFile) } @@ -146,7 +157,7 @@ func BenchmarkValidWholeExemplary(b *testing.B) { AccountRequired: test.Config.AccountRequired, } - auctionEndpointHandler, mockBidServers, mockCurrencyRatesServer, err := buildTestEndpoint(test, cfg) + auctionEndpointHandler, _, mockBidServers, mockCurrencyRatesServer, err := buildTestEndpoint(test, cfg) if err != nil { b.Fatal(err.Error()) } diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index cd2a065ffd4..a531d09cb6f 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -6,16 +6,25 @@ import ( "encoding/json" "errors" "io" - "io/ioutil" "net" "net/http" "net/http/httptest" + "os" "path/filepath" "strings" "testing" "time" + "github.com/buger/jsonparser" "github.com/julienschmidt/httprouter" + "github.com/prebid/openrtb/v17/native1" + nativeRequests "github.com/prebid/openrtb/v17/native1/request" + "github.com/prebid/openrtb/v17/openrtb2" + "github.com/prebid/prebid-server/hooks" + "github.com/prebid/prebid-server/hooks/hookexecution" + "github.com/prebid/prebid-server/hooks/hookstage" + "github.com/stretchr/testify/assert" + analyticsConf "github.com/prebid/prebid-server/analytics/config" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -26,12 +35,6 @@ import ( "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" "github.com/prebid/prebid-server/stored_responses" "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" ) func TestJsonSampleRequests(t *testing.T) { @@ -59,6 +62,10 @@ func TestJsonSampleRequests(t *testing.T) { "Makes sure we handle (default) aliased bidders properly", "aliased", }, + { + "Asserts we return 500s on requests referencing accounts with malformed configs.", + "account-malformed", + }, { "Asserts we return 503s on requests with blacklisted accounts and apps.", "blacklisted", @@ -105,7 +112,7 @@ func TestJsonSampleRequests(t *testing.T) { testCaseFiles, err := getTestFiles(filepath.Join("sample-requests", tc.sampleRequestsSubDir)) if assert.NoError(t, err, "Test case %s. Error reading files from directory %s \n", tc.description, tc.sampleRequestsSubDir) { for _, testFile := range testCaseFiles { - fileData, err := ioutil.ReadFile(testFile) + fileData, err := os.ReadFile(testFile) if assert.NoError(t, err, "Test case %s. Error reading file %s \n", tc.description, testFile) { // Retrieve test case input and expected output from JSON file test, err := parseTestFile(fileData, testFile) @@ -122,9 +129,10 @@ func TestJsonSampleRequests(t *testing.T) { cfg.BlacklistedAcctMap = test.Config.getBlackListedAccountMap() cfg.AccountRequired = test.Config.AccountRequired } + cfg.MarshalAccountDefaults() test.endpointType = OPENRTB_ENDPOINT - auctionEndpointHandler, mockBidServers, mockCurrencyRatesServer, err := buildTestEndpoint(test, cfg) + auctionEndpointHandler, _, mockBidServers, mockCurrencyRatesServer, err := buildTestEndpoint(test, cfg) if assert.NoError(t, err) { runTestCase(t, auctionEndpointHandler, test, fileData, testFile) } @@ -374,7 +382,8 @@ func TestExplicitUserId(t *testing.T) { map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), - empty_fetcher.EmptyFetcher{}) + empty_fetcher.EmptyFetcher{}, + hooks.EmptyPlanBuilder{}) endpoint(httptest.NewRecorder(), request, nil) @@ -409,9 +418,8 @@ func doBadAliasRequest(t *testing.T, filename string, expectMsg string) { // aliasJSON lacks a comma after the "appnexus" entry so is bad JSON aliasJSON := []byte(`{"ext":{"prebid":{"aliases": {"test1": "appnexus" "test2": "rubicon", "test3": "openx"}}}}`) - adaptersConfigs := make(map[string]config.Adapter) - bidderInfos := getBidderInfos(adaptersConfigs, openrtb_ext.CoreBidderNames()) + bidderInfos := getBidderInfos(nil, openrtb_ext.CoreBidderNames()) bidderMap := exchange.GetActiveBidders(bidderInfos) disabledBidders := exchange.GetDisabledBiddersErrorMessages(bidderInfos) @@ -430,7 +438,8 @@ func doBadAliasRequest(t *testing.T, filename string, expectMsg string) { disabledBidders, aliasJSON, bidderMap, - empty_fetcher.EmptyFetcher{}) + empty_fetcher.EmptyFetcher{}, + hooks.EmptyPlanBuilder{}) request := httptest.NewRequest("POST", "/openrtb2/auction", bytes.NewReader(testBidRequest)) recorder := httptest.NewRecorder() @@ -481,7 +490,8 @@ func TestNilExchange(t *testing.T) { analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), - empty_fetcher.EmptyFetcher{}) + empty_fetcher.EmptyFetcher{}, + hooks.EmptyPlanBuilder{}) if err == nil { t.Errorf("NewEndpoint should return an error when given a nil Exchange.") @@ -504,7 +514,8 @@ func TestNilValidator(t *testing.T) { map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), - empty_fetcher.EmptyFetcher{}) + empty_fetcher.EmptyFetcher{}, + hooks.EmptyPlanBuilder{}) if err == nil { t.Errorf("NewEndpoint should return an error when given a nil BidderParamValidator.") @@ -527,7 +538,8 @@ func TestExchangeError(t *testing.T) { map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), - empty_fetcher.EmptyFetcher{}) + empty_fetcher.EmptyFetcher{}, + hooks.EmptyPlanBuilder{}) request := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json"))) recorder := httptest.NewRecorder() @@ -542,7 +554,7 @@ func TestExchangeError(t *testing.T) { func TestUserAgentSetting(t *testing.T) { httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json"))) httpReq.Header.Set("User-Agent", "foo") - bidReq := &openrtb2.BidRequest{} + bidReq := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{}} setUAImplicitly(httpReq, bidReq) @@ -558,11 +570,11 @@ func TestUserAgentSetting(t *testing.T) { func TestUserAgentOverride(t *testing.T) { httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json"))) httpReq.Header.Set("User-Agent", "foo") - bidReq := &openrtb2.BidRequest{ + bidReq := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ Device: &openrtb2.Device{ UA: "bar", }, - } + }} setUAImplicitly(httpReq, bidReq) @@ -572,7 +584,7 @@ func TestUserAgentOverride(t *testing.T) { } func TestAuctionTypeDefault(t *testing.T) { - bidReq := &openrtb2.BidRequest{} + bidReq := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{}} setAuctionTypeImplicitly(bidReq) if bidReq.AT != 1 { @@ -651,7 +663,8 @@ func TestImplicitIPsEndToEnd(t *testing.T) { map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), - empty_fetcher.EmptyFetcher{}) + empty_fetcher.EmptyFetcher{}, + hooks.EmptyPlanBuilder{}) httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, test.reqJSONFile))) httpReq.Header.Set("X-Forwarded-For", test.xForwardedForHeader) @@ -785,8 +798,9 @@ func TestImplicitDNT(t *testing.T) { for _, test := range testCases { httpReq := httptest.NewRequest("POST", "/openrtb2/auction", nil) httpReq.Header.Set("DNT", test.dntHeader) - setDoNotTrackImplicitly(httpReq, &test.request) - assert.Equal(t, test.expectedRequest, test.request) + reqWrapper := &openrtb_ext.RequestWrapper{BidRequest: &test.request} + setDoNotTrackImplicitly(httpReq, reqWrapper) + assert.Equal(t, test.expectedRequest, *reqWrapper.BidRequest, test.description) } } @@ -847,7 +861,8 @@ func TestImplicitDNTEndToEnd(t *testing.T) { map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), - empty_fetcher.EmptyFetcher{}) + empty_fetcher.EmptyFetcher{}, + hooks.EmptyPlanBuilder{}) httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, test.reqJSONFile))) httpReq.Header.Set("DNT", test.dntHeader) @@ -864,13 +879,15 @@ func TestImplicitDNTEndToEnd(t *testing.T) { func TestImplicitSecure(t *testing.T) { httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json"))) - httpReq.Header.Set(http.CanonicalHeaderKey("X-Forwarded-Proto"), "https") + httpReq.Header.Set("X-Forwarded-Proto", "https") - imps := []openrtb2.Imp{ - {}, - {}, + imps := []*openrtb_ext.ImpWrapper{ + {Imp: &openrtb2.Imp{}}, + {Imp: &openrtb2.Imp{}}, } + setImpsImplicitly(httpReq, imps) + for _, imp := range imps { if imp.Secure == nil || *imp.Secure != 1 { t.Errorf("imp.Secure should be set to 1 if the request is https. Got %#v", imp.Secure) @@ -878,22 +895,83 @@ func TestImplicitSecure(t *testing.T) { } } -func TestRefererParsing(t *testing.T) { - httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json"))) - httpReq.Header.Set("Referer", "http://test.mysite.com") - bidReq := &openrtb2.BidRequest{} +func TestReferer(t *testing.T) { + testCases := []struct { + description string + givenSitePage string + givenSiteDomain string + givenPublisherDomain string + givenReferer string + expectedDomain string + expectedPage string + expectedPublisherDomain string + }{ + { + description: "site.page/domain are unchanged when site.page/domain and http referer are not set", + expectedDomain: "", + expectedPage: "", + expectedPublisherDomain: "", + }, + { + description: "site.page/domain are derived from referer when neither is set and http referer is set", + givenReferer: "https://test.somepage.com", + expectedDomain: "test.somepage.com", + expectedPublisherDomain: "somepage.com", + expectedPage: "https://test.somepage.com", + }, + { + description: "site.domain is derived from site.page when site.page is set and http referer is not set", + givenSitePage: "https://test.somepage.com", + expectedDomain: "test.somepage.com", + expectedPublisherDomain: "somepage.com", + expectedPage: "https://test.somepage.com", + }, + { + description: "site.domain is derived from http referer when site.page and http referer are set", + givenSitePage: "https://test.somepage.com", + givenReferer: "http://test.com", + expectedDomain: "test.com", + expectedPublisherDomain: "test.com", + expectedPage: "https://test.somepage.com", + }, + { + description: "site.page/domain are unchanged when site.page/domain and http referer are set", + givenSitePage: "https://test.somepage.com", + givenSiteDomain: "some.domain.com", + givenReferer: "http://test.com", + expectedDomain: "some.domain.com", + expectedPublisherDomain: "test.com", + expectedPage: "https://test.somepage.com", + }, + { + description: "Publisher domain shouldn't be overrwriten if already set", + givenSitePage: "https://test.somepage.com", + givenSiteDomain: "", + givenPublisherDomain: "differentpage.com", + expectedDomain: "test.somepage.com", + expectedPublisherDomain: "differentpage.com", + expectedPage: "https://test.somepage.com", + }, + } - setSiteImplicitly(httpReq, bidReq) + for _, test := range testCases { + httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json"))) + httpReq.Header.Set("Referer", test.givenReferer) + + bidReq := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Domain: test.givenSiteDomain, + Page: test.givenSitePage, + Publisher: &openrtb2.Publisher{Domain: test.givenPublisherDomain}, + }, + }} - if bidReq.Site == nil { - t.Fatalf("bidrequest.site should not be nil.") - } + setSiteImplicitly(httpReq, bidReq) - if bidReq.Site.Domain != "mysite.com" { - t.Errorf("Bad bidrequest.site.domain. Expected mysite.com, got %s", bidReq.Site.Domain) - } - if bidReq.Site.Page != "http://test.mysite.com" { - t.Errorf("Bad bidrequest.site.page. Expected mysite.com, got %s", bidReq.Site.Page) + assert.NotNil(t, bidReq.Site, test.description) + assert.Equal(t, test.expectedDomain, bidReq.Site.Domain, test.description) + assert.Equal(t, test.expectedPage, bidReq.Site.Page, test.description) + assert.Equal(t, test.expectedPublisherDomain, bidReq.Site.Publisher.Domain, test.description) } } @@ -917,6 +995,10 @@ func TestParseImpInfoSingleImpression(t *testing.T) { Imp: json.RawMessage(`{"id": "some-static-imp","video":{"mimes":["video/mp4"]},"ext": {"appnexus": {"placementId": "abc","position": "below"}}}`), ImpExtPrebid: openrtb_ext.ExtImpPrebid{}, }, + { + Imp: json.RawMessage(`{"id":"my-imp-id", "video":{"h":300, "w":200}, "ext":{"prebid":{"storedrequest": {"id": "6"}}}}`), + ImpExtPrebid: openrtb_ext.ExtImpPrebid{StoredRequest: &openrtb_ext.ExtStoredRequest{ID: "6"}}, + }, } for i, requestData := range testStoredRequests { @@ -1008,14 +1090,17 @@ func TestStoredRequests(t *testing.T) { nil, hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, + hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}), } - testStoreVideoAttr := []bool{true, true, false, false} + testStoreVideoAttr := []bool{true, true, false, false, false} for i, requestData := range testStoredRequests { 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) + storedBidRequestId, hasStoredBidRequest, storedRequests, storedImps, errs := deps.getStoredRequests(context.Background(), json.RawMessage(requestData), impInfo) + assert.Len(t, errs, 0, "No errors should be returned") + newRequest, impExtInfoMap, errList := deps.processStoredRequests(json.RawMessage(requestData), impInfo, storedRequests, storedImps, storedBidRequestId, hasStoredBidRequest) if len(errList) != 0 { for _, err := range errList { if err != nil { @@ -1026,7 +1111,6 @@ func TestStoredRequests(t *testing.T) { } } expectJson := json.RawMessage(testFinalRequests[i]) - assert.JSONEq(t, string(expectJson), string(newRequest), "Incorrect result request %d", i) expectedImp := testStoredImpIds[i] @@ -1155,180 +1239,209 @@ func TestMergeBidderParams(t *testing.T) { } for _, test := range testCases { - actualErr := mergeBidderParams(&openrtb_ext.RequestWrapper{BidRequest: &test.givenRequest}) + w := &openrtb_ext.RequestWrapper{BidRequest: &test.givenRequest} + actualErr := mergeBidderParams(w) // errors are only possible from the marshal operation, which is not testable assert.NoError(t, actualErr, test.description+":err") + // rebuild request before asserting value + assert.NoError(t, w.RebuildRequest(), test.description+":rebuild_request") + assert.Equal(t, test.givenRequest.Imp, test.expectedRequestImps, test.description+":imps") } } -func TestAddMissingReqExtParamsInImpExtPrebid(t *testing.T) { +func TestMergeBidderParamsImpExt(t *testing.T) { testCases := []struct { - description string - givenImpExtByBidder map[string]json.RawMessage - givenReqExtParams map[string]map[string]json.RawMessage - expectedModified bool - expectedImpExtByBidder map[string]json.RawMessage + description string + givenImpExt map[string]json.RawMessage + givenReqExtParams map[string]map[string]json.RawMessage + expectedModified bool + expectedImpExt map[string]json.RawMessage }{ { - description: "No Prebid Section", - givenImpExtByBidder: map[string]json.RawMessage{}, - givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}}, - expectedModified: false, - expectedImpExtByBidder: map[string]json.RawMessage{}, - }, - { - description: "Malformed Prebid Section", - givenImpExtByBidder: map[string]json.RawMessage{"prebid": json.RawMessage(`malformed`)}, - givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}}, - expectedModified: false, - expectedImpExtByBidder: map[string]json.RawMessage{"prebid": json.RawMessage(`malformed`)}, + description: "One Bidder - Modified (no collision)", + givenImpExt: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1}`)}, + givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}}, + expectedModified: true, + expectedImpExt: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`)}, }, { - description: "No Prebid Bidder Section", - givenImpExtByBidder: map[string]json.RawMessage{"prebid": json.RawMessage(`{}`)}, - givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}}, - expectedModified: false, - expectedImpExtByBidder: map[string]json.RawMessage{"prebid": json.RawMessage(`{}`)}, + description: "One Bidder - Modified (imp.ext bidder empty)", + givenImpExt: map[string]json.RawMessage{"bidder1": json.RawMessage(`{}`)}, + givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}}, + expectedModified: true, + expectedImpExt: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"b":2}`)}, }, { - description: "Malformed Prebid Bidder Section", - givenImpExtByBidder: map[string]json.RawMessage{"prebid": json.RawMessage(`{"bidder": malformed}`)}, - givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}}, - expectedModified: false, - expectedImpExtByBidder: map[string]json.RawMessage{"prebid": json.RawMessage(`{"bidder": malformed}`)}, + description: "One Bidder - Not Modified (imp.ext bidder not defined)", + givenImpExt: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`)}, + givenReqExtParams: map[string]map[string]json.RawMessage{"bidder-not-defined": {"b": json.RawMessage(`4`)}}, + expectedModified: false, + expectedImpExt: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`)}, }, { - description: "One Bidder - Modified (no collision)", - givenImpExtByBidder: map[string]json.RawMessage{"prebid": json.RawMessage(`{"bidder":{"bidder1":{"a":1}}}`)}, - givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}}, - expectedModified: true, - expectedImpExtByBidder: map[string]json.RawMessage{"prebid": json.RawMessage(`{"bidder":{"bidder1":{"a":1,"b":2}}}`)}, + description: "One Bidder - Not Modified (imp.ext bidder nil)", + givenImpExt: map[string]json.RawMessage{"bidder1": nil}, + givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`4`)}}, + expectedModified: false, + expectedImpExt: map[string]json.RawMessage{"bidder1": nil}, }, { - description: "One Bidder - Not Modified (imp.ext wins)", - givenImpExtByBidder: map[string]json.RawMessage{"prebid": json.RawMessage(`{"bidder":{"bidder1":{"a":1,"b":2}}}`)}, - givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`4`)}}, - expectedModified: false, - expectedImpExtByBidder: map[string]json.RawMessage{"prebid": json.RawMessage(`{"bidder":{"bidder1":{"a":1,"b":2}}}`)}, + description: "One Bidder - Not Modified (imp.ext wins)", + givenImpExt: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`)}, + givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`4`)}}, + expectedModified: false, + expectedImpExt: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`)}, }, { - description: "One Bidder - Not Modified (reserved bidder ignored)", - givenImpExtByBidder: map[string]json.RawMessage{"prebid": json.RawMessage(`{"bidder":{"gpid":{"a":1}}}`)}, - givenReqExtParams: map[string]map[string]json.RawMessage{"gpid": {"b": json.RawMessage(`2`)}}, - expectedModified: false, - expectedImpExtByBidder: map[string]json.RawMessage{"prebid": json.RawMessage(`{"bidder":{"gpid":{"a":1}}}`)}, + description: "One Bidder - Not Modified (reserved bidder ignored)", + givenImpExt: map[string]json.RawMessage{"gpid": json.RawMessage(`{"a":1}`)}, + givenReqExtParams: map[string]map[string]json.RawMessage{"gpid": {"b": json.RawMessage(`2`)}}, + expectedModified: false, + expectedImpExt: map[string]json.RawMessage{"gpid": json.RawMessage(`{"a":1}`)}, }, { - description: "One Bidder - Not Modified (reserved bidder ignored - not embedded object)", - givenImpExtByBidder: map[string]json.RawMessage{"prebid": json.RawMessage(`{"bidder":{"gpid":1}}`)}, - givenReqExtParams: map[string]map[string]json.RawMessage{"gpid": {"b": json.RawMessage(`2`)}}, - expectedModified: false, - expectedImpExtByBidder: map[string]json.RawMessage{"prebid": json.RawMessage(`{"bidder":{"gpid":1}}`)}, + description: "One Bidder - Not Modified (reserved bidder ignored - not embedded object)", + givenImpExt: map[string]json.RawMessage{"gpid": json.RawMessage(`1`)}, + givenReqExtParams: map[string]map[string]json.RawMessage{"gpid": {"b": json.RawMessage(`2`)}}, + expectedModified: false, + expectedImpExt: map[string]json.RawMessage{"gpid": json.RawMessage(`1`)}, }, { - description: "One Bidder - Not Modified (malformed ignored)", - givenImpExtByBidder: map[string]json.RawMessage{"prebid": json.RawMessage(`{"bidder":{"bidder1":malformed}}`)}, - givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}}, - expectedModified: false, - expectedImpExtByBidder: map[string]json.RawMessage{"prebid": json.RawMessage(`{"bidder":{"bidder1":malformed}}`)}, + description: "One Bidder - Not Modified (malformed ignored)", + givenImpExt: map[string]json.RawMessage{"bidder1": json.RawMessage(`malformed`)}, + givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}}, + expectedModified: false, + expectedImpExt: map[string]json.RawMessage{"bidder1": json.RawMessage(`malformed`)}, }, { - description: "Multiple Bidders - Mixed", - givenImpExtByBidder: map[string]json.RawMessage{"prebid": json.RawMessage(`{"bidder":{"bidder1":{"a":1},"bidder2":{"a":"one","b":"two"}}}`)}, - givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}, "bidder2": {"b": json.RawMessage(`"three"`)}}, - expectedModified: true, - expectedImpExtByBidder: map[string]json.RawMessage{"prebid": json.RawMessage(`{"bidder":{"bidder1":{"a":1,"b":2},"bidder2":{"a":"one","b":"two"}}}`)}, + description: "Multiple Bidders - Mixed", + givenImpExt: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1}`), "bidder2": json.RawMessage(`{"a":"one","b":"two"}`)}, + givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}, "bidder2": {"b": json.RawMessage(`"three"`)}}, + expectedModified: true, + expectedImpExt: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`), "bidder2": json.RawMessage(`{"a":"one","b":"two"}`)}, }, { - description: "Multiple Bidders - None Modified", - givenImpExtByBidder: map[string]json.RawMessage{"prebid": json.RawMessage(`{"bidder":{"bidder1":{"a":1},"bidder2":{"a":"one","b":"two"}}}`)}, - givenReqExtParams: map[string]map[string]json.RawMessage{}, - expectedModified: false, - expectedImpExtByBidder: map[string]json.RawMessage{"prebid": json.RawMessage(`{"bidder":{"bidder1":{"a":1},"bidder2":{"a":"one","b":"two"}}}`)}, + description: "Multiple Bidders - None Modified", + givenImpExt: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1}`), "bidder2": json.RawMessage(`{"a":"one","b":"two"}`)}, + givenReqExtParams: map[string]map[string]json.RawMessage{}, + expectedModified: false, + expectedImpExt: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1}`), "bidder2": json.RawMessage(`{"a":"one","b":"two"}`)}, }, } for _, test := range testCases { - actualModified, actualErr := addMissingReqExtParamsInImpExtPrebid(test.givenImpExtByBidder, test.givenReqExtParams) + impExt := openrtb_ext.CreateImpExtForTesting(test.givenImpExt, nil) + + err := mergeBidderParamsImpExt(&impExt, test.givenReqExtParams) // errors are only possible from the marshal operation, which is not testable - assert.NoError(t, actualErr, test.description+":err") + assert.NoError(t, err, test.description+":err") - assert.Equal(t, test.expectedModified, actualModified, test.description+":modified") - assert.Equal(t, test.expectedImpExtByBidder, test.givenImpExtByBidder, test.description+":imp.ext") + assert.Equal(t, test.expectedModified, impExt.Dirty(), test.description+":modified") + assert.Equal(t, test.expectedImpExt, impExt.GetExt(), test.description+":imp.ext") } } -func TestAddMissingReqExtParamsInImpExt(t *testing.T) { +func TestMergeBidderParamsImpExtPrebid(t *testing.T) { testCases := []struct { - description string - givenImpExtByBidder map[string]json.RawMessage - givenReqExtParams map[string]map[string]json.RawMessage - expectedModified bool - expectedImpExtByBidder map[string]json.RawMessage + description string + givenImpExtPrebid *openrtb_ext.ExtImpPrebid + givenReqExtParams map[string]map[string]json.RawMessage + expectedModified bool + expectedImpExtPrebid *openrtb_ext.ExtImpPrebid }{ { - description: "One Bidder - Modified (no collision)", - givenImpExtByBidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1}`)}, - givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}}, - expectedModified: true, - expectedImpExtByBidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`)}, + description: "No Prebid Section", + givenImpExtPrebid: nil, + givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}}, + expectedModified: false, + expectedImpExtPrebid: nil, + }, + { + description: "No Prebid Bidder Section", + givenImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: nil}, + givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}}, + expectedModified: false, + expectedImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: nil}, }, { - description: "One Bidder - Not Modified (imp.ext wins)", - givenImpExtByBidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`)}, - givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`4`)}}, - expectedModified: false, - expectedImpExtByBidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`)}, + description: "Empty Prebid Bidder Section", + givenImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{}}, + givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}}, + expectedModified: false, + expectedImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{}}, }, { - description: "One Bidder - Not Modified (reserved bidder ignored)", - givenImpExtByBidder: map[string]json.RawMessage{"gpid": json.RawMessage(`{"a":1}`)}, - givenReqExtParams: map[string]map[string]json.RawMessage{"gpid": {"b": json.RawMessage(`2`)}}, - expectedModified: false, - expectedImpExtByBidder: map[string]json.RawMessage{"gpid": json.RawMessage(`{"a":1}`)}, + description: "One Bidder - Modified (no collision)", + givenImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1}`)}}, + givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}}, + expectedModified: true, + expectedImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`)}}, }, { - description: "One Bidder - Not Modified (reserved bidder ignored - not embedded object)", - givenImpExtByBidder: map[string]json.RawMessage{"gpid": json.RawMessage(`1`)}, - givenReqExtParams: map[string]map[string]json.RawMessage{"gpid": {"b": json.RawMessage(`2`)}}, - expectedModified: false, - expectedImpExtByBidder: map[string]json.RawMessage{"gpid": json.RawMessage(`1`)}, + description: "One Bidder - Modified (imp.ext bidder empty)", + givenImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{}`)}}, + givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}}, + expectedModified: true, + expectedImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"b":2}`)}}, }, { - description: "One Bidder - Not Modified (malformed ignored)", - givenImpExtByBidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`malformed`)}, - givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}}, - expectedModified: false, - expectedImpExtByBidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`malformed`)}, + description: "One Bidder - Not Modified (imp.ext wins)", + givenImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`)}}, + givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`4`)}}, + expectedModified: false, + expectedImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`)}}, }, { - description: "Multiple Bidders - Mixed", - givenImpExtByBidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1}`), "bidder2": json.RawMessage(`{"a":"one","b":"two"}`)}, - givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}, "bidder2": {"b": json.RawMessage(`"three"`)}}, - expectedModified: true, - expectedImpExtByBidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`), "bidder2": json.RawMessage(`{"a":"one","b":"two"}`)}, + description: "One Bidder - Not Modified (imp.ext bidder not defined)", + givenImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`)}}, + givenReqExtParams: map[string]map[string]json.RawMessage{"bidder-not-defined": {"b": json.RawMessage(`4`)}}, + expectedModified: false, + expectedImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`)}}, }, { - description: "Multiple Bidders - None Modified", - givenImpExtByBidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1}`), "bidder2": json.RawMessage(`{"a":"one","b":"two"}`)}, - givenReqExtParams: map[string]map[string]json.RawMessage{}, - expectedModified: false, - expectedImpExtByBidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1}`), "bidder2": json.RawMessage(`{"a":"one","b":"two"}`)}, + description: "One Bidder - Not Modified (imp.ext bidder nil)", + givenImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": nil}}, + givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`4`)}}, + expectedModified: false, + expectedImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": nil}}, + }, + { + description: "One Bidder - Not Modified (malformed ignored)", + givenImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`malformed`)}}, + givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}}, + expectedModified: false, + expectedImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`malformed`)}}, + }, + { + description: "Multiple Bidders - Mixed", + givenImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1}`), "bidder2": json.RawMessage(`{"a":"one","b":"two"}`)}}, + givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}, "bidder2": {"b": json.RawMessage(`"three"`)}}, + expectedModified: true, + expectedImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`), "bidder2": json.RawMessage(`{"a":"one","b":"two"}`)}}, + }, + { + description: "Multiple Bidders - None Modified", + givenImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1}`), "bidder2": json.RawMessage(`{"a":"one","b":"two"}`)}}, + givenReqExtParams: map[string]map[string]json.RawMessage{}, + expectedModified: false, + expectedImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1}`), "bidder2": json.RawMessage(`{"a":"one","b":"two"}`)}}, }, } for _, test := range testCases { - actualModified, actualErr := addMissingReqExtParamsInImpExt(test.givenImpExtByBidder, test.givenReqExtParams) + impExt := openrtb_ext.CreateImpExtForTesting(map[string]json.RawMessage{}, test.givenImpExtPrebid) + + err := mergeBidderParamsImpExtPrebid(&impExt, test.givenReqExtParams) // errors are only possible from the marshal operation, which is not testable - assert.NoError(t, actualErr, test.description+":err") + assert.NoError(t, err, test.description+":err") - assert.Equal(t, test.expectedModified, actualModified, test.description+":modified") - assert.Equal(t, test.expectedImpExtByBidder, test.givenImpExtByBidder, test.description+":imp.ext") + assert.Equal(t, test.expectedModified, impExt.Dirty(), test.description+":modified") + assert.Equal(t, test.expectedImpExtPrebid, impExt.GetPrebid(), test.description+":imp.ext.prebid") } } @@ -1351,6 +1464,7 @@ func TestValidateRequest(t *testing.T) { nil, hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, + hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}), } testCases := []struct { @@ -1530,6 +1644,75 @@ func TestValidateRequest(t *testing.T) { } } +func TestValidateRequestExt(t *testing.T) { + testCases := []struct { + description string + givenRequestExt json.RawMessage + expectedError string + }{ + { + description: "nil", + givenRequestExt: nil, + }, + { + description: "empty", + givenRequestExt: json.RawMessage(`{}`), + }, + { + description: "prebid - empty", + givenRequestExt: json.RawMessage(`{"prebid": {}}`), + }, + { + description: "prebid cache - empty", + givenRequestExt: json.RawMessage(`{"prebid": {"cache": {}}}`), + expectedError: `request.ext is invalid: request.ext.prebid.cache requires one of the "bids" or "vastxml" properties`, + }, + { + description: "prebid cache - bids - null", + givenRequestExt: json.RawMessage(`{"prebid": {"cache": {"bids": null}}}`), + expectedError: `request.ext is invalid: request.ext.prebid.cache requires one of the "bids" or "vastxml" properties`, + }, + { + description: "prebid cache - bids - wrong type", + givenRequestExt: json.RawMessage(`{"prebid": {"cache": {"bids": true}}}`), + expectedError: `json: cannot unmarshal bool into Go struct field ExtRequestPrebidCache.cache.bids of type openrtb_ext.ExtRequestPrebidCacheBids`, + }, + { + description: "prebid cache - bids - provided", + givenRequestExt: json.RawMessage(`{"prebid": {"cache": {"bids": {}}}}`), + }, + { + description: "prebid cache - vastxml - null", + givenRequestExt: json.RawMessage(`{"prebid": {"cache": {"vastxml": null}}}`), + expectedError: `request.ext is invalid: request.ext.prebid.cache requires one of the "bids" or "vastxml" properties`, + }, + { + description: "prebid cache - vastxml - wrong type", + givenRequestExt: json.RawMessage(`{"prebid": {"cache": {"vastxml": true}}}`), + expectedError: `json: cannot unmarshal bool into Go struct field ExtRequestPrebidCache.cache.vastxml of type openrtb_ext.ExtRequestPrebidCacheVAST`, + }, + { + description: "prebid cache - vastxml - provided", + givenRequestExt: json.RawMessage(`{"prebid": {"cache": {"vastxml": {}}}}`), + }, + { + description: "prebid cache - bids + vastxml - provided", + givenRequestExt: json.RawMessage(`{"prebid": {"cache": {"bids": {}, "vastxml": {}}}}`), + }, + } + + for _, test := range testCases { + w := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Ext: test.givenRequestExt}} + err := validateRequestExt(w) + + if len(test.expectedError) > 0 { + assert.EqualError(t, err, test.expectedError, test.description) + } else { + assert.NoError(t, err, test.description) + } + } +} + func TestValidateOrFillChannel(t *testing.T) { testCases := []struct { description string @@ -1654,6 +1837,7 @@ func TestSetIntegrationType(t *testing.T) { nil, hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, + hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}), } testCases := []struct { @@ -1694,7 +1878,6 @@ func TestSetIntegrationType(t *testing.T) { integrationTypeFromReq, err2 := getIntegrationFromRequest(test.givenRequestWrapper) assert.Empty(t, err2, test.description) assert.Equalf(t, test.expectedIntegrationType, integrationTypeFromReq, "Integration type information isn't correct: %s\n", test.description) - } } @@ -1719,6 +1902,7 @@ func TestStoredRequestGenerateUuid(t *testing.T) { nil, hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, + hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}), } req := &openrtb2.BidRequest{} @@ -1785,7 +1969,9 @@ func TestStoredRequestGenerateUuid(t *testing.T) { 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) + storedBidRequestId, hasStoredBidRequest, storedRequests, storedImps, errs := deps.getStoredRequests(context.Background(), json.RawMessage(test.givenRawData), impInfo) + assert.Empty(t, errs, test.description) + newRequest, _, errList := deps.processStoredRequests(json.RawMessage(test.givenRawData), impInfo, storedRequests, storedImps, storedBidRequestId, hasStoredBidRequest) assert.Empty(t, errList, test.description) if err := json.Unmarshal(newRequest, req); err != nil { @@ -1819,6 +2005,7 @@ func TestOversizedRequest(t *testing.T) { nil, hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, + hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}), } req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody)) @@ -1856,6 +2043,7 @@ func TestRequestSizeEdgeCase(t *testing.T) { nil, hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, + hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}), } req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody)) @@ -1887,6 +2075,7 @@ func TestNoEncoding(t *testing.T) { []byte{}, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, + hooks.EmptyPlanBuilder{}, ) request := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json"))) recorder := httptest.NewRecorder() @@ -1912,11 +2101,14 @@ func TestImplicitAMPNoExt(t *testing.T) { return } - bidReq := openrtb2.BidRequest{ + reqWrapper := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ Site: &openrtb2.Site{}, - } - setSiteImplicitly(httpReq, &bidReq) - assert.JSONEq(t, `{"amp":0}`, string(bidReq.Site.Ext)) + }} + + setSiteImplicitly(httpReq, reqWrapper) + + assert.NoError(t, reqWrapper.RebuildRequest()) + assert.JSONEq(t, `{"amp":0}`, string(reqWrapper.Site.Ext)) } func TestImplicitAMPOtherExt(t *testing.T) { @@ -1925,13 +2117,16 @@ func TestImplicitAMPOtherExt(t *testing.T) { return } - bidReq := openrtb2.BidRequest{ + reqWrapper := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ Site: &openrtb2.Site{ Ext: json.RawMessage(`{"other":true}`), }, - } - setSiteImplicitly(httpReq, &bidReq) - assert.JSONEq(t, `{"amp":0,"other":true}`, string(bidReq.Site.Ext)) + }} + + setSiteImplicitly(httpReq, reqWrapper) + + assert.NoError(t, reqWrapper.RebuildRequest()) + assert.JSONEq(t, `{"amp":0,"other":true}`, string(reqWrapper.Site.Ext)) } func TestExplicitAMP(t *testing.T) { @@ -1940,12 +2135,12 @@ func TestExplicitAMP(t *testing.T) { return } - bidReq := openrtb2.BidRequest{ + bidReq := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ Site: &openrtb2.Site{ Ext: json.RawMessage(`{"amp":1}`), }, - } - setSiteImplicitly(httpReq, &bidReq) + }} + setSiteImplicitly(httpReq, bidReq) assert.JSONEq(t, `{"amp":1}`, string(bidReq.Site.Ext)) } @@ -1964,6 +2159,7 @@ func TestContentType(t *testing.T) { []byte{}, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, + hooks.EmptyPlanBuilder{}, ) request := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json"))) recorder := httptest.NewRecorder() @@ -2003,37 +2199,37 @@ func TestValidateImpExt(t *testing.T) { description: "Unknown Bidder only", impExt: json.RawMessage(`{"unknownbidder":{"placement_id":555}}`), expectedImpExt: `{"unknownbidder":{"placement_id":555}}`, - expectedErrs: []error{errors.New("request.imp[0].ext contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, + expectedErrs: []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, }, { description: "Unknown Prebid Ext Bidder only", impExt: json.RawMessage(`{"prebid":{"bidder":{"unknownbidder":{"placement_id":555}}}}`), expectedImpExt: `{"prebid":{"bidder":{"unknownbidder":{"placement_id":555}}}}`, - expectedErrs: []error{errors.New("request.imp[0].ext contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, + expectedErrs: []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, }, { description: "Unknown Prebid Ext Bidder + First Party Data Context", impExt: json.RawMessage(`{"prebid":{"bidder":{"unknownbidder":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`), expectedImpExt: `{"prebid":{"bidder":{"unknownbidder":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`, - expectedErrs: []error{errors.New("request.imp[0].ext contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, + expectedErrs: []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, }, { description: "Unknown Bidder + First Party Data Context", impExt: json.RawMessage(`{"unknownbidder":{"placement_id":555} ,"context":{"data":{"keywords":"prebid server example"}}}`), expectedImpExt: `{"unknownbidder":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`, - expectedErrs: []error{errors.New("request.imp[0].ext contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, + expectedErrs: []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, }, { description: "Unknown Bidder + Disabled Bidder", impExt: json.RawMessage(`{"unknownbidder":{"placement_id":555},"disabledbidder":{"foo":"bar"}}`), expectedImpExt: `{"unknownbidder":{"placement_id":555},"disabledbidder":{"foo":"bar"}}`, - expectedErrs: []error{errors.New("request.imp[0].ext contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, + expectedErrs: []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, }, { description: "Unknown Bidder + Disabled Prebid Ext Bidder", impExt: json.RawMessage(`{"unknownbidder":{"placement_id":555},"prebid":{"bidder":{"disabledbidder":{"foo":"bar"}}}}`), expectedImpExt: `{"unknownbidder":{"placement_id":555},"prebid":{"bidder":{"disabledbidder":{"foo":"bar"}}}}`, - expectedErrs: []error{errors.New("request.imp[0].ext contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, + expectedErrs: []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, }, }, }, @@ -2043,10 +2239,10 @@ func TestValidateImpExt(t *testing.T) { { description: "Disabled Bidder", impExt: json.RawMessage(`{"disabledbidder":{"foo":"bar"}}`), - expectedImpExt: `{}`, + expectedImpExt: `{"disabledbidder":{"foo":"bar"}}`, expectedErrs: []error{ &errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}, - errors.New("request.imp[0].ext must contain at least one bidder"), + errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder"), }, // if only bidder(s) found in request.imp[x].ext.{biddername} or request.imp[x].ext.prebid.bidder.{biddername} are disabled, return error }, @@ -2056,25 +2252,25 @@ func TestValidateImpExt(t *testing.T) { expectedImpExt: `{"prebid":{"bidder":{"disabledbidder":{"foo":"bar"}}}}`, expectedErrs: []error{ &errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}, - errors.New("request.imp[0].ext must contain at least one bidder"), + errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder"), }, }, { description: "Disabled Bidder + First Party Data Context", impExt: json.RawMessage(`{"disabledbidder":{"foo":"bar"},"context":{"data":{"keywords":"prebid server example"}}}`), - expectedImpExt: `{"context":{"data":{"keywords":"prebid server example"}}}`, + expectedImpExt: `{"disabledbidder":{"foo":"bar"},"context":{"data":{"keywords":"prebid server example"}}}`, expectedErrs: []error{ &errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}, - errors.New("request.imp[0].ext must contain at least one bidder"), + errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder"), }, }, { description: "Disabled Prebid Ext Bidder + First Party Data Context", impExt: json.RawMessage(`{"prebid":{"bidder":{"disabledbidder":{"foo":"bar"}}},"context":{"data":{"keywords":"prebid server example"}}}`), - expectedImpExt: `{"context":{"data":{"keywords":"prebid server example"}}, "prebid":{"bidder":{"disabledbidder":{"foo":"bar"}}}}`, + expectedImpExt: `{"prebid":{"bidder":{"disabledbidder":{"foo":"bar"}}},"context":{"data":{"keywords":"prebid server example"}}}`, expectedErrs: []error{ &errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}, - errors.New("request.imp[0].ext must contain at least one bidder"), + errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder"), }, }, }, @@ -2087,7 +2283,7 @@ func TestValidateImpExt(t *testing.T) { impExt: json.RawMessage(`{"context":{"data":{"keywords":"prebid server example"}}}`), expectedImpExt: `{"context":{"data":{"keywords":"prebid server example"}}}`, expectedErrs: []error{ - errors.New("request.imp[0].ext must contain at least one bidder"), + errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder"), }, }, }, @@ -2098,7 +2294,7 @@ func TestValidateImpExt(t *testing.T) { { description: "Valid bidder root ext", impExt: json.RawMessage(`{"appnexus":{"placement_id":555}}`), - expectedImpExt: `{"appnexus":{"placement_id":555}}`, + expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}}}`, expectedErrs: []error{}, }, { @@ -2110,7 +2306,7 @@ func TestValidateImpExt(t *testing.T) { { description: "Valid Bidder + First Party Data Context", impExt: json.RawMessage(`{"appnexus":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`), - expectedImpExt: `{"appnexus":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`, + expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`, expectedErrs: []error{}, }, { @@ -2123,43 +2319,43 @@ func TestValidateImpExt(t *testing.T) { description: "Valid Bidder + Unknown Bidder", impExt: json.RawMessage(`{"appnexus":{"placement_id":555},"unknownbidder":{"placement_id":555}}`), expectedImpExt: `{"appnexus":{"placement_id":555},"unknownbidder":{"placement_id":555}}`, - expectedErrs: []error{errors.New("request.imp[0].ext contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, + expectedErrs: []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, }, { description: "Valid Bidder + Disabled Bidder", impExt: json.RawMessage(`{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"}}`), - expectedImpExt: `{"appnexus":{"placement_id":555}}`, + expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}}}`, expectedErrs: []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}}, }, { description: "Valid Bidder + Disabled Bidder + First Party Data Context", impExt: json.RawMessage(`{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"},"context":{"data":{"keywords":"prebid server example"}}}`), - expectedImpExt: `{"appnexus":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`, + expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`, expectedErrs: []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}}, }, { description: "Valid Bidder + Disabled Bidder + Unknown Bidder + First Party Data Context", impExt: json.RawMessage(`{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"},"unknownbidder":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`), expectedImpExt: `{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"},"unknownbidder":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`, - expectedErrs: []error{errors.New("request.imp[0].ext contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, + expectedErrs: []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, }, { description: "Valid Prebid Ext Bidder + Disabled Bidder Ext", impExt: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"}}}}`), - expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id": 555},"disabledbidder":{"foo":"bar"}}},"appnexus":{"placement_id":555}}`, + expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id": 555}}}}`, expectedErrs: []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}}, }, { description: "Valid Prebid Ext Bidder + Disabled Ext Bidder + First Party Data Context", impExt: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"}}},"context":{"data":{"keywords":"prebid server example"}}}`), - expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id": 555},"disabledbidder":{"foo":"bar"}}},"appnexus":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`, + expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id": 555}}},"context":{"data":{"keywords":"prebid server example"}}}`, expectedErrs: []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}}, }, { description: "Valid Prebid Ext Bidder + Disabled Ext Bidder + Unknown Ext + First Party Data Context", impExt: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"},"unknownbidder":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`), - expectedImpExt: `{"context":{"data":{"keywords":"prebid server example"}},"prebid":{"bidder":{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"},"unknownbidder":{"placement_id":555}}}}`, - expectedErrs: []error{errors.New("request.imp[0].ext contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, + expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"},"unknownbidder":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`, + expectedErrs: []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, }, }, }, @@ -2183,13 +2379,17 @@ func TestValidateImpExt(t *testing.T) { nil, hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, + hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}), } for _, group := range testGroups { for _, test := range group.testCases { imp := &openrtb2.Imp{Ext: test.impExt} + impWrapper := &openrtb_ext.ImpWrapper{Imp: imp} - errs := deps.validateImpExt(imp, nil, 0, false, nil) + errs := deps.validateImpExt(impWrapper, nil, 0, false, nil) + + assert.NoError(t, impWrapper.RebuildImp(), test.description+":rebuild_imp") if len(test.expectedImpExt) > 0 { assert.JSONEq(t, test.expectedImpExt, string(imp.Ext), "imp.ext JSON does not match expected. Test: %s. %s\n", group.description, test.description) @@ -2202,7 +2402,7 @@ func TestValidateImpExt(t *testing.T) { } func validRequest(t *testing.T, filename string) string { - requestData, err := ioutil.ReadFile("sample-requests/valid-whole/supplementary/" + filename) + requestData, err := os.ReadFile("sample-requests/valid-whole/supplementary/" + filename) if err != nil { t.Fatalf("Failed to fetch a valid request: %v", err) } @@ -2231,6 +2431,7 @@ func TestCurrencyTrunc(t *testing.T) { nil, hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, + hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}), } ui := int64(1) @@ -2277,6 +2478,7 @@ func TestCCPAInvalid(t *testing.T) { nil, hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, + hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}), } ui := int64(1) @@ -2327,6 +2529,7 @@ func TestNoSaleInvalid(t *testing.T) { nil, hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, + hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}), } ui := int64(1) @@ -2380,6 +2583,7 @@ func TestValidateSourceTID(t *testing.T) { nil, hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, + hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}), } ui := int64(1) @@ -2423,6 +2627,7 @@ func TestSChainInvalid(t *testing.T) { nil, hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, + hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}), } ui := int64(1) @@ -2454,13 +2659,13 @@ func TestMapSChains(t *testing.T) { const seller1SChain string = `"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}` const seller2SChain string = `"schain":{"complete":2,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":2}],"ver":"2.0"}` - seller1SChainUnpacked := openrtb_ext.ExtRequestPrebidSChainSChain{ + seller1SChainUnpacked := openrtb2.SupplyChain{ Complete: 1, - Nodes: []*openrtb_ext.ExtRequestPrebidSChainSChainNode{{ + Nodes: []openrtb2.SupplyChainNode{{ ASI: "directseller1.com", SID: "00001", RID: "BidRequest1", - HP: 1, + HP: openrtb2.Int8Ptr(1), }}, Ver: "1.0", } @@ -2468,8 +2673,8 @@ func TestMapSChains(t *testing.T) { tests := []struct { description string bidRequest openrtb2.BidRequest - wantReqExtSChain *openrtb_ext.ExtRequestPrebidSChainSChain - wantSourceExtSChain *openrtb_ext.ExtRequestPrebidSChainSChain + wantReqExtSChain *openrtb2.SupplyChain + wantSourceExtSChain *openrtb2.SupplyChain wantError bool }{ { @@ -2704,7 +2909,9 @@ func TestSanitizeRequest(t *testing.T) { } for _, test := range testCases { - sanitizeRequest(test.req, test.ipValidator) + bidReq := &openrtb_ext.RequestWrapper{BidRequest: test.req} + + sanitizeRequest(bidReq, test.ipValidator) assert.Equal(t, test.expectedIPv4, test.req.Device.IP, test.description+":ipv4") assert.Equal(t, test.expectedIPv6, test.req.Device.IPv6, test.description+":ipv6") } @@ -2772,6 +2979,7 @@ func TestEidPermissionsInvalid(t *testing.T) { nil, hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, + hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}), } ui := int64(1) @@ -3019,7 +3227,8 @@ func TestIOS14EndToEnd(t *testing.T) { map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), - empty_fetcher.EmptyFetcher{}) + empty_fetcher.EmptyFetcher{}, + hooks.EmptyPlanBuilder{}) httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "app-ios140-no-ifa.json"))) @@ -3054,6 +3263,7 @@ func TestAuctionWarnings(t *testing.T) { nil, hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, + hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}), } req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody)) @@ -3095,11 +3305,12 @@ func TestParseRequestParseImpInfoError(t *testing.T) { nil, hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, + hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}), } req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody)) - resReq, impExtInfoMap, _, _, errL := deps.parseRequest(req) + resReq, impExtInfoMap, _, _, _, _, errL := deps.parseRequest(req, &metrics.Labels{}) 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") @@ -3565,7 +3776,8 @@ func TestAuctionResponseHeaders(t *testing.T) { map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), - empty_fetcher.EmptyFetcher{}) + empty_fetcher.EmptyFetcher{}, + hooks.EmptyPlanBuilder{}) for _, test := range testCases { httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(test.requestBody)) @@ -3668,11 +3880,14 @@ func TestParseRequestMergeBidderParams(t *testing.T) { nil, hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, + hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}), } req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(test.givenRequestBody)) - resReq, _, _, _, errL := deps.parseRequest(req) + resReq, _, _, _, _, _, errL := deps.parseRequest(req, &metrics.Labels{}) + + assert.NoError(t, resReq.RebuildRequest()) var expIExt, iExt map[string]interface{} err := json.Unmarshal(test.expectedImpExt, &expIExt) @@ -3768,11 +3983,12 @@ func TestParseRequestStoredResponses(t *testing.T) { nil, hardcodedResponseIPValidator{response: true}, &mockStoredResponseFetcher{mockStoredResponses}, + hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}), } req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(test.givenRequestBody)) - _, _, storedResponses, _, errL := deps.parseRequest(req) + _, _, storedResponses, _, _, _, errL := deps.parseRequest(req, &metrics.Labels{}) if test.expectedErrorCount == 0 { assert.Equal(t, test.expectedStoredResponses, storedResponses, "stored responses should match") @@ -3855,10 +4071,11 @@ func TestParseRequestStoredBidResponses(t *testing.T) { nil, hardcodedResponseIPValidator{response: true}, &mockStoredResponseFetcher{mockStoredBidResponses}, + hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}), } req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(test.givenRequestBody)) - _, _, _, storedBidResponses, errL := deps.parseRequest(req) + _, _, _, storedBidResponses, _, _, errL := deps.parseRequest(req, &metrics.Labels{}) if test.expectedErrorCount == 0 { assert.Equal(t, test.expectedStoredBidResponses, storedBidResponses, "stored responses should match") @@ -3888,6 +4105,7 @@ func TestValidateStoredResp(t *testing.T) { nil, hardcodedResponseIPValidator{response: true}, &mockStoredResponseFetcher{}, + hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}), } testCases := []struct { @@ -4438,6 +4656,75 @@ func TestValidateStoredResp(t *testing.T) { } } +func TestValidResponseWhenRequestRejected(t *testing.T) { + const nbr int = 123 + const file string = "sample-requests/valid-whole/hooks/auction_reject.json" + + testCases := []struct { + description string + file string + planBuilder hooks.ExecutionPlanBuilder + }{ + { + description: "Assert correct BidResponse when request rejected at entrypoint stage", + file: file, + planBuilder: mockPlanBuilder{entrypointPlan: makeRejectPlan[hookstage.Entrypoint](mockRejectionHook{nbr})}, + }, + { + description: "Assert correct BidResponse when request rejected at raw-auction stage", + file: file, + planBuilder: mockPlanBuilder{rawAuctionPlan: makeRejectPlan[hookstage.RawAuctionRequest](mockRejectionHook{nbr})}, + }, + { + description: "Assert correct BidResponse when request rejected at processed-auction stage", + file: file, + planBuilder: mockPlanBuilder{processedAuctionPlan: makeRejectPlan[hookstage.ProcessedAuctionRequest](mockRejectionHook{nbr})}, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + fileData, err := os.ReadFile(tc.file) + assert.NoError(t, err, "Failed to read test file.") + + test, err := parseTestFile(fileData, tc.file) + assert.NoError(t, err, "Failed to parse test file.") + test.planBuilder = tc.planBuilder + test.endpointType = OPENRTB_ENDPOINT + + auctionEndpointHandler, _, mockBidServers, mockCurrencyRatesServer, err := buildTestEndpoint(test, &config.Configuration{MaxRequestSize: maxSize}) + assert.NoError(t, err, "Failed to build test endpoint.") + + recorder := httptest.NewRecorder() + req := httptest.NewRequest("POST", "/openrtb2/auction", bytes.NewReader(test.BidRequest)) + auctionEndpointHandler(recorder, req, nil) + assert.Equal(t, recorder.Code, http.StatusOK, "Endpoint should return 200 OK.") + + var actualResp openrtb2.BidResponse + var expectedResp openrtb2.BidResponse + var actualExt openrtb_ext.ExtBidResponse + var expectedExt openrtb_ext.ExtBidResponse + + assert.NoError(t, json.Unmarshal(test.ExpectedBidResponse, &expectedResp), "Unable to unmarshal expected BidResponse.") + assert.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &actualResp), "Unable to unmarshal actual BidResponse.") + if expectedResp.Ext != nil { + assert.NoError(t, json.Unmarshal(expectedResp.Ext, &expectedExt), "Unable to unmarshal expected ExtBidResponse.") + assert.NoError(t, json.Unmarshal(actualResp.Ext, &actualExt), "Unable to unmarshal actual ExtBidResponse.") + } + + assertBidResponseEqual(t, tc.file, expectedResp, actualResp) + assert.Equal(t, expectedResp.NBR, actualResp.NBR, "Invalid NBR.") + assert.Equal(t, expectedExt.Warnings, actualExt.Warnings, "Wrong bidResponse.ext.") + + // Close servers regardless if the test case was run or not + for _, mockBidServer := range mockBidServers { + mockBidServer.Close() + } + mockCurrencyRatesServer.Close() + }) + } +} + type mockStoredResponseFetcher struct { data map[string]json.RawMessage } @@ -4451,7 +4738,7 @@ func (cf *mockStoredResponseFetcher) FetchResponses(ctx context.Context, ids []s } func getObject(t *testing.T, filename, key string) json.RawMessage { - requestData, err := ioutil.ReadFile("sample-requests/valid-whole/supplementary/" + filename) + requestData, err := os.ReadFile("sample-requests/valid-whole/supplementary/" + filename) if err != nil { t.Fatalf("Failed to fetch a valid request: %v", err) } diff --git a/endpoints/openrtb2/interstitial.go b/endpoints/openrtb2/interstitial.go index 359bae11d4c..0a617e22924 100644 --- a/endpoints/openrtb2/interstitial.go +++ b/endpoints/openrtb2/interstitial.go @@ -3,7 +3,7 @@ package openrtb2 import ( "fmt" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" @@ -11,8 +11,8 @@ import ( func processInterstitials(req *openrtb_ext.RequestWrapper) error { unmarshalled := true - for i := range req.Imp { - if req.Imp[i].Instl == 1 { + for _, imp := range req.GetImp() { + if imp.Instl == 1 { var prebid *openrtb_ext.ExtDevicePrebid if unmarshalled { if req.Device.Ext == nil { @@ -20,16 +20,17 @@ func processInterstitials(req *openrtb_ext.RequestWrapper) error { return nil } deviceExt, err := req.GetDeviceExt() + if err != nil { return err } prebid = deviceExt.GetPrebid() - if prebid.Interstitial == nil { + if prebid == nil || prebid.Interstitial == nil { // No special interstitial support requested, so bail as there is nothing to do return nil } } - err := processInterstitialsForImp(&req.Imp[i], prebid, req.Device) + err := processInterstitialsForImp(imp, prebid, req.Device) if err != nil { return err } @@ -38,7 +39,7 @@ func processInterstitials(req *openrtb_ext.RequestWrapper) error { return nil } -func processInterstitialsForImp(imp *openrtb2.Imp, devExtPrebid *openrtb_ext.ExtDevicePrebid, device *openrtb2.Device) error { +func processInterstitialsForImp(imp *openrtb_ext.ImpWrapper, 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. diff --git a/endpoints/openrtb2/interstitial_test.go b/endpoints/openrtb2/interstitial_test.go index fe0ed966c3c..c8be6a1846c 100644 --- a/endpoints/openrtb2/interstitial_test.go +++ b/endpoints/openrtb2/interstitial_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -33,6 +33,30 @@ var request = &openrtb2.BidRequest{ }, } +var requestWithoutPrebidDeviceExt = &openrtb2.BidRequest{ + ID: "some-id", + Imp: []openrtb2.Imp{ + { + ID: "my-imp-id", + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ + { + W: 300, + H: 600, + }, + }, + }, + Instl: 1, + Ext: json.RawMessage(`{"appnexus": {"placementId": 12883451}}`), + }, + }, + Device: &openrtb2.Device{ + H: 640, + W: 320, + Ext: json.RawMessage(`{"field": 1}`), + }, +} + func TestInterstitial(t *testing.T) { myRequest := request if err := processInterstitials(&openrtb_ext.RequestWrapper{BidRequest: myRequest}); err != nil { @@ -83,3 +107,17 @@ func TestInterstitial(t *testing.T) { assert.Equal(t, targetFormat, myRequest.Imp[0].Banner.Format) } + +func TestInterstitialWithoutPrebidDeviceExt(t *testing.T) { + myRequest := requestWithoutPrebidDeviceExt + if err := processInterstitials(&openrtb_ext.RequestWrapper{BidRequest: myRequest}); err != nil { + t.Fatalf("Error processing interstitials: %v", err) + } + targetFormat := []openrtb2.Format{ + { + W: 300, + H: 600, + }, + } + assert.Equal(t, targetFormat, myRequest.Imp[0].Banner.Format) +} diff --git a/endpoints/openrtb2/sample-requests/account-malformed/malformed-acct.json b/endpoints/openrtb2/sample-requests/account-malformed/malformed-acct.json new file mode 100644 index 00000000000..93612993fb4 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/account-malformed/malformed-acct.json @@ -0,0 +1,65 @@ +{ + "description": "Account is valid but account config is malformed", + "mockBidRequest": { + "id": "some-request-id", + "user": {}, + "site": { + "id": "cool_app", + "publisher": { + "id": "malformed_acct" + } + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "tmax": 500, + "ext": { + "prebid": { + "aliases": { + "districtm": "appnexus" + }, + "bidadjustmentfactors": { + "appnexus": 1.01, + "districtm": 0.98, + "rubicon": 0.99 + }, + "cache": { + "bids": {} + }, + "targeting": { + "includewinners": false, + "pricegranularity": { + "precision": 2, + "ranges": [ + { + "max": 20, + "increment": 0.10 + } + ] + } + } + } + } + }, + "expectedReturnCode": 500, + "expectedErrorMessage": "Invalid request: The prebid-server account config for account id \"malformed_acct\" is malformed. Please reach out to the prebid server host.\n" +} diff --git a/endpoints/openrtb2/sample-requests/amp/consent-through-query/addtl-consent-through-query.json b/endpoints/openrtb2/sample-requests/amp/consent-through-query/addtl-consent-through-query.json new file mode 100644 index 00000000000..7247e2df8af --- /dev/null +++ b/endpoints/openrtb2/sample-requests/amp/consent-through-query/addtl-consent-through-query.json @@ -0,0 +1,92 @@ +{ + "description": "Additional consent providers string passed through query: expect user.ext.ConsentedProvidersSettings.consented_providers to be set to the addtl_consent string", + "query": "tag_id=101&addtl_consent=1~1.35.41.101", + "config": { + "mockBidders": [ + {"bidderName": "appnexus", "currency": "USD", "price": 15} + ] + }, + "mockBidRequest": { + "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5", + "site": {"page": "prebid.org"}, + "imp": [ + { + "id": "/19968336/header-bid-tag-0", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ] + }, + "expectedValidatedBidRequest": { + "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5", + "site": { + "page": "prebid.org", + "ext": {"amp":1} + }, + "device": { + "ip":"192.0.2.1" + }, + "at": 1, + "imp": [ + { + "id": "/19968336/header-bid-tag-0", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "secure": 1, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12883451 + } + } + } + } + } + ], + "user": { + "ext": { + "ConsentedProvidersSettings":{"consented_providers":"1~1.35.41.101"}, + "consented_providers_settings":{"consented_providers":[1,35,41,101]} + } + }, + "ext": {"prebid":{"cache":{"bids":{"returnCreative":null},"vastxml":null},"channel":{"name":"amp","version":""},"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":20,"increment":0.1}]},"includewinners":true,"includebidderkeys":true,"includebrandcategory":null,"includeformat":false,"durationrangesec":null,"preferdeals":false}}} + }, + "expectedAmpResponse": { + "targeting": { + "hb_bidder": "appnexus", + "hb_bidder_appnexus": "appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_id": "0", + "hb_cache_id_appnexus": "0", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_pb": "15.00", + "hb_pb_appnexus": "15.00" + }, + "warnings": { + "general": [ + {"code": 10002, "message": "debug turned off for account"} + ] + } + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-ccpa-through-query.json b/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-ccpa-through-query.json new file mode 100644 index 00000000000..d420d19afd9 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-ccpa-through-query.json @@ -0,0 +1,137 @@ +{ + "description": "CCPA consent type, valid US privacy string: expect regs.ext.gdpr set to 0 and user.ext.us_privacy set to the US string", + "query": "tag_id=101&consent_type=3&consent_string=1YYY", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 15 + } + ] + }, + "mockBidRequest": { + "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "/19968336/header-bid-tag-0", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12883451 + } + } + } + } + } + ] + }, + "expectedValidatedBidRequest": { + "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5", + "site": { + "page": "prebid.org", + "ext": { + "amp": 1 + } + }, + "device": { + "ip": "192.0.2.1" + }, + "at": 1, + "imp": [ + { + "id": "/19968336/header-bid-tag-0", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "secure": 1, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12883451 + } + } + } + } + } + ], + "regs": { + "ext": { + "us_privacy": "1YYY" + } + }, + "ext": { + "prebid": { + "cache": { + "bids": { + "returnCreative": null + }, + "vastxml": null + }, + "channel": { + "name": "amp", + "version": "" + }, + "targeting": { + "pricegranularity": { + "precision": 2, + "ranges": [ + { + "min": 0, + "max": 20, + "increment": 0.1 + } + ] + }, + "includewinners": true, + "includebidderkeys": true, + "includebrandcategory": null, + "includeformat": false, + "durationrangesec": null, + "preferdeals": false + } + } + } + }, + "expectedAmpResponse": { + "targeting": { + "hb_bidder": "appnexus", + "hb_bidder_appnexus": "appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_id": "0", + "hb_cache_id_appnexus": "0", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_pb": "15.00", + "hb_pb_appnexus": "15.00" + }, + "warnings": { + "general": [ + { + "code": 10002, + "message": "debug turned off for account" + } + ] + } + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-legacy-tcf2-consent-through-query.json b/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-legacy-tcf2-consent-through-query.json new file mode 100644 index 00000000000..2ff32e59ad4 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-legacy-tcf2-consent-through-query.json @@ -0,0 +1,142 @@ +{ + "description": "TCF2 consent type, valid consent string passed through legacy gdpr_consent field, gdpr_applies set to true: expect regs.ext.gdpr to be set to 1 and user.ext.consent equal to the consent_string", + "query": "tag_id=101&consent_type=2&gdpr_applies=true&gdpr_consent=CPdECS0PdECS0ACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 15 + } + ] + }, + "mockBidRequest": { + "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "/19968336/header-bid-tag-0", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12883451 + } + } + } + } + } + ] + }, + "expectedValidatedBidRequest": { + "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5", + "site": { + "page": "prebid.org", + "ext": { + "amp": 1 + } + }, + "device": { + "ip": "192.0.2.1" + }, + "at": 1, + "imp": [ + { + "id": "/19968336/header-bid-tag-0", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "secure": 1, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12883451 + } + } + } + } + } + ], + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "CPdECS0PdECS0ACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA" + } + }, + "ext": { + "prebid": { + "cache": { + "bids": { + "returnCreative": null + }, + "vastxml": null + }, + "channel": { + "name": "amp", + "version": "" + }, + "targeting": { + "pricegranularity": { + "precision": 2, + "ranges": [ + { + "min": 0, + "max": 20, + "increment": 0.1 + } + ] + }, + "includewinners": true, + "includebidderkeys": true, + "includebrandcategory": null, + "includeformat": false, + "durationrangesec": null, + "preferdeals": false + } + } + } + }, + "expectedAmpResponse": { + "targeting": { + "hb_bidder": "appnexus", + "hb_bidder_appnexus": "appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_id": "0", + "hb_cache_id_appnexus": "0", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_pb": "15.00", + "hb_pb_appnexus": "15.00" + }, + "warnings": { + "general": [ + { + "code": 10002, + "message": "debug turned off for account" + } + ] + } + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-tcf1-consent-through-query.json b/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-tcf1-consent-through-query.json new file mode 100644 index 00000000000..2d2d866917b --- /dev/null +++ b/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-tcf1-consent-through-query.json @@ -0,0 +1,142 @@ +{ + "description": "TCF1 consent type, valid consent string, and gdpr_applies. Expect regs.ext.gdpr to be set to 1 because the stored request comes with that value and user.ext.consent not set because TCF1 is deprecated", + "query": "tag_id=101&consent_type=1&consent_string&BOMhx4COMhx4CABABBAAABAAAAAfTABAAAA", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 15 + } + ] + }, + "mockBidRequest": { + "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5", + "site": { + "page": "prebid.org" + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "imp": [ + { + "id": "/19968336/header-bid-tag-0", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12883451 + } + } + } + } + } + ] + }, + "expectedValidatedBidRequest": { + "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5", + "site": { + "page": "prebid.org", + "ext": { + "amp": 1 + } + }, + "device": { + "ip": "192.0.2.1" + }, + "at": 1, + "imp": [ + { + "id": "/19968336/header-bid-tag-0", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "secure": 1, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12883451 + } + } + } + } + } + ], + "regs": { + "ext": { + "gdpr": 1 + } + }, + "ext": { + "prebid": { + "cache": { + "bids": { + "returnCreative": null + }, + "vastxml": null + }, + "channel": { + "name": "amp", + "version": "" + }, + "targeting": { + "pricegranularity": { + "precision": 2, + "ranges": [ + { + "min": 0, + "max": 20, + "increment": 0.1 + } + ] + }, + "includewinners": true, + "includebidderkeys": true, + "includebrandcategory": null, + "includeformat": false, + "durationrangesec": null, + "preferdeals": false + } + } + } + }, + "expectedAmpResponse": { + "targeting": { + "hb_bidder": "appnexus", + "hb_bidder_appnexus": "appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_id": "0", + "hb_cache_id_appnexus": "0", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_pb": "15.00", + "hb_pb_appnexus": "15.00" + }, + "warnings": { + "general": [ + { + "code": 10002, + "message": "debug turned off for account" + } + ] + } + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-tcf2-consent-through-query.json b/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-tcf2-consent-through-query.json new file mode 100644 index 00000000000..b1731327ff6 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-tcf2-consent-through-query.json @@ -0,0 +1,142 @@ +{ + "description": "TCF2 consent type, valid consent_string, gdpr_applies set to true: expect regs.ext.gdpr to be set to 1 and user.ext.consent equal to the consent_string", + "query": "tag_id=101&consent_type=2&gdpr_applies=true&consent_string=CPdECS0PdECS0ACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 15 + } + ] + }, + "mockBidRequest": { + "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "/19968336/header-bid-tag-0", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12883451 + } + } + } + } + } + ] + }, + "expectedValidatedBidRequest": { + "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5", + "site": { + "page": "prebid.org", + "ext": { + "amp": 1 + } + }, + "device": { + "ip": "192.0.2.1" + }, + "at": 1, + "imp": [ + { + "id": "/19968336/header-bid-tag-0", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "secure": 1, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12883451 + } + } + } + } + } + ], + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "CPdECS0PdECS0ACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA" + } + }, + "ext": { + "prebid": { + "cache": { + "bids": { + "returnCreative": null + }, + "vastxml": null + }, + "channel": { + "name": "amp", + "version": "" + }, + "targeting": { + "pricegranularity": { + "precision": 2, + "ranges": [ + { + "min": 0, + "max": 20, + "increment": 0.1 + } + ] + }, + "includewinners": true, + "includebidderkeys": true, + "includebrandcategory": null, + "includeformat": false, + "durationrangesec": null, + "preferdeals": false + } + } + } + }, + "expectedAmpResponse": { + "targeting": { + "hb_bidder": "appnexus", + "hb_bidder_appnexus": "appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_id": "0", + "hb_cache_id_appnexus": "0", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_pb": "15.00", + "hb_pb_appnexus": "15.00" + }, + "warnings": { + "general": [ + { + "code": 10002, + "message": "debug turned off for account" + } + ] + } + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/aliased-buyeruids.json b/endpoints/openrtb2/sample-requests/amp/valid-supplementary/aliased-buyeruids.json similarity index 70% rename from endpoints/openrtb2/sample-requests/valid-whole/supplementary/aliased-buyeruids.json rename to endpoints/openrtb2/sample-requests/amp/valid-supplementary/aliased-buyeruids.json index bf0f7a04440..b74faf2d56b 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/aliased-buyeruids.json +++ b/endpoints/openrtb2/sample-requests/amp/valid-supplementary/aliased-buyeruids.json @@ -1,24 +1,38 @@ { "description": "Amp request where root.user.ext.prebid.buyeruids field makes use of alias defined in root.ext.prebid.aliases", + "query": "tag_id=101", "config": { "mockBidders": [ - {"bidderName": "appnexus", "currency": "USD", "price": 2} + { + "bidderName": "appnexus", + "currency": "USD", + "price": 2 + } ] }, "mockBidRequest": { "id": "request-with-user-ext-obj", - "site": {"page": "test.somepage.com"}, + "site": { + "page": "test.somepage.com" + }, "imp": [ { "id": "my-imp-id", "banner": { "format": [ - {"w": 300, "h": 600} + { + "w": 300, + "h": 600 + } ] }, "ext": { - "appnexus": { - "placementId": 12883451 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12883451 + } + } } } } @@ -55,9 +69,12 @@ }, "warnings": { "general": [ - {"code": 10002, "message": "debug turned off for account"} + { + "code": 10002, + "message": "debug turned off for account" + } ] } }, "expectedReturnCode": 200 -} +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/amp/valid-supplementary/aliases.json b/endpoints/openrtb2/sample-requests/amp/valid-supplementary/aliases.json new file mode 100644 index 00000000000..2ada07a1720 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/amp/valid-supplementary/aliases.json @@ -0,0 +1,71 @@ +{ + "description": "Amp request impression requests a bid from a bidder alias defined in root.ext.prebid.aliases", + "query": "tag_id=101", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 2 + } + ] + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "unknown": { + "placementId": 12883451 + } + } + } + } + } + ], + "ext": { + "prebid": { + "aliases": { + "unknown": "appnexus" + } + } + } + }, + "expectedAmpResponse": { + "targeting": { + "hb_bidder": "unknown", + "hb_bidder_unknown": "unknown", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_unknow": "www.pbcserver.com", + "hb_cache_id": "0", + "hb_cache_id_unknown": "0", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_unknow": "/pbcache/endpoint", + "hb_pb": "2.00", + "hb_pb_unknown": "2.00" + }, + "warnings": { + "general": [ + { + "code": 10002, + "message": "debug turned off for account" + } + ] + } + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr-no-consentstring.json b/endpoints/openrtb2/sample-requests/amp/valid-supplementary/gdpr-no-consentstring.json similarity index 64% rename from endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr-no-consentstring.json rename to endpoints/openrtb2/sample-requests/amp/valid-supplementary/gdpr-no-consentstring.json index fe7fb53ffdf..833dd6e7aca 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr-no-consentstring.json +++ b/endpoints/openrtb2/sample-requests/amp/valid-supplementary/gdpr-no-consentstring.json @@ -1,30 +1,46 @@ { "description": "Amp request comes with a root.regs.ext.gdpr value but is missing GDPR consent string", + "query": "tag_id=101", "config": { "mockBidders": [ - {"bidderName": "appnexus", "currency": "USD", "price": 3} + { + "bidderName": "appnexus", + "currency": "USD", + "price": 3 + } ] }, "mockBidRequest": { "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5", - "site": {"page": "prebid.org"}, + "site": { + "page": "prebid.org" + }, "imp": [ { "id": "/19968336/header-bid-tag-0", "banner": { "format": [ - {"w": 300, "h": 600} + { + "w": 300, + "h": 600 + } ] }, "ext": { - "appnexus": { - "placementId": 12883451 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12883451 + } + } } } } ], "regs": { - "ext": {"gdpr": 1} + "ext": { + "gdpr": 1 + } } }, "expectedAmpResponse": { @@ -42,9 +58,12 @@ }, "warnings": { "general": [ - {"code": 10002, "message": "debug turned off for account"} + { + "code": 10002, + "message": "debug turned off for account" + } ] } }, "expectedReturnCode": 200 -} +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr.json b/endpoints/openrtb2/sample-requests/amp/valid-supplementary/gdpr.json similarity index 67% rename from endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr.json rename to endpoints/openrtb2/sample-requests/amp/valid-supplementary/gdpr.json index 1792d0e6d61..f692e2bd280 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr.json +++ b/endpoints/openrtb2/sample-requests/amp/valid-supplementary/gdpr.json @@ -1,13 +1,20 @@ { "description": "Amp request comes with a root.regs.ext.gdpr and a GDPR consent string found in root.user.ext.consent", + "query": "tag_id=101", "config": { "mockBidders": [ - {"bidderName": "appnexus", "currency": "USD", "price": 15} + { + "bidderName": "appnexus", + "currency": "USD", + "price": 15 + } ] }, "mockBidRequest": { "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5", - "site": {"page": "prebid.org"}, + "site": { + "page": "prebid.org" + }, "imp": [ { "id": "/19968336/header-bid-tag-0", @@ -20,17 +27,25 @@ ] }, "ext": { - "appnexus": { - "placementId": 12883451 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12883451 + } + } } } } ], "regs": { - "ext": {"gdpr": 1} + "ext": { + "gdpr": 1 + } }, "user": { - "ext": {"consent": "some-consent-string"} + "ext": { + "consent": "some-consent-string" + } } }, "expectedAmpResponse": { @@ -48,9 +63,12 @@ }, "warnings": { "general": [ - {"code": 10002, "message": "debug turned off for account"} + { + "code": 10002, + "message": "debug turned off for account" + } ] } }, "expectedReturnCode": 200 -} +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-resp.json b/endpoints/openrtb2/sample-requests/amp/valid-supplementary/imp-with-stored-resp.json similarity index 76% rename from endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-resp.json rename to endpoints/openrtb2/sample-requests/amp/valid-supplementary/imp-with-stored-resp.json index a8b8eb54dfd..eadabcde1c8 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-resp.json +++ b/endpoints/openrtb2/sample-requests/amp/valid-supplementary/imp-with-stored-resp.json @@ -1,27 +1,35 @@ { "description": "Amp request where impression makes reference to a valid stored response with a $5.00 bid", + "query": "tag_id=101", "mockAmpStoredResponse": { "6d718149": "[{\"bid\": [{\"id\": \"bid_id\", \"price\": 5.00, \"ext\": {\"prebid\": {\"targeting\": { \"hb_pb\": \"1.20\", \"hb_cat\": \"IAB-20\", \"hb_cache_id\": \"some_id\"}}}}],\"seat\": \"appnexus\"}]" }, "mockBidRequest": { "id": "request-with-stored-resp", - "site": {"page": "test.somepage.com"}, + "site": { + "page": "test.somepage.com" + }, "imp": [ { "id": "my-imp-id", "banner": { "format": [ - {"w": 300, "h": 600} + { + "w": 300, + "h": 600 + } ] }, "ext": { "prebid": { + "bidder": { + "appnexus": { + "placementId": 12883451 + } + }, "storedauctionresponse": { "id": "6d718149" } - }, - "appnexus": { - "placementId": 12883451 } } } @@ -42,9 +50,12 @@ }, "warnings": { "general": [ - {"code": 10002, "message": "debug turned off for account"} + { + "code": 10002, + "message": "debug turned off for account" + } ] } }, "expectedReturnCode": 200 -} +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/disabled/bad/bad-bidder.json b/endpoints/openrtb2/sample-requests/disabled/bad/bad-bidder.json index 91e760ee41e..c871110ee04 100644 --- a/endpoints/openrtb2/sample-requests/disabled/bad/bad-bidder.json +++ b/endpoints/openrtb2/sample-requests/disabled/bad/bad-bidder.json @@ -25,5 +25,5 @@ ] }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: Bidder \"appnexus\" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.\nInvalid request: request.imp[0].ext must contain at least one bidder\n" + "expectedErrorMessage": "Invalid request: Bidder \"appnexus\" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.\nInvalid request: request.imp[0].ext.prebid.bidder must contain at least one bidder\n" } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-empty.json b/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-empty.json index 6c60ed5def2..d4b8cdcfca4 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-empty.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-empty.json @@ -18,5 +18,5 @@ } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.imp[0].ext must contain at least one bidder\n" + "expectedErrorMessage": "Invalid request: request.imp[0].ext.prebid.bidder must contain at least one bidder\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 1363eefbe22..11df84b2035 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 @@ -23,5 +23,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: request.imp[0].ext.prebid.bidder.appnexus failed validation.\n(root): Invalid type. Expected: object, given: string\n" } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-unknown-bidder.json b/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-unknown-bidder.json index 9f5a45c861d..8a7b7932179 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-unknown-bidder.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-unknown-bidder.json @@ -22,5 +22,5 @@ } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.imp[0].ext contains unknown bidder: noBidderShouldEverHaveThisName. Did you forget an alias in request.ext.prebid.aliases?\n" + "expectedErrorMessage": "Invalid request: request.imp[0].ext.prebid.bidder contains unknown bidder: noBidderShouldEverHaveThisName. Did you forget an alias in request.ext.prebid.aliases?\n" } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-invalid.json b/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-invalid.json index 1847dc72283..03e789eef86 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-invalid.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-invalid.json @@ -44,5 +44,5 @@ } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.regs.ext.gdpr must be either 0 or 1.\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-gdpr-string.json b/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-string.json index 4a315911906..dc15410a290 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.gdpr must be either 0 or 1.\n" + "expectedErrorMessage": "Invalid request: request.regs.ext is invalid: gdpr must be an integer\n" } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-eids-uids-empty.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-eids-uids-empty.json deleted file mode 100644 index d1cd1eb512e..00000000000 --- a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-eids-uids-empty.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "description": "Bid request with empty uids array in user.ext.eids array element", - "mockBidRequest": { - "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5", - "site": { - "page": "prebid.org", - "publisher": { - "id": "a3de7af2-a86a-4043-a77b-c7e86744155e" - } - }, - "source": { - "tid": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5" - }, - "tmax": 1000, - "imp": [ - { - "id": "/19968336/header-bid-tag-0", - "ext": { - "appnexus": { - "placementId": 12883451 - } - }, - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 300 - } - ] - } - } - ], - "regs": { - "ext": { - "gdpr": 1 - } - }, - "user": { - "ext": { - "eids": [ - { - "source": "source1", - "uids": [] - } - ] - } - } - }, - "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.user.ext.eids[0].uids must contain at least one element or be undefined\n" -} diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-empty.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-empty.json index 98297739105..0baf98955b1 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-empty.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-empty.json @@ -1,44 +1,33 @@ { "description": "Bid request with empty eids array in user.ext", "mockBidRequest": { - "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5", + "id": "anyRequestID", "site": { "page": "prebid.org", "publisher": { - "id": "a3de7af2-a86a-4043-a77b-c7e86744155e" + "id": "anyPublisher" } }, - "source": { - "tid": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5" - }, - "tmax": 1000, - "imp": [ - { - "id": "/19968336/header-bid-tag-0", - "ext": { - "appnexus": { - "placementId": 12883451 - } - }, - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 300 - } - ] - } - } - ], - "regs": { + "imp": [{ + "id": "anyImpID", "ext": { - "gdpr": 1 + "appnexus": { + "placementId": 42 + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 300 + } + ] } - }, + }], + "tmax": 1000, "user": { "ext": { "eids": [] @@ -47,4 +36,4 @@ }, "expectedReturnCode": 400, "expectedErrorMessage": "Invalid request: request.user.ext.eids must contain at least one element or be undefined\n" -} +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-source-duplicate.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-source-duplicate.json new file mode 100644 index 00000000000..05cd7f34f8c --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-source-duplicate.json @@ -0,0 +1,49 @@ +{ + "description": "Bid request where more than one request.user.ext.eids array elements share the same source field value", + "mockBidRequest": { + "id": "anyRequestID", + "site": { + "page": "prebid.org", + "publisher": { + "id": "anyPublisher" + } + }, + "imp": [{ + "id": "anyImpID", + "ext": { + "appnexus": { + "placementId": 42 + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 300 + } + ] + } + }], + "tmax": 1000, + "user": { + "ext": { + "eids": [{ + "source": "source1", + "uids": [{ + "id": "A" + }] + }, { + "source": "source1", + "uids": [{ + "id": "B" + }] + }] + } + } + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.user.ext.eids must contain unique sources\n" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-source-empty.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-source-empty.json index 06684b5e62e..3a451ecbd76 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-source-empty.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-source-empty.json @@ -1,52 +1,43 @@ { "description": "Bid request with user.ext.eids array element array element that does not contain source field", "mockBidRequest": { - "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5", + "id": "anyRequestID", "site": { "page": "prebid.org", "publisher": { - "id": "a3de7af2-a86a-4043-a77b-c7e86744155e" + "id": "anyPublisher" } }, - "source": { - "tid": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5" - }, - "tmax": 1000, - "imp": [ - { - "id": "/19968336/header-bid-tag-0", - "ext": { - "appnexus": { - "placementId": 12883451 - } - }, - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 300 - } - ] - } - } - ], - "regs": { + "imp": [{ + "id": "anyImpID", "ext": { - "gdpr": 1 + "appnexus": { + "placementId": 42 + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 300 + } + ] } - }, + }], + "tmax": 1000, "user": { "ext": { - "eids": [ - {} - ] + "eids": [{ + "uids": [{ + "id": "A" + }] + }] } } }, "expectedReturnCode": 400, "expectedErrorMessage": "Invalid request: request.user.ext.eids[0] missing required field: \"source\"\n" -} +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-source-unique.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-source-unique.json deleted file mode 100644 index 5a6548c3eb2..00000000000 --- a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-source-unique.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "description": "Bid request where more than one request.user.ext.eids array elements share the same source field value", - "mockBidRequest": { - "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5", - "site": { - "page": "prebid.org", - "publisher": { - "id": "a3de7af2-a86a-4043-a77b-c7e86744155e" - } - }, - "source": { - "tid": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5" - }, - "tmax": 1000, - "imp": [ - { - "id": "/19968336/header-bid-tag-0", - "ext": { - "appnexus": { - "placementId": 12883451 - } - }, - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 300 - } - ] - } - } - ], - "regs": { - "ext": { - "gdpr": 1 - } - }, - "user": { - "ext": { - "eids": [ - { - "source": "source1", - "id": "id1" - }, - { - "source": "source1", - "id": "id2" - } - ] - } - } - }, - "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.user.ext.eids must contain unique sources\n" -} diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-uids-id-empty.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-uids-id-empty.json index faa09f93ad1..910e9650d75 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-uids-id-empty.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-uids-id-empty.json @@ -1,57 +1,42 @@ { "description": "Bid request where a request.user.ext.eids.uids array element is missing its id field", "mockBidRequest": { - "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5", + "id": "anyRequestID", "site": { "page": "prebid.org", "publisher": { - "id": "a3de7af2-a86a-4043-a77b-c7e86744155e" + "id": "anyPublisher" } }, - "source": { - "tid": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5" - }, - "tmax": 1000, - "imp": [ - { - "id": "/19968336/header-bid-tag-0", - "ext": { - "appnexus": { - "placementId": 12883451 - } - }, - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 300 - } - ] - } - } - ], - "regs": { - "ext": { - "gdpr": 1 - } - }, - "user": { + "imp": [{ + "id": "anyImpID", "ext": { - "eids": [ + "appnexus": { + "placementId": 42 + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }, { - "source": "source1", - "uids": [ - {} - ] + "w": 300, + "h": 300 } ] } + }], + "tmax": 1000, + "user": { + "ext": { + "eids": [{ + "source": "source1", + "uids": [{}] + }] + } } }, "expectedReturnCode": 400, "expectedErrorMessage": "Invalid request: request.user.ext.eids[0].uids[0] missing required field: \"id\"\n" -} +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-id-uids-empty.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-uids-missing.json similarity index 53% rename from endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-id-uids-empty.json rename to endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-uids-missing.json index 09ad4eeb68a..eed386b4c7d 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-id-uids-empty.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-uids-missing.json @@ -1,5 +1,5 @@ { - "description": "Bid request with user.ext.eids array element array element that does not contain an id nor uids", + "description": "Bid request with user.ext.eids array element array element that does not contain uids", "mockBidRequest": { "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5", "site": { @@ -12,28 +12,25 @@ "tid": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5" }, "tmax": 1000, - "imp": [ - { - "id": "/19968336/header-bid-tag-0", - "ext": { - "appnexus": { - "placementId": 12883451 - } - }, - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 300 - } - ] + "imp": [{ + "id": "/19968336/header-bid-tag-0", + "ext": { + "appnexus": { + "placementId": 12883451 } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 300 + } + ] } - ], + }], "regs": { "ext": { "gdpr": 1 @@ -41,14 +38,12 @@ }, "user": { "ext": { - "eids": [ - { - "source": "source1" - } - ] + "eids": [{ + "source": "source1" + }] } } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.user.ext.eids[0] must contain either \"id\" or \"uids\" field\n" -} + "expectedErrorMessage": "Invalid request: request.user.ext.eids[0].uids must contain at least one element or be undefined\n" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/hooks/auction_reject.json b/endpoints/openrtb2/sample-requests/valid-whole/hooks/auction_reject.json new file mode 100644 index 00000000000..9b77ce561e2 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/hooks/auction_reject.json @@ -0,0 +1,43 @@ +{ + "description": "Simple request", + "config": { + "mockBidders": [ + {"bidderName": "appnexus", "currency": "USD", "price": 0.00} + ] + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "tmax": 50, + "ext": {} + }, + "expectedBidResponse": { + "id": "some-request-id", + "nbr": 123 + }, + "expectedReturnCode": 200 +} 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 index 274c2c70f4b..425070d17a6 100644 --- 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 @@ -84,17 +84,18 @@ } }, "expectedImpExt": { - "appnexus": { - "placementId": 111111 - }, - "pubmatic": { - "publisherId": "5678", - "wrapper": { - "profile": 1234 - } - }, "prebid": { - "bidder": {} + "bidder": { + "appnexus": { + "placementId": 111111 + }, + "pubmatic": { + "publisherId": "5678", + "wrapper": { + "profile": 1234 + } + } + } } }, "expectedReturnCode": 200 diff --git a/endpoints/openrtb2/test_utils.go b/endpoints/openrtb2/test_utils.go index d42ba22fc00..f5b156e1d98 100644 --- a/endpoints/openrtb2/test_utils.go +++ b/endpoints/openrtb2/test_utils.go @@ -6,10 +6,10 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" "net" "net/http" "net/http/httptest" + "os" "path/filepath" "strconv" "testing" @@ -17,7 +17,8 @@ import ( "github.com/buger/jsonparser" "github.com/julienschmidt/httprouter" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" + "github.com/prebid/openrtb/v17/openrtb3" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/analytics" analyticsConf "github.com/prebid/prebid-server/analytics/config" @@ -25,7 +26,10 @@ import ( "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/exchange" + "github.com/prebid/prebid-server/experiment/adscert" "github.com/prebid/prebid-server/gdpr" + "github.com/prebid/prebid-server/hooks" + "github.com/prebid/prebid-server/hooks/hookstage" "github.com/prebid/prebid-server/metrics" metricsConfig "github.com/prebid/prebid-server/metrics/config" "github.com/prebid/prebid-server/openrtb_ext" @@ -56,12 +60,15 @@ const ( type testCase struct { // Common - endpointType int - Description string `json:"description"` - Config *testConfigValues `json:"config"` - BidRequest json.RawMessage `json:"mockBidRequest"` - ExpectedReturnCode int `json:"expectedReturnCode,omitempty"` - ExpectedErrorMessage string `json:"expectedErrorMessage"` + endpointType int + Description string `json:"description"` + Config *testConfigValues `json:"config"` + BidRequest json.RawMessage `json:"mockBidRequest"` + ExpectedValidatedBidReq json.RawMessage `json:"expectedValidatedBidRequest"` + ExpectedReturnCode int `json:"expectedReturnCode,omitempty"` + ExpectedErrorMessage string `json:"expectedErrorMessage"` + Query string `json:"query"` + planBuilder hooks.ExecutionPlanBuilder // "/openrtb2/auction" endpoint JSON test info ExpectedBidResponse json.RawMessage `json:"expectedBidResponse"` @@ -117,6 +124,17 @@ var testStoredRequestData = map[string]json.RawMessage{ }`), // Valid JSON "4": json.RawMessage(`{"id": "ThisID", "cur": ["USD"]}`), + + // Stored Request with Root Ext Passthrough + "5": json.RawMessage(`{ + "ext": { + "prebid": { + "passthrough": { + "root_ext_passthrough": 20 + } + } + } + }`), } // Stored Imp Requests @@ -195,6 +213,17 @@ var testStoredImpData = map[string]json.RawMessage{ } } }`), + // Stored Imp with Passthrough + "6": json.RawMessage(`{ + "id": "my-imp-id", + "ext": { + "prebid": { + "passthrough": { + "imp_passthrough": 30 + } + } + } + }`), } // Incoming requests with stored request IDs @@ -321,6 +350,32 @@ var testStoredRequests = []string{ } } }`, + `{ + "id": "ThisID", + "imp": [ + { + "id": "my-imp-id", + "video":{ + "h":300, + "w":200 + }, + "ext": { + "prebid": { + "storedrequest": { + "id": "6" + } + } + } + } + ], + "ext": { + "prebid": { + "storedrequest": { + "id": "5" + } + } + } + }`, } // The expected requests after stored request processing @@ -436,8 +491,7 @@ var testFinalRequests = []string{ } ], "tmax": 500 - } -`, + }`, `{ "id": "ThisID", "imp": [ @@ -487,11 +541,43 @@ var testFinalRequests = []string{ } } } +}`, + `{ + "id": "ThisID", + "imp": [ + { + "ext":{ + "prebid":{ + "passthrough":{ + "imp_passthrough":30 + }, + "storedrequest":{ + "id":"6" + } + } + }, + "id":"my-imp-id", + "video":{ + "h":300, + "w":200 + } + } + ], + "ext":{ + "prebid":{ + "passthrough":{ + "root_ext_passthrough":20 + }, + "storedrequest":{ + "id":"5" + } + } + } }`, } var testStoredImpIds = []string{ - "adUnit1", "adUnit2", "adUnit1", "some-static-imp", + "adUnit1", "adUnit2", "adUnit1", "some-static-imp", "my-imp-id", } var testStoredImps = []string{ @@ -543,6 +629,16 @@ var testStoredImps = []string{ } }`, ``, + `{ + "id": "my-imp-id", + "ext": { + "prebid": { + "passthrough": { + "imp_passthrough": 30 + } + } + } + }`, } var testBidRequests = []string{ @@ -804,7 +900,7 @@ func (e *nobidExchange) HoldAuction(ctx context.Context, auctionRequest exchange return &openrtb2.BidResponse{ ID: r.BidRequest.ID, BidID: "test bid id", - NBR: openrtb2.NoBidReasonCodeUnknownError.Ptr(), + NBR: openrtb3.NoBidUnknownError.Ptr(), }, nil } @@ -895,11 +991,13 @@ func (b mockBidderHandler) bid(w http.ResponseWriter, req *http.Request) { // mockAdapter is a mock impression-splitting adapter type mockAdapter struct { mockServerURL string + Server config.Server } -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { adapter := &mockAdapter{ mockServerURL: config.Endpoint, + Server: server, } return adapter, nil } @@ -971,28 +1069,31 @@ func (a mockAdapter) MakeBids(request *openrtb2.BidRequest, requestData *adapter // Auxiliary functions that don't make assertions and don't // take t *testing.T as parameter // --------------------------------------------------------- -func getBidderInfos(cfg map[string]config.Adapter, biddersNames []openrtb_ext.BidderName) config.BidderInfos { +func getBidderInfos(disabledAdapters []string, biddersNames []openrtb_ext.BidderName) config.BidderInfos { biddersInfos := make(config.BidderInfos) for _, name := range biddersNames { - adapterConfig, ok := cfg[string(name)] - if !ok { - adapterConfig = config.Adapter{} + isDisabled := false + for _, disabledAdapter := range disabledAdapters { + if string(name) == disabledAdapter { + isDisabled = true + break + } } - biddersInfos[string(name)] = newBidderInfo(adapterConfig) + biddersInfos[string(name)] = newBidderInfo(isDisabled) } return biddersInfos } -func newBidderInfo(cfg config.Adapter) config.BidderInfo { +func newBidderInfo(isDisabled bool) config.BidderInfo { return config.BidderInfo{ - Enabled: !cfg.Disabled, + Disabled: isDisabled, } } func getTestFiles(dir string) ([]string, error) { var filesToAssert []string - fileList, err := ioutil.ReadDir(dir) + fileList, err := os.ReadDir(dir) if err != nil { return nil, err } @@ -1072,19 +1173,27 @@ func (tc *testConfigValues) getBlackListedAccountMap() map[string]bool { return blacklistedAccountMap } -func (tc *testConfigValues) getAdaptersConfigMap() map[string]config.Adapter { - var adaptersConfig map[string]config.Adapter +// exchangeTestWrapper is a wrapper that asserts the openrtb2 bid request just before the HoldAuction call +type exchangeTestWrapper struct { + ex exchange.Exchange + actualValidatedBidReq *openrtb2.BidRequest +} - if len(tc.DisabledAdapters) > 0 { - adaptersConfig = make(map[string]config.Adapter, len(tc.DisabledAdapters)) - for _, adapterName := range tc.DisabledAdapters { - adaptersConfig[adapterName] = config.Adapter{Disabled: true} - } +func (te *exchangeTestWrapper) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) { + + // rebuild/resync the request in the request wrapper. + if err := r.BidRequestWrapper.RebuildRequest(); err != nil { + return nil, err } - return adaptersConfig + + // Save the validated bidRequest that we are about to feed HoldAuction + te.actualValidatedBidReq = r.BidRequestWrapper.BidRequest + + // Call HoldAuction() implementation as written in the exchange package + return te.ex.HoldAuction(ctx, r, debugLog) } -// buildTestExchange returns an exchange with mock bidder servers and mock currency convertion server +// buildTestExchange returns an exchange with mock bidder servers and mock currency conversion server func buildTestExchange(testCfg *testConfigValues, adapterMap map[openrtb_ext.BidderName]exchange.AdaptedBidder, mockBidServersArray []*httptest.Server, mockCurrencyRatesServer *httptest.Server, bidderInfos config.BidderInfos, cfg *config.Configuration, met metrics.MetricsEngine, mockFetcher stored_requests.CategoryFetcher) (exchange.Exchange, []*httptest.Server) { if len(testCfg.MockBidders) == 0 { testCfg.MockBidders = append(testCfg.MockBidders, mockBidderHandler{BidderName: "appnexus", Currency: "USD", Price: 0.00}) @@ -1094,27 +1203,42 @@ func buildTestExchange(testCfg *testConfigValues, adapterMap map[openrtb_ext.Bid bidderAdapter := mockAdapter{mockServerURL: bidServer.URL} bidderName := openrtb_ext.BidderName(mockBidder.BidderName) - adapterMap[bidderName] = exchange.AdaptBidder(bidderAdapter, bidServer.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, bidderName, nil) + adapterMap[bidderName] = exchange.AdaptBidder(bidderAdapter, bidServer.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, bidderName, nil, "") mockBidServersArray = append(mockBidServersArray, bidServer) } mockCurrencyConverter := currency.NewRateConverter(mockCurrencyRatesServer.Client(), mockCurrencyRatesServer.URL, time.Second) mockCurrencyConverter.Run() - return exchange.NewExchange(adapterMap, + gdprPermsBuilder := fakePermissionsBuilder{ + permissions: &fakePermissions{}, + }.Builder + tcf2ConfigBuilder := fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + }.Builder + + testExchange := exchange.NewExchange(adapterMap, &wellBehavedCache{}, cfg, nil, met, bidderInfos, - gdpr.NewVendorListFetcher(context.Background(), config.GDPR{}, &http.Client{}, gdpr.VendorListURLMaker), + gdprPermsBuilder, + tcf2ConfigBuilder, mockCurrencyConverter, mockFetcher, - ), mockBidServersArray + &adscert.NilSigner{}, + ) + + testExchange = &exchangeTestWrapper{ + ex: testExchange, + } + + return testExchange, mockBidServersArray } // buildTestEndpoint instantiates an openrtb2 Auction endpoint designed to test endpoints/openrtb2/auction.go -func buildTestEndpoint(test testCase, cfg *config.Configuration) (httprouter.Handle, []*httptest.Server, *httptest.Server, error) { +func buildTestEndpoint(test testCase, cfg *config.Configuration) (httprouter.Handle, *exchangeTestWrapper, []*httptest.Server, *httptest.Server, error) { if test.Config == nil { test.Config = &testConfigValues{} } @@ -1124,13 +1248,13 @@ func buildTestEndpoint(test testCase, cfg *config.Configuration) (httprouter.Han var err error paramValidator, err = openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } } else { paramValidator = mockBidderParamValidator{} } - bidderInfos := getBidderInfos(test.Config.getAdaptersConfigMap(), openrtb_ext.CoreBidderNames()) + bidderInfos := getBidderInfos(test.Config.DisabledAdapters, openrtb_ext.CoreBidderNames()) bidderMap := exchange.GetActiveBidders(bidderInfos) disabledBidders := exchange.GetDisabledBiddersErrorMessages(bidderInfos) met := &metricsConfig.NilMetricsEngine{} @@ -1148,7 +1272,7 @@ func buildTestEndpoint(test testCase, cfg *config.Configuration) (httprouter.Han } mockCurrencyRatesServer := httptest.NewServer(http.HandlerFunc(mockCurrencyConversionService.handle)) - ex, mockBidServersArray := buildTestExchange(test.Config, adapterMap, mockBidServersArray, mockCurrencyRatesServer, bidderInfos, cfg, met, mockFetcher) + testExchange, mockBidServersArray := buildTestExchange(test.Config, adapterMap, mockBidServersArray, mockCurrencyRatesServer, bidderInfos, cfg, met, mockFetcher) var storedRequestFetcher stored_requests.Fetcher if len(test.storedRequest) > 0 { @@ -1164,7 +1288,19 @@ func buildTestEndpoint(test testCase, cfg *config.Configuration) (httprouter.Han storedResponseFetcher = empty_fetcher.EmptyFetcher{} } - var endpointBuilder func(uuidutil.UUIDGenerator, exchange.Exchange, openrtb_ext.BidderParamValidator, stored_requests.Fetcher, stored_requests.AccountFetcher, *config.Configuration, metrics.MetricsEngine, analytics.PBSAnalyticsModule, map[string]string, []byte, map[string]openrtb_ext.BidderName, stored_requests.Fetcher) (httprouter.Handle, error) + var accountFetcher stored_requests.AccountFetcher + accountFetcher = &mockAccountFetcher{ + data: map[string]json.RawMessage{ + "malformed_acct": json.RawMessage(`{"disabled":"invalid type"}`), + }, + } + + planBuilder := test.planBuilder + if planBuilder == nil { + planBuilder = hooks.EmptyPlanBuilder{} + } + + var endpointBuilder func(uuidutil.UUIDGenerator, exchange.Exchange, openrtb_ext.BidderParamValidator, stored_requests.Fetcher, stored_requests.AccountFetcher, *config.Configuration, metrics.MetricsEngine, analytics.PBSAnalyticsModule, map[string]string, []byte, map[string]openrtb_ext.BidderName, stored_requests.Fetcher, hooks.ExecutionPlanBuilder) (httprouter.Handle, error) switch test.endpointType { case AMP_ENDPOINT: @@ -1175,10 +1311,10 @@ func buildTestEndpoint(test testCase, cfg *config.Configuration) (httprouter.Han endpoint, err := endpointBuilder( fakeUUIDGenerator{}, - ex, + testExchange, paramValidator, storedRequestFetcher, - mockFetcher, + accountFetcher, cfg, met, analyticsConf.NewPBSAnalytics(&config.Analytics{}), @@ -1186,9 +1322,10 @@ func buildTestEndpoint(test testCase, cfg *config.Configuration) (httprouter.Han []byte(test.Config.AliasJSON), bidderMap, storedResponseFetcher, + planBuilder, ) - return endpoint, mockBidServersArray, mockCurrencyRatesServer, err + return endpoint, testExchange.(*exchangeTestWrapper), mockBidServersArray, mockCurrencyRatesServer, err } type mockBidderParamValidator struct{} @@ -1198,6 +1335,17 @@ func (v mockBidderParamValidator) Validate(name openrtb_ext.BidderName, ext json } func (v mockBidderParamValidator) Schema(name openrtb_ext.BidderName) string { return "" } +type mockAccountFetcher struct { + data map[string]json.RawMessage +} + +func (af *mockAccountFetcher) FetchAccount(ctx context.Context, accountID string) (json.RawMessage, []error) { + if account, ok := af.data[accountID]; ok { + return account, nil + } + return nil, []error{stored_requests.NotFoundError{ID: accountID, DataType: "Account"}} +} + type mockAmpStoredReqFetcher struct { data map[string]json.RawMessage } @@ -1248,9 +1396,123 @@ func (c *wellBehavedCache) PutJson(ctx context.Context, values []pbc.Cacheable) } func readFile(t *testing.T, filename string) []byte { - data, err := ioutil.ReadFile(filename) + data, err := os.ReadFile(filename) if err != nil { t.Fatalf("Failed to read file %s: %v", filename, err) } return data } + +type fakePermissionsBuilder struct { + permissions gdpr.Permissions +} + +func (fpb fakePermissionsBuilder) Builder(gdpr.TCF2ConfigReader, gdpr.RequestInfo) gdpr.Permissions { + return fpb.permissions +} + +type fakeTCF2ConfigBuilder struct { + cfg gdpr.TCF2ConfigReader +} + +func (fcr fakeTCF2ConfigBuilder) Builder(hostConfig config.TCF2, accountConfig config.AccountGDPR) gdpr.TCF2ConfigReader { + return fcr.cfg +} + +type fakePermissions struct { +} + +func (p *fakePermissions) HostCookiesAllowed(ctx context.Context) (bool, error) { + return true, nil +} + +func (p *fakePermissions) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName) (bool, error) { + return true, nil +} + +func (p *fakePermissions) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions gdpr.AuctionPermissions, err error) { + return gdpr.AuctionPermissions{ + AllowBidRequest: true, + }, nil +} + +type mockPlanBuilder struct { + entrypointPlan hooks.Plan[hookstage.Entrypoint] + rawAuctionPlan hooks.Plan[hookstage.RawAuctionRequest] + processedAuctionPlan hooks.Plan[hookstage.ProcessedAuctionRequest] + bidderRequestPlan hooks.Plan[hookstage.BidderRequest] + bidderResponsePlan hooks.Plan[hookstage.RawBidderResponse] + allProcessedBidResponsesPlan hooks.Plan[hookstage.AllProcessedBidResponses] + auctionResponsePlan hooks.Plan[hookstage.AuctionResponse] +} + +func (m mockPlanBuilder) PlanForEntrypointStage(_ string) hooks.Plan[hookstage.Entrypoint] { + return m.entrypointPlan +} + +func (m mockPlanBuilder) PlanForRawAuctionStage(_ string, _ *config.Account) hooks.Plan[hookstage.RawAuctionRequest] { + return m.rawAuctionPlan +} + +func (m mockPlanBuilder) PlanForProcessedAuctionStage(_ string, _ *config.Account) hooks.Plan[hookstage.ProcessedAuctionRequest] { + return m.processedAuctionPlan +} + +func (m mockPlanBuilder) PlanForBidderRequestStage(_ string, _ *config.Account) hooks.Plan[hookstage.BidderRequest] { + return m.bidderRequestPlan +} + +func (m mockPlanBuilder) PlanForRawBidderResponseStage(_ string, _ *config.Account) hooks.Plan[hookstage.RawBidderResponse] { + return m.bidderResponsePlan +} + +func (m mockPlanBuilder) PlanForAllProcessedBidResponsesStage(_ string, _ *config.Account) hooks.Plan[hookstage.AllProcessedBidResponses] { + return m.allProcessedBidResponsesPlan +} + +func (m mockPlanBuilder) PlanForAuctionResponseStage(_ string, _ *config.Account) hooks.Plan[hookstage.AuctionResponse] { + return m.auctionResponsePlan +} + +func makeRejectPlan[H any](hook H) hooks.Plan[H] { + return hooks.Plan[H]{ + { + Timeout: 5 * time.Millisecond, + Hooks: []hooks.HookWrapper[H]{ + { + Module: "foobar", + Code: "foo", + Hook: hook, + }, + }, + }, + } +} + +type mockRejectionHook struct { + nbr int +} + +func (m mockRejectionHook) HandleEntrypointHook( + _ context.Context, + _ hookstage.ModuleInvocationContext, + _ hookstage.EntrypointPayload, +) (hookstage.HookResult[hookstage.EntrypointPayload], error) { + return hookstage.HookResult[hookstage.EntrypointPayload]{Reject: true, NbrCode: m.nbr}, nil +} + +func (m mockRejectionHook) HandleRawAuctionHook( + _ context.Context, + _ hookstage.ModuleInvocationContext, + _ hookstage.RawAuctionRequestPayload, +) (hookstage.HookResult[hookstage.RawAuctionRequestPayload], error) { + return hookstage.HookResult[hookstage.RawAuctionRequestPayload]{Reject: true, NbrCode: m.nbr}, nil +} + +func (m mockRejectionHook) HandleProcessedAuctionHook( + _ context.Context, + _ hookstage.ModuleInvocationContext, + _ hookstage.ProcessedAuctionRequestPayload, +) (hookstage.HookResult[hookstage.ProcessedAuctionRequestPayload], error) { + return hookstage.HookResult[hookstage.ProcessedAuctionRequestPayload]{Reject: true, NbrCode: m.nbr}, nil +} diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index ec5b1c579c5..5c71bc5e6e8 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "net/http" "net/url" "regexp" @@ -18,7 +17,8 @@ import ( "github.com/gofrs/uuid" "github.com/golang/glog" "github.com/julienschmidt/httprouter" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" + "github.com/prebid/prebid-server/hooks/hookexecution" jsonpatch "gopkg.in/evanphx/json-patch.v4" accountService "github.com/prebid/prebid-server/account" @@ -26,7 +26,6 @@ import ( "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/prebid_cache_client" @@ -86,31 +85,32 @@ func NewVideoEndpoint( cache, videoEndpointRegexp, ipValidator, - empty_fetcher.EmptyFetcher{}}).VideoAuctionEndpoint), nil + empty_fetcher.EmptyFetcher{}, + &hookexecution.EmptyHookExecutor{}}).VideoAuctionEndpoint), nil } /* -1. Parse "storedrequestid" field from simplified endpoint request body. -2. If config flag to require that field is set (which it will be for us) and this field is not given then error out here. -3. Load the stored request JSON for the given storedrequestid, if the id was invalid then error out here. -4. Use "json-patch" 3rd party library to merge the request body JSON data into the stored request JSON data. -5. Unmarshal the merged JSON data into a Go structure. -6. Add fields from merged JSON data that correspond to an OpenRTB request into the OpenRTB bid request we are building. - a. Unmarshal certain OpenRTB defined structs directly into the OpenRTB bid request. - b. In cases where customized logic is needed just copy/fill the fields in directly. -7. Call setFieldsImplicitly from auction.go to get basic data from the HTTP request into an OpenRTB bid request to start building the OpenRTB bid request. -8. Loop through ad pods to build array of Imps into OpenRTB request, for each pod: - a. Load the stored impression to use as the basis for impressions generated for this pod from the configid field. - b. NumImps = adpoddurationsec / MIN_VALUE(allowedDurations) - c. Build impression array for this pod: - I.Create array of NumImps entries initialized to the base impression loaded from the configid. - 1. If requireexactdurations = true, iterate over allowdDurations and for (NumImps / len(allowedDurations)) number of Imps set minduration = maxduration = allowedDurations[i] - 2. If requireexactdurations = false, set maxduration = MAX_VALUE(allowedDurations) - II. Set Imp.id field to "podX_Y" where X is the pod index and Y is the impression index within this pod. - d. Append impressions for this pod to the overall list of impressions in the OpenRTB bid request. -9. Call validateRequest() function from auction.go to validate the generated request. -10. Call HoldAuction() function to run the auction for the OpenRTB bid request that was built in the previous step. -11. Build proper response format. + 1. Parse "storedrequestid" field from simplified endpoint request body. + 2. If config flag to require that field is set (which it will be for us) and this field is not given then error out here. + 3. Load the stored request JSON for the given storedrequestid, if the id was invalid then error out here. + 4. Use "json-patch" 3rd party library to merge the request body JSON data into the stored request JSON data. + 5. Unmarshal the merged JSON data into a Go structure. + 6. Add fields from merged JSON data that correspond to an OpenRTB request into the OpenRTB bid request we are building. + a. Unmarshal certain OpenRTB defined structs directly into the OpenRTB bid request. + b. In cases where customized logic is needed just copy/fill the fields in directly. + 7. Call setFieldsImplicitly from auction.go to get basic data from the HTTP request into an OpenRTB bid request to start building the OpenRTB bid request. + 8. Loop through ad pods to build array of Imps into OpenRTB request, for each pod: + a. Load the stored impression to use as the basis for impressions generated for this pod from the configid field. + b. NumImps = adpoddurationsec / MIN_VALUE(allowedDurations) + c. Build impression array for this pod: + I.Create array of NumImps entries initialized to the base impression loaded from the configid. + 1. If requireexactdurations = true, iterate over allowdDurations and for (NumImps / len(allowedDurations)) number of Imps set minduration = maxduration = allowedDurations[i] + 2. If requireexactdurations = false, set maxduration = MAX_VALUE(allowedDurations) + II. Set Imp.id field to "podX_Y" where X is the pod index and Y is the impression index within this pod. + d. Append impressions for this pod to the overall list of impressions in the OpenRTB bid request. + 9. Call validateRequest() function from auction.go to validate the generated request. + 10. Call HoldAuction() function to run the auction for the OpenRTB bid request that was built in the previous step. + 11. Build proper response format. */ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { start := time.Now() @@ -161,7 +161,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re R: r.Body, N: deps.cfg.MaxRequestSize, } - requestJson, err := ioutil.ReadAll(lr) + requestJson, err := io.ReadAll(lr) if err != nil { handleError(&labels, w, []error{err}, &vo, &debugLog) return @@ -247,17 +247,20 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re bidReq.Imp = imps bidReq.ID = "bid_id" //TODO: look at prebid.js + // all code after this line should use the bidReqWrapper instead of bidReq directly + bidReqWrapper := &openrtb_ext.RequestWrapper{BidRequest: bidReq} + // Populate any "missing" OpenRTB fields with info from other sources, (e.g. HTTP request headers). - deps.setFieldsImplicitly(r, bidReq) // move after merge + deps.setFieldsImplicitly(r, bidReqWrapper) - errL = deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: bidReq}, false, false, nil) + errL = deps.validateRequest(bidReqWrapper, false, false, nil) if errortypes.ContainsFatalError(errL) { handleError(&labels, w, errL, &vo, &debugLog) return } ctx := context.Background() - timeout := deps.cfg.AuctionTimeouts.LimitAuctionTimeout(time.Duration(bidReq.TMax) * time.Millisecond) + timeout := deps.cfg.AuctionTimeouts.LimitAuctionTimeout(time.Duration(bidReqWrapper.TMax) * time.Millisecond) if timeout > 0 { var cancel context.CancelFunc ctx, cancel = context.WithDeadline(ctx, start.Add(timeout)) @@ -265,17 +268,17 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re } usersyncs := usersync.ParseCookieFromRequest(r, &(deps.cfg.HostCookie)) - if bidReq.App != nil { + if bidReqWrapper.App != nil { labels.Source = metrics.DemandApp - labels.PubID = getAccountID(bidReq.App.Publisher) - } else { // both bidReq.App == nil and bidReq.Site != nil are true + labels.PubID = getAccountID(bidReqWrapper.App.Publisher) + } else { // both bidReqWrapper.App == nil and bidReqWrapper.Site != nil are true labels.Source = metrics.DemandWeb if usersyncs.HasAnyLiveSyncs() { labels.CookieFlag = metrics.CookieFlagYes } else { labels.CookieFlag = metrics.CookieFlagNo } - labels.PubID = getAccountID(bidReq.Site.Publisher) + labels.PubID = getAccountID(bidReqWrapper.Site.Publisher) } // Look up account now that we have resolved the pubID value @@ -288,19 +291,19 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re secGPC := r.Header.Get("Sec-GPC") auctionRequest := exchange.AuctionRequest{ - BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: bidReq}, + BidRequestWrapper: bidReqWrapper, Account: *account, UserSyncs: usersyncs, RequestType: labels.RType, StartTime: start, LegacyLabels: labels, - TCF2ConfigBuilder: gdpr.NewTCF2Config, - GDPRPermissionsBuilder: gdpr.NewPermissions, GlobalPrivacyControlHeader: secGPC, + PubID: labels.PubID, + HookExecutor: deps.hookExecutor, } response, err := deps.ex.HoldAuction(ctx, auctionRequest, &debugLog) - vo.Request = bidReq + vo.Request = bidReqWrapper.BidRequest vo.Response = response if err != nil { errL := []error{err} @@ -376,6 +379,10 @@ func handleError(labels *metrics.Labels, w http.ResponseWriter, errL []error, vo status = http.StatusBadRequest labels.RequestStatus = metrics.RequestStatusBadInput break + } else if erVal == errortypes.MalformedAcctErrorCode { + status = http.StatusInternalServerError + labels.RequestStatus = metrics.RequestStatusAccountConfigErr + break } errors = fmt.Sprintf("%s %s", errors, er.Error()) } diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index 8ffa945a13a..c9cd0aff5a0 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -4,9 +4,9 @@ import ( "context" "encoding/json" "errors" - "io/ioutil" "net/http" "net/http/httptest" + "os" "regexp" "strings" "testing" @@ -14,14 +14,17 @@ import ( "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/errortypes" "github.com/prebid/prebid-server/exchange" + "github.com/prebid/prebid-server/hooks/hookexecution" "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" + "github.com/prebid/openrtb/v17/adcom1" + "github.com/prebid/openrtb/v17/openrtb2" gometrics "github.com/rcrowley/go-metrics" "github.com/stretchr/testify/assert" ) @@ -335,9 +338,7 @@ func TestVideoEndpointValidationsPositive(t *testing.T) { mimes = append(mimes, "mp4") mimes = append(mimes, "") - videoProtocols := make([]openrtb2.Protocol, 0) - videoProtocols = append(videoProtocols, 15) - videoProtocols = append(videoProtocols, 30) + videoProtocols := []adcom1.MediaCreativeSubtype{15, 30} req := openrtb_ext.BidRequestVideo{ StoredRequestId: "123", @@ -378,7 +379,7 @@ func TestVideoEndpointValidationsCritical(t *testing.T) { mimes = append(mimes, "") mimes = append(mimes, "") - videoProtocols := make([]openrtb2.Protocol, 0) + videoProtocols := []adcom1.MediaCreativeSubtype{} req := openrtb_ext.BidRequestVideo{ StoredRequestId: "", @@ -447,9 +448,7 @@ func TestVideoEndpointValidationsPodErrors(t *testing.T) { mimes = append(mimes, "mp4") mimes = append(mimes, "") - videoProtocols := make([]openrtb2.Protocol, 0) - videoProtocols = append(videoProtocols, 15) - videoProtocols = append(videoProtocols, 30) + videoProtocols := []adcom1.MediaCreativeSubtype{15, 30} req := openrtb_ext.BidRequestVideo{ StoredRequestId: "123", @@ -517,9 +516,7 @@ func TestVideoEndpointValidationsSiteAndApp(t *testing.T) { mimes = append(mimes, "mp4") mimes = append(mimes, "") - videoProtocols := make([]openrtb2.Protocol, 0) - videoProtocols = append(videoProtocols, 15) - videoProtocols = append(videoProtocols, 30) + videoProtocols := []adcom1.MediaCreativeSubtype{15, 30} req := openrtb_ext.BidRequestVideo{ StoredRequestId: "123", @@ -575,9 +572,7 @@ func TestVideoEndpointValidationsSiteMissingRequiredField(t *testing.T) { mimes = append(mimes, "mp4") mimes = append(mimes, "") - videoProtocols := make([]openrtb2.Protocol, 0) - videoProtocols = append(videoProtocols, 15) - videoProtocols = append(videoProtocols, 30) + videoProtocols := []adcom1.MediaCreativeSubtype{15, 30} req := openrtb_ext.BidRequestVideo{ StoredRequestId: "123", @@ -817,30 +812,77 @@ func TestMergeOpenRTBToVideoRequest(t *testing.T) { } func TestHandleError(t *testing.T) { - vo := analytics.VideoObject{ - Status: 200, - Errors: make([]error, 0), + tests := []struct { + description string + giveErrors []error + wantCode int + wantMetricsStatus metrics.RequestStatus + }{ + { + description: "Blocked account - return 503 with blocked metrics status", + giveErrors: []error{ + &errortypes.BlacklistedAcct{}, + }, + wantCode: 503, + wantMetricsStatus: metrics.RequestStatusBlacklisted, + }, + { + description: "Blocked app - return 503 with blocked metrics status", + giveErrors: []error{ + &errortypes.BlacklistedApp{}, + }, + wantCode: 503, + wantMetricsStatus: metrics.RequestStatusBlacklisted, + }, + { + description: "Account required error - return 400 with bad input metrics status", + giveErrors: []error{ + &errortypes.AcctRequired{}, + }, + wantCode: 400, + wantMetricsStatus: metrics.RequestStatusBadInput, + }, + { + description: "Malformed account config error - return 500 with account config error metrics status", + giveErrors: []error{ + &errortypes.MalformedAcct{}, + }, + wantCode: 500, + wantMetricsStatus: metrics.RequestStatusAccountConfigErr, + }, + { + description: "Multiple generic errors - return 500 with generic error metrics status", + giveErrors: []error{ + errors.New("Error for testing handleError 1"), + errors.New("Error for testing handleError 2"), + }, + wantCode: 500, + wantMetricsStatus: metrics.RequestStatusErr, + }, } - labels := metrics.Labels{ - Source: metrics.DemandUnknown, - RType: metrics.ReqTypeVideo, - PubID: metrics.PublisherUnknown, - CookieFlag: metrics.CookieFlagUnknown, - RequestStatus: metrics.RequestStatusOK, - } + for _, tt := range tests { + vo := analytics.VideoObject{ + Status: 200, + Errors: make([]error, 0), + } - recorder := httptest.NewRecorder() - err1 := errors.New("Error for testing handleError 1") - err2 := errors.New("Error for testing handleError 2") - handleError(&labels, recorder, []error{err1, err2}, &vo, nil) + labels := metrics.Labels{ + Source: metrics.DemandUnknown, + RType: metrics.ReqTypeVideo, + PubID: metrics.PublisherUnknown, + CookieFlag: metrics.CookieFlagUnknown, + RequestStatus: metrics.RequestStatusOK, + } - assert.Equal(t, metrics.RequestStatusErr, labels.RequestStatus, "labels.RequestStatus should indicate an error") - assert.Equal(t, 500, recorder.Code, "Error status should be written to writer") - assert.Equal(t, 500, vo.Status, "Analytics object should have error status") - assert.Equal(t, 2, len(vo.Errors), "New errors should be appended to Analytics object Errors") - assert.Equal(t, "Error for testing handleError 1", vo.Errors[0].Error(), "Error in Analytics object should have test error message for first error") - assert.Equal(t, "Error for testing handleError 2", vo.Errors[1].Error(), "Error in Analytics object should have test error message for second error") + recorder := httptest.NewRecorder() + handleError(&labels, recorder, tt.giveErrors, &vo, nil) + + assert.Equal(t, tt.wantMetricsStatus, labels.RequestStatus, tt.description) + assert.Equal(t, tt.wantCode, recorder.Code, tt.description) + assert.Equal(t, tt.wantCode, vo.Status, tt.description) + assert.ElementsMatch(t, tt.giveErrors, vo.Errors, tt.description) + } } func TestHandleErrorMetrics(t *testing.T) { @@ -998,25 +1040,25 @@ func TestCreateImpressionTemplate(t *testing.T) { imp := openrtb2.Imp{} imp.Video = &openrtb2.Video{} - imp.Video.Protocols = []openrtb2.Protocol{1, 2} + imp.Video.Protocols = []adcom1.MediaCreativeSubtype{1, 2} imp.Video.MIMEs = []string{"video/mp4"} imp.Video.H = 200 imp.Video.W = 400 - imp.Video.PlaybackMethod = []openrtb2.PlaybackMethod{5, 6} + imp.Video.PlaybackMethod = []adcom1.PlaybackMethod{5, 6} video := openrtb2.Video{} - video.Protocols = []openrtb2.Protocol{3, 4} + video.Protocols = []adcom1.MediaCreativeSubtype{3, 4} video.MIMEs = []string{"video/flv"} video.H = 300 video.W = 0 - video.PlaybackMethod = []openrtb2.PlaybackMethod{7, 8} + video.PlaybackMethod = []adcom1.PlaybackMethod{7, 8} res := createImpressionTemplate(imp, &video) - assert.Equal(t, res.Video.Protocols, []openrtb2.Protocol{3, 4}, "Incorrect video protocols") + assert.Equal(t, res.Video.Protocols, []adcom1.MediaCreativeSubtype{3, 4}, "Incorrect video protocols") assert.Equal(t, res.Video.MIMEs, []string{"video/flv"}, "Incorrect video MIMEs") assert.Equal(t, int(res.Video.H), 300, "Incorrect video height") assert.Equal(t, int(res.Video.W), 0, "Incorrect video width") - assert.Equal(t, res.Video.PlaybackMethod, []openrtb2.PlaybackMethod{7, 8}, "Incorrect video playback method") + assert.Equal(t, res.Video.PlaybackMethod, []adcom1.PlaybackMethod{7, 8}, "Incorrect video playback method") } func TestCCPA(t *testing.T) { @@ -1175,7 +1217,7 @@ func TestVideoAuctionResponseHeaders(t *testing.T) { func mockDepsWithMetrics(t *testing.T, ex *mockExchangeVideo) (*endpointDeps, *metrics.Metrics, *mockAnalyticsModule) { mockModule := &mockAnalyticsModule{} - metrics := metrics.NewMetrics(gometrics.NewRegistry(), openrtb_ext.CoreBidderNames(), config.DisabledMetrics{}, nil) + metrics := metrics.NewMetrics(gometrics.NewRegistry(), openrtb_ext.CoreBidderNames(), config.DisabledMetrics{}, nil, nil) deps := &endpointDeps{ fakeUUIDGenerator{}, @@ -1183,7 +1225,7 @@ func mockDepsWithMetrics(t *testing.T, ex *mockExchangeVideo) (*endpointDeps, *m mockBidderParamValidator{}, &mockVideoStoredReqFetcher{}, &mockVideoStoredReqFetcher{}, - empty_fetcher.EmptyFetcher{}, + &mockAccountFetcher{data: mockVideoAccountData}, &config.Configuration{MaxRequestSize: maxSize}, metrics, mockModule, @@ -1195,6 +1237,7 @@ func mockDepsWithMetrics(t *testing.T, ex *mockExchangeVideo) (*endpointDeps, *m nil, hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, + &hookexecution.EmptyHookExecutor{}, } return deps, metrics, mockModule } @@ -1227,7 +1270,7 @@ func mockDeps(t *testing.T, ex *mockExchangeVideo) *endpointDeps { mockBidderParamValidator{}, &mockVideoStoredReqFetcher{}, &mockVideoStoredReqFetcher{}, - empty_fetcher.EmptyFetcher{}, + &mockAccountFetcher{data: mockVideoAccountData}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), @@ -1239,6 +1282,7 @@ func mockDeps(t *testing.T, ex *mockExchangeVideo) *endpointDeps { regexp.MustCompile(`[<>]`), hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, + &hookexecution.EmptyHookExecutor{}, } } @@ -1261,6 +1305,7 @@ func mockDepsAppendBidderNames(t *testing.T, ex *mockExchangeAppendBidderNames) regexp.MustCompile(`[<>]`), hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, + &hookexecution.EmptyHookExecutor{}, } return deps @@ -1285,6 +1330,7 @@ func mockDepsNoBids(t *testing.T, ex *mockExchangeVideoNoBids) *endpointDeps { regexp.MustCompile(`[<>]`), hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, + &hookexecution.EmptyHookExecutor{}, } return edep @@ -1400,6 +1446,12 @@ func (m *mockExchangeVideoNoBids) HoldAuction(ctx context.Context, r exchange.Au }, nil } +var mockVideoAccountData = map[string]json.RawMessage{ + "valid_acct": json.RawMessage(`{"disabled":false}`), + "disabled_acct": json.RawMessage(`{"disabled":true}`), + "malformed_acct": json.RawMessage(`{"disabled":"invalid type"}`), +} + var testVideoStoredImpData = map[string]json.RawMessage{ "fba10607-0c12-43d1-ad07-b8a513bc75d6": json.RawMessage(`{"ext": {"appnexus": {"placementId": 14997137}}}`), "8b452b41-2681-4a20-9086-6f16ffad7773": json.RawMessage(`{"ext": {"appnexus": {"placementId": 15016213}}}`), @@ -1411,7 +1463,7 @@ var testVideoStoredRequestData = map[string]json.RawMessage{ } func readVideoTestFile(t *testing.T, filename string) string { - requestData, err := ioutil.ReadFile(filename) + requestData, err := os.ReadFile(filename) if err != nil { t.Fatalf("Failed to fetch a valid request: %v", err) } diff --git a/endpoints/setuid.go b/endpoints/setuid.go index 014ff3122c1..53ad228d8ee 100644 --- a/endpoints/setuid.go +++ b/endpoints/setuid.go @@ -10,10 +10,12 @@ import ( "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/gdpr" "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/stored_requests" "github.com/prebid/prebid-server/usersync" "github.com/prebid/prebid-server/util/httputil" ) @@ -26,8 +28,8 @@ const ( chromeiOSStrLen = len(chromeiOSStr) ) -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 +func NewSetUIDEndpoint(cfg *config.Configuration, syncersByBidder map[string]usersync.Syncer, gdprPermsBuilder gdpr.PermissionsBuilder, tcf2CfgBuilder gdpr.TCF2ConfigBuilder, pbsanalytics analytics.PBSAnalyticsModule, accountsFetcher stored_requests.AccountFetcher, metricsEngine metrics.MetricsEngine) httprouter.Handle { + cookieTTL := time.Duration(cfg.HostCookie.TTL) * 24 * time.Hour // 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. @@ -44,7 +46,7 @@ func NewSetUIDEndpoint(cfg config.HostCookie, syncersByBidder map[string]usersyn defer pbsanalytics.LogSetUIDObject(&so) - pc := usersync.ParseCookieFromRequest(r, &cfg) + pc := usersync.ParseCookieFromRequest(r, &cfg.HostCookie) if !pc.AllowSyncs() { w.WriteHeader(http.StatusUnauthorized) metricsEngine.RecordSetUid(metrics.SetUidOptOut) @@ -59,6 +61,7 @@ func NewSetUIDEndpoint(cfg config.HostCookie, syncersByBidder map[string]usersyn w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) metricsEngine.RecordSetUid(metrics.SetUidSyncerUnknown) + so.Errors = []error{err} so.Status = http.StatusBadRequest return } @@ -69,11 +72,38 @@ func NewSetUIDEndpoint(cfg config.HostCookie, syncersByBidder map[string]usersyn w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) metricsEngine.RecordSetUid(metrics.SetUidBadRequest) + so.Errors = []error{err} so.Status = http.StatusBadRequest return } - if shouldReturn, status, body := preventSyncsGDPR(query.Get("gdpr"), query.Get("gdpr_consent"), perms); shouldReturn { + accountID := query.Get("account") + if accountID == "" { + accountID = metrics.PublisherUnknown + } + account, fetchErrs := accountService.GetAccount(context.Background(), cfg, accountsFetcher, accountID) + if len(fetchErrs) > 0 { + w.WriteHeader(http.StatusBadRequest) + err := combineErrors(fetchErrs) + w.Write([]byte(err.Error())) + switch err { + case errCookieSyncAccountBlocked: + metricsEngine.RecordSetUid(metrics.SetUidAccountBlocked) + case errCookieSyncAccountConfigMalformed: + metricsEngine.RecordSetUid(metrics.SetUidAccountConfigMalformed) + case errCookieSyncAccountInvalid: + metricsEngine.RecordSetUid(metrics.SetUidAccountInvalid) + default: + metricsEngine.RecordSetUid(metrics.SetUidBadRequest) + } + so.Errors = []error{err} + so.Status = http.StatusBadRequest + return + } + + tcf2Cfg := tcf2CfgBuilder(cfg.GDPR.TCF2, account.GDPR) + + if shouldReturn, status, body := preventSyncsGDPR(query.Get("gdpr"), query.Get("gdpr_consent"), gdprPermsBuilder, tcf2Cfg); shouldReturn { w.WriteHeader(status) w.Write([]byte(body)) switch status { @@ -82,6 +112,7 @@ func NewSetUIDEndpoint(cfg config.HostCookie, syncersByBidder map[string]usersyn case http.StatusUnavailableForLegalReasons: metricsEngine.RecordSetUid(metrics.SetUidGDPRHostCookieBlocked) } + so.Errors = []error{errors.New(body)} so.Status = status return } @@ -101,7 +132,7 @@ func NewSetUIDEndpoint(cfg config.HostCookie, syncersByBidder map[string]usersyn } setSiteCookie := siteCookieCheck(r.UserAgent()) - pc.SetCookieOnResponse(w, setSiteCookie, &cfg, cookieTTL) + pc.SetCookieOnResponse(w, setSiteCookie, &cfg.HostCookie, cookieTTL) switch responseFormat { case "i": @@ -185,7 +216,7 @@ func checkChromeBrowserVersion(ua string, index int, chromeStrLength int) bool { return result } -func preventSyncsGDPR(gdprEnabled string, gdprConsent string, perms gdpr.Permissions) (shouldReturn bool, status int, body string) { +func preventSyncsGDPR(gdprEnabled string, gdprConsent string, permsBuilder gdpr.PermissionsBuilder, tcf2Cfg gdpr.TCF2ConfigReader) (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 } @@ -200,7 +231,14 @@ func preventSyncsGDPR(gdprEnabled string, gdprConsent string, perms gdpr.Permiss gdprSignal = gdpr.Signal(i) } - allowed, err := perms.HostCookiesAllowed(context.Background(), gdprSignal, gdprConsent) + gdprRequestInfo := gdpr.RequestInfo{ + Consent: gdprConsent, + GDPRSignal: gdprSignal, + } + + perms := permsBuilder(tcf2Cfg, gdprRequestInfo) + + allowed, err := perms.HostCookiesAllowed(context.Background()) if err != nil { if _, ok := err.(*gdpr.ErrorMalformedConsent); ok { return true, http.StatusBadRequest, "gdpr_consent was invalid. " + err.Error() diff --git a/endpoints/setuid_test.go b/endpoints/setuid_test.go index b27fd20cde3..94e4e09bc1f 100644 --- a/endpoints/setuid_test.go +++ b/endpoints/setuid_test.go @@ -2,6 +2,7 @@ package endpoints import ( "context" + "encoding/json" "errors" "net/http" "net/http/httptest" @@ -10,6 +11,7 @@ import ( "testing" "time" + "github.com/prebid/prebid-server/analytics" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/gdpr" "github.com/prebid/prebid-server/metrics" @@ -232,12 +234,34 @@ func TestSetUIDEndpoint(t *testing.T) { expectedBody: `"f" query param is invalid. must be "b" or "i"`, description: "Set uid for valid bidder with invalid format", }, + { + uri: "/setuid?bidder=pubmatic&uid=123&account=valid_acct", + 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 valid account provided", + }, + { + uri: "/setuid?bidder=pubmatic&uid=123&account=disabled_acct", + syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, + existingSyncs: nil, + gdprAllowsHostCookies: true, + expectedSyncs: nil, + expectedStatusCode: http.StatusBadRequest, + expectedBody: "account is disabled, please reach out to the prebid server host", + description: "Set uid for valid bidder with valid disabled account provided", + }, } + analytics := analyticsConf.NewPBSAnalytics(&config.Analytics{}) metrics := &metricsConf.NilMetricsEngine{} + for _, test := range testCases { - response := doRequest(makeRequest(test.uri, test.existingSyncs), metrics, - test.syncersBidderNameToKey, test.gdprAllowsHostCookies, test.gdprReturnsError, test.gdprMalformed) + response := doRequest(makeRequest(test.uri, test.existingSyncs), analytics, metrics, + test.syncersBidderNameToKey, test.gdprAllowsHostCookies, test.gdprReturnsError, test.gdprMalformed, false) assert.Equal(t, test.expectedStatusCode, response.Code, "Test Case: %s. /setuid returned unexpected error code", test.description) if test.expectedSyncs != nil { @@ -274,8 +298,10 @@ func TestSetUIDEndpointMetrics(t *testing.T) { cookies []*usersync.Cookie syncersBidderNameToKey map[string]string gdprAllowsHostCookies bool + cfgAccountRequired bool expectedResponseCode int expectedMetrics func(*metrics.MetricsEngineMock) + expectedAnalytics func(*MockAnalytics) }{ { description: "Success - Sync", @@ -288,6 +314,16 @@ func TestSetUIDEndpointMetrics(t *testing.T) { m.On("RecordSetUid", metrics.SetUidOK).Once() m.On("RecordSyncerSet", "pubmatic", metrics.SyncerSetUidOK).Once() }, + expectedAnalytics: func(a *MockAnalytics) { + expected := analytics.SetUIDObject{ + Status: 200, + Bidder: "pubmatic", + UID: "123", + Errors: []error{}, + Success: true, + } + a.On("LogSetUIDObject", &expected).Once() + }, }, { description: "Success - Unsync", @@ -300,6 +336,16 @@ func TestSetUIDEndpointMetrics(t *testing.T) { m.On("RecordSetUid", metrics.SetUidOK).Once() m.On("RecordSyncerSet", "pubmatic", metrics.SyncerSetUidCleared).Once() }, + expectedAnalytics: func(a *MockAnalytics) { + expected := analytics.SetUIDObject{ + Status: 200, + Bidder: "pubmatic", + UID: "", + Errors: []error{}, + Success: true, + } + a.On("LogSetUIDObject", &expected).Once() + }, }, { description: "Cookie Opted Out", @@ -311,6 +357,16 @@ func TestSetUIDEndpointMetrics(t *testing.T) { expectedMetrics: func(m *metrics.MetricsEngineMock) { m.On("RecordSetUid", metrics.SetUidOptOut).Once() }, + expectedAnalytics: func(a *MockAnalytics) { + expected := analytics.SetUIDObject{ + Status: 401, + Bidder: "", + UID: "", + Errors: []error{}, + Success: false, + } + a.On("LogSetUIDObject", &expected).Once() + }, }, { description: "Unknown Syncer Key", @@ -322,6 +378,16 @@ func TestSetUIDEndpointMetrics(t *testing.T) { expectedMetrics: func(m *metrics.MetricsEngineMock) { m.On("RecordSetUid", metrics.SetUidSyncerUnknown).Once() }, + expectedAnalytics: func(a *MockAnalytics) { + expected := analytics.SetUIDObject{ + Status: 400, + Bidder: "", + UID: "", + Errors: []error{errors.New("The bidder name provided is not supported by Prebid Server")}, + Success: false, + } + a.On("LogSetUIDObject", &expected).Once() + }, }, { description: "Unknown Format", @@ -333,6 +399,16 @@ func TestSetUIDEndpointMetrics(t *testing.T) { expectedMetrics: func(m *metrics.MetricsEngineMock) { m.On("RecordSetUid", metrics.SetUidBadRequest).Once() }, + expectedAnalytics: func(a *MockAnalytics) { + expected := analytics.SetUIDObject{ + Status: 400, + Bidder: "pubmatic", + UID: "", + Errors: []error{errors.New(`"f" query param is invalid. must be "b" or "i"`)}, + Success: false, + } + a.On("LogSetUIDObject", &expected).Once() + }, }, { description: "Prevented By GDPR - Invalid Consent String", @@ -344,6 +420,16 @@ func TestSetUIDEndpointMetrics(t *testing.T) { expectedMetrics: func(m *metrics.MetricsEngineMock) { m.On("RecordSetUid", metrics.SetUidBadRequest).Once() }, + expectedAnalytics: func(a *MockAnalytics) { + expected := analytics.SetUIDObject{ + Status: 400, + Bidder: "pubmatic", + UID: "", + Errors: []error{errors.New("gdpr_consent is required when gdpr=1")}, + Success: false, + } + a.On("LogSetUIDObject", &expected).Once() + }, }, { description: "Prevented By GDPR - Permission Denied By Consent String", @@ -355,10 +441,110 @@ func TestSetUIDEndpointMetrics(t *testing.T) { expectedMetrics: func(m *metrics.MetricsEngineMock) { m.On("RecordSetUid", metrics.SetUidGDPRHostCookieBlocked).Once() }, + expectedAnalytics: func(a *MockAnalytics) { + expected := analytics.SetUIDObject{ + Status: 451, + Bidder: "pubmatic", + UID: "", + Errors: []error{errors.New("The gdpr_consent string prevents cookies from being saved")}, + Success: false, + } + a.On("LogSetUIDObject", &expected).Once() + }, + }, + { + description: "Blocked account", + uri: "/setuid?bidder=pubmatic&uid=123&account=blocked_acct", + cookies: []*usersync.Cookie{}, + syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, + gdprAllowsHostCookies: true, + expectedResponseCode: 400, + expectedMetrics: func(m *metrics.MetricsEngineMock) { + m.On("RecordSetUid", metrics.SetUidAccountBlocked).Once() + }, + expectedAnalytics: func(a *MockAnalytics) { + expected := analytics.SetUIDObject{ + Status: 400, + Bidder: "pubmatic", + UID: "", + Errors: []error{errCookieSyncAccountBlocked}, + Success: false, + } + a.On("LogSetUIDObject", &expected).Once() + }, + }, + { + description: "Invalid account", + uri: "/setuid?bidder=pubmatic&uid=123&account=unknown", + cookies: []*usersync.Cookie{}, + syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, + gdprAllowsHostCookies: true, + cfgAccountRequired: true, + expectedResponseCode: 400, + expectedMetrics: func(m *metrics.MetricsEngineMock) { + m.On("RecordSetUid", metrics.SetUidAccountInvalid).Once() + }, + expectedAnalytics: func(a *MockAnalytics) { + expected := analytics.SetUIDObject{ + Status: 400, + Bidder: "pubmatic", + UID: "", + Errors: []error{errCookieSyncAccountInvalid}, + Success: false, + } + a.On("LogSetUIDObject", &expected).Once() + }, + }, + { + description: "Malformed account", + uri: "/setuid?bidder=pubmatic&uid=123&account=malformed_acct", + cookies: []*usersync.Cookie{}, + syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, + gdprAllowsHostCookies: true, + cfgAccountRequired: true, + expectedResponseCode: 400, + expectedMetrics: func(m *metrics.MetricsEngineMock) { + m.On("RecordSetUid", metrics.SetUidAccountConfigMalformed).Once() + }, + expectedAnalytics: func(a *MockAnalytics) { + expected := analytics.SetUIDObject{ + Status: 400, + Bidder: "pubmatic", + UID: "", + Errors: []error{errCookieSyncAccountConfigMalformed}, + Success: false, + } + a.On("LogSetUIDObject", &expected).Once() + }, + }, + { + description: "Invalid JSON account", + uri: "/setuid?bidder=pubmatic&uid=123&account=invalid_json_acct", + cookies: []*usersync.Cookie{}, + syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, + gdprAllowsHostCookies: true, + cfgAccountRequired: true, + expectedResponseCode: 400, + expectedMetrics: func(m *metrics.MetricsEngineMock) { + m.On("RecordSetUid", metrics.SetUidBadRequest).Once() + }, + expectedAnalytics: func(a *MockAnalytics) { + expected := analytics.SetUIDObject{ + Status: 400, + Bidder: "pubmatic", + UID: "", + Errors: []error{errors.New("Invalid JSON Patch")}, + Success: false, + } + a.On("LogSetUIDObject", &expected).Once() + }, }, } for _, test := range testCases { + analyticsEngine := &MockAnalytics{} + test.expectedAnalytics(analyticsEngine) + metricsEngine := &metrics.MetricsEngineMock{} test.expectedMetrics(metricsEngine) @@ -366,9 +552,10 @@ func TestSetUIDEndpointMetrics(t *testing.T) { for _, v := range test.cookies { addCookie(req, v) } - response := doRequest(req, metricsEngine, test.syncersBidderNameToKey, test.gdprAllowsHostCookies, false, false) + response := doRequest(req, analyticsEngine, metricsEngine, test.syncersBidderNameToKey, test.gdprAllowsHostCookies, false, false, test.cfgAccountRequired) assert.Equal(t, test.expectedResponseCode, response.Code, test.description) + analyticsEngine.AssertExpectations(t) metricsEngine.AssertExpectations(t) } } @@ -379,8 +566,9 @@ func TestOptedOut(t *testing.T) { cookie.SetOptOut(true) addCookie(request, cookie) syncersBidderNameToKey := map[string]string{"pubmatic": "pubmatic"} + analytics := analyticsConf.NewPBSAnalytics(&config.Analytics{}) metrics := &metricsConf.NilMetricsEngine{} - response := doRequest(request, metrics, syncersBidderNameToKey, true, false, false) + response := doRequest(request, analytics, metrics, syncersBidderNameToKey, true, false, false, false) assert.Equal(t, http.StatusUnauthorized, response.Code) } @@ -516,21 +704,44 @@ func makeRequest(uri string, existingSyncs map[string]string) *http.Request { return request } -func doRequest(req *http.Request, metrics metrics.MetricsEngine, syncersBidderNameToKey map[string]string, gdprAllowsHostCookies, gdprReturnsError, gdprReturnsMalformedError bool) *httptest.ResponseRecorder { - cfg := config.Configuration{} - perms := &mockPermsSetUID{ +func doRequest(req *http.Request, analytics analytics.PBSAnalyticsModule, metrics metrics.MetricsEngine, syncersBidderNameToKey map[string]string, gdprAllowsHostCookies, gdprReturnsError, gdprReturnsMalformedError, cfgAccountRequired bool) *httptest.ResponseRecorder { + cfg := config.Configuration{ + AccountRequired: cfgAccountRequired, + BlacklistedAcctMap: map[string]bool{ + "blocked_acct": true, + }, + } + cfg.MarshalAccountDefaults() + + query := req.URL.Query() + + perms := &fakePermsSetUID{ allowHost: gdprAllowsHostCookies, + consent: query.Get("gdpr_consent"), errorHost: gdprReturnsError, errorMalformed: gdprReturnsMalformedError, personalInfoAllowed: true, } - analytics := analyticsConf.NewPBSAnalytics(&cfg.Analytics) + gdprPermsBuilder := fakePermissionsBuilder{ + permissions: perms, + }.Builder + tcf2ConfigBuilder := fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + }.Builder + syncersByBidder := make(map[string]usersync.Syncer) for bidderName, syncerKey := range syncersBidderNameToKey { syncersByBidder[bidderName] = fakeSyncer{key: syncerKey, defaultSyncType: usersync.SyncTypeIFrame} } - endpoint := NewSetUIDEndpoint(cfg.HostCookie, syncersByBidder, perms, analytics, metrics) + fakeAccountsFetcher := FakeAccountsFetcher{AccountData: map[string]json.RawMessage{ + "valid_acct": json.RawMessage(`{"disabled":false}`), + "disabled_acct": json.RawMessage(`{"disabled":true}`), + "malformed_acct": json.RawMessage(`{"disabled":"malformed"}`), + "invalid_json_acct": json.RawMessage(`{"}`), + }} + + endpoint := NewSetUIDEndpoint(&cfg, syncersByBidder, gdprPermsBuilder, tcf2ConfigBuilder, analytics, fakeAccountsFetcher, metrics) response := httptest.NewRecorder() endpoint(response, req, nil) return response @@ -552,16 +763,33 @@ func parseCookieString(t *testing.T, response *httptest.ResponseRecorder) *users return usersync.ParseCookie(&httpCookie) } -type mockPermsSetUID struct { +type fakePermissionsBuilder struct { + permissions gdpr.Permissions +} + +func (fpb fakePermissionsBuilder) Builder(gdpr.TCF2ConfigReader, gdpr.RequestInfo) gdpr.Permissions { + return fpb.permissions +} + +type fakeTCF2ConfigBuilder struct { + cfg gdpr.TCF2ConfigReader +} + +func (fcr fakeTCF2ConfigBuilder) Builder(hostConfig config.TCF2, accountConfig config.AccountGDPR) gdpr.TCF2ConfigReader { + return fcr.cfg +} + +type fakePermsSetUID struct { allowHost bool + consent string errorHost bool errorMalformed bool personalInfoAllowed bool } -func (g *mockPermsSetUID) HostCookiesAllowed(ctx context.Context, gdprSignal gdpr.Signal, consent string) (bool, error) { +func (g *fakePermsSetUID) HostCookiesAllowed(ctx context.Context) (bool, error) { if g.errorMalformed { - return g.allowHost, &gdpr.ErrorMalformedConsent{Consent: consent, Cause: errors.New("some error")} + return g.allowHost, &gdpr.ErrorMalformedConsent{Consent: g.consent, Cause: errors.New("some error")} } if g.errorHost { return g.allowHost, errors.New("something went wrong") @@ -569,11 +797,11 @@ func (g *mockPermsSetUID) HostCookiesAllowed(ctx context.Context, gdprSignal gdp return g.allowHost, nil } -func (g *mockPermsSetUID) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, gdprSignal gdpr.Signal, consent string) (bool, error) { +func (g *fakePermsSetUID) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName) (bool, error) { return false, nil } -func (g *mockPermsSetUID) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string, aliasGVLIDs map[string]uint16) (permissions gdpr.AuctionPermissions, err error) { +func (g *fakePermsSetUID) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions gdpr.AuctionPermissions, err error) { return gdpr.AuctionPermissions{ AllowBidRequest: g.personalInfoAllowed, PassGeo: g.personalInfoAllowed, diff --git a/endpoints/version_test.go b/endpoints/version_test.go index 5629bea2ad9..e83a5a2629e 100644 --- a/endpoints/version_test.go +++ b/endpoints/version_test.go @@ -1,7 +1,7 @@ package endpoints import ( - "io/ioutil" + "io" "net/http/httptest" "testing" @@ -47,7 +47,7 @@ func TestVersion(t *testing.T) { handler(w, nil) - response, err := ioutil.ReadAll(w.Result().Body) + response, err := io.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/errortypes/code.go b/errortypes/code.go index 869e7d541a4..d6e081d2bfc 100644 --- a/errortypes/code.go +++ b/errortypes/code.go @@ -12,6 +12,8 @@ const ( BlacklistedAcctErrorCode AcctRequiredErrorCode NoConversionRateErrorCode + MalformedAcctErrorCode + ModuleRejectionErrorCode ) // Defines numeric codes for well-known warnings. @@ -21,6 +23,7 @@ const ( AccountLevelDebugDisabledWarningCode BidderLevelDebugDisabledWarningCode DisabledCurrencyConversionWarningCode + AlternateBidderCodeWarningCode ) // Coder provides an error or warning code with severity. diff --git a/errortypes/errortypes.go b/errortypes/errortypes.go index 1fed2d7da6e..d93075b7c6c 100644 --- a/errortypes/errortypes.go +++ b/errortypes/errortypes.go @@ -165,6 +165,24 @@ func (err *BidderTemporarilyDisabled) Severity() Severity { return SeverityWarning } +// MalformedAcct should be used when the retrieved account config cannot be unmarshaled +// These errors will be written to http.ResponseWriter before canceling execution +type MalformedAcct struct { + Message string +} + +func (err *MalformedAcct) Error() string { + return err.Message +} + +func (err *MalformedAcct) Code() int { + return MalformedAcctErrorCode +} + +func (err *MalformedAcct) Severity() Severity { + return SeverityFatal +} + // Warning is a generic non-fatal error. type Warning struct { Message string diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 348747fadbd..3669e0cac7c 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -20,8 +20,10 @@ import ( "github.com/prebid/prebid-server/adapters/adot" "github.com/prebid/prebid-server/adapters/adpone" "github.com/prebid/prebid-server/adapters/adprime" + "github.com/prebid/prebid-server/adapters/adrino" "github.com/prebid/prebid-server/adapters/adtarget" "github.com/prebid/prebid-server/adapters/adtelligent" + "github.com/prebid/prebid-server/adapters/adtrgtme" "github.com/prebid/prebid-server/adapters/advangelists" "github.com/prebid/prebid-server/adapters/adview" "github.com/prebid/prebid-server/adapters/adxcg" @@ -32,19 +34,26 @@ import ( "github.com/prebid/prebid-server/adapters/apacdex" "github.com/prebid/prebid-server/adapters/applogy" "github.com/prebid/prebid-server/adapters/appnexus" + "github.com/prebid/prebid-server/adapters/appush" "github.com/prebid/prebid-server/adapters/audienceNetwork" + "github.com/prebid/prebid-server/adapters/automatad" "github.com/prebid/prebid-server/adapters/avocet" "github.com/prebid/prebid-server/adapters/axonix" "github.com/prebid/prebid-server/adapters/beachfront" "github.com/prebid/prebid-server/adapters/beintoo" "github.com/prebid/prebid-server/adapters/between" + "github.com/prebid/prebid-server/adapters/beyondmedia" "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/bidstack" "github.com/prebid/prebid-server/adapters/bizzclick" "github.com/prebid/prebid-server/adapters/bliink" + "github.com/prebid/prebid-server/adapters/blue" "github.com/prebid/prebid-server/adapters/bmtm" + "github.com/prebid/prebid-server/adapters/boldwin" "github.com/prebid/prebid-server/adapters/brightroll" + "github.com/prebid/prebid-server/adapters/ccx" "github.com/prebid/prebid-server/adapters/coinzilla" "github.com/prebid/prebid-server/adapters/colossus" "github.com/prebid/prebid-server/adapters/compass" @@ -56,12 +65,14 @@ import ( "github.com/prebid/prebid-server/adapters/datablocks" "github.com/prebid/prebid-server/adapters/decenterads" "github.com/prebid/prebid-server/adapters/deepintent" + "github.com/prebid/prebid-server/adapters/dianomi" "github.com/prebid/prebid-server/adapters/dmx" 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" "github.com/prebid/prebid-server/adapters/epom" + "github.com/prebid/prebid-server/adapters/freewheelssp" "github.com/prebid/prebid-server/adapters/gamma" "github.com/prebid/prebid-server/adapters/gamoshi" "github.com/prebid/prebid-server/adapters/grid" @@ -69,12 +80,14 @@ import ( "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/infytv" "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/kargo" "github.com/prebid/prebid-server/adapters/kayzen" "github.com/prebid/prebid-server/adapters/kidoz" "github.com/prebid/prebid-server/adapters/krushmedia" @@ -109,6 +122,7 @@ import ( "github.com/prebid/prebid-server/adapters/rtbhouse" "github.com/prebid/prebid-server/adapters/rubicon" salunamedia "github.com/prebid/prebid-server/adapters/sa_lunamedia" + "github.com/prebid/prebid-server/adapters/seedingAlliance" "github.com/prebid/prebid-server/adapters/sharethrough" "github.com/prebid/prebid-server/adapters/silvermob" "github.com/prebid/prebid-server/adapters/smaato" @@ -119,7 +133,11 @@ import ( "github.com/prebid/prebid-server/adapters/smilewanted" "github.com/prebid/prebid-server/adapters/sonobi" "github.com/prebid/prebid-server/adapters/sovrn" + "github.com/prebid/prebid-server/adapters/sspBC" + "github.com/prebid/prebid-server/adapters/stroeerCore" + "github.com/prebid/prebid-server/adapters/suntContent" "github.com/prebid/prebid-server/adapters/synacormedia" + "github.com/prebid/prebid-server/adapters/taboola" "github.com/prebid/prebid-server/adapters/tappx" "github.com/prebid/prebid-server/adapters/telaria" "github.com/prebid/prebid-server/adapters/trafficgate" @@ -161,10 +179,12 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderAdnuntius: adnuntius.Builder, openrtb_ext.BidderAdOcean: adocean.Builder, openrtb_ext.BidderAdoppler: adoppler.Builder, - openrtb_ext.BidderAdpone: adpone.Builder, openrtb_ext.BidderAdot: adot.Builder, + openrtb_ext.BidderAdpone: adpone.Builder, openrtb_ext.BidderAdprime: adprime.Builder, + openrtb_ext.BidderAdrino: adrino.Builder, openrtb_ext.BidderAdtarget: adtarget.Builder, + openrtb_ext.BidderAdtrgtme: adtrgtme.Builder, openrtb_ext.BidderAdtelligent: adtelligent.Builder, openrtb_ext.BidderAdvangelists: advangelists.Builder, openrtb_ext.BidderAdView: adview.Builder, @@ -176,19 +196,26 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderApacdex: apacdex.Builder, openrtb_ext.BidderApplogy: applogy.Builder, openrtb_ext.BidderAppnexus: appnexus.Builder, + openrtb_ext.BidderAppush: appush.Builder, openrtb_ext.BidderAudienceNetwork: audienceNetwork.Builder, + openrtb_ext.BidderAutomatad: automatad.Builder, openrtb_ext.BidderAvocet: avocet.Builder, openrtb_ext.BidderAxonix: axonix.Builder, openrtb_ext.BidderBeachfront: beachfront.Builder, openrtb_ext.BidderBeintoo: beintoo.Builder, openrtb_ext.BidderBetween: between.Builder, + openrtb_ext.BidderBeyondMedia: beyondmedia.Builder, openrtb_ext.BidderBidmachine: bidmachine.Builder, openrtb_ext.BidderBidmyadz: bidmyadz.Builder, openrtb_ext.BidderBidsCube: bidscube.Builder, + openrtb_ext.BidderBidstack: bidstack.Builder, openrtb_ext.BidderBizzclick: bizzclick.Builder, openrtb_ext.BidderBliink: bliink.Builder, + openrtb_ext.BidderBlue: blue.Builder, openrtb_ext.BidderBmtm: bmtm.Builder, + openrtb_ext.BidderBoldwin: boldwin.Builder, openrtb_ext.BidderBrightroll: brightroll.Builder, + openrtb_ext.BidderCcx: ccx.Builder, openrtb_ext.BidderCoinzilla: coinzilla.Builder, openrtb_ext.BidderColossus: colossus.Builder, openrtb_ext.BidderCompass: compass.Builder, @@ -200,6 +227,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderDatablocks: datablocks.Builder, openrtb_ext.BidderDecenterAds: decenterads.Builder, openrtb_ext.BidderDeepintent: deepintent.Builder, + openrtb_ext.BidderDianomi: dianomi.Builder, openrtb_ext.BidderDmx: dmx.Builder, openrtb_ext.BidderEmxDigital: emx_digital.Builder, openrtb_ext.BidderEngageBDR: engagebdr.Builder, @@ -207,6 +235,8 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderEPlanning: eplanning.Builder, openrtb_ext.BidderEpom: epom.Builder, openrtb_ext.BidderEVolution: evolution.Builder, + openrtb_ext.BidderFreewheelSSP: freewheelssp.Builder, + openrtb_ext.BidderFreewheelSSPOld: freewheelssp.Builder, openrtb_ext.BidderGamma: gamma.Builder, openrtb_ext.BidderGamoshi: gamoshi.Builder, openrtb_ext.BidderGrid: grid.Builder, @@ -216,6 +246,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderHuaweiAds: huaweiads.Builder, openrtb_ext.BidderImpactify: impactify.Builder, openrtb_ext.BidderImprovedigital: improvedigital.Builder, + openrtb_ext.BidderInfyTV: infytv.Builder, openrtb_ext.BidderInMobi: inmobi.Builder, openrtb_ext.BidderInteractiveoffers: interactiveoffers.Builder, openrtb_ext.BidderInvibes: invibes.Builder, @@ -223,6 +254,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderIx: ix.Builder, openrtb_ext.BidderJANet: adtelligent.Builder, openrtb_ext.BidderJixie: jixie.Builder, + openrtb_ext.BidderKargo: kargo.Builder, openrtb_ext.BidderKayzen: kayzen.Builder, openrtb_ext.BidderKidoz: kidoz.Builder, openrtb_ext.BidderKrushmedia: krushmedia.Builder, @@ -230,10 +262,9 @@ 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, + openrtb_ext.BidderMediafuse: appnexus.Builder, openrtb_ext.BidderMedianet: medianet.Builder, openrtb_ext.BidderMgid: mgid.Builder, openrtb_ext.BidderMobfoxpb: mobfoxpb.Builder, @@ -242,6 +273,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderNextMillennium: nextmillennium.Builder, openrtb_ext.BidderNinthDecimal: ninthdecimal.Builder, openrtb_ext.BidderNoBid: nobid.Builder, + openrtb_ext.BidderOFTMedia: adtelligent.Builder, openrtb_ext.BidderOneTag: onetag.Builder, openrtb_ext.BidderOpenWeb: openweb.Builder, openrtb_ext.BidderOpenx: openx.Builder, @@ -262,6 +294,8 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderRichaudience: richaudience.Builder, openrtb_ext.BidderRTBHouse: rtbhouse.Builder, openrtb_ext.BidderRubicon: rubicon.Builder, + openrtb_ext.BidderSeedingAlliance: seedingAlliance.Builder, + openrtb_ext.BidderSaLunaMedia: salunamedia.Builder, openrtb_ext.BidderSharethrough: sharethrough.Builder, openrtb_ext.BidderSilverMob: silvermob.Builder, openrtb_ext.BidderSmaato: smaato.Builder, @@ -272,8 +306,12 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderSmileWanted: smilewanted.Builder, openrtb_ext.BidderSonobi: sonobi.Builder, openrtb_ext.BidderSovrn: sovrn.Builder, + openrtb_ext.BidderSspBC: sspBC.Builder, openrtb_ext.BidderStreamkey: adtelligent.Builder, + openrtb_ext.BidderSuntContent: suntContent.Builder, + openrtb_ext.BidderStroeerCore: stroeerCore.Builder, openrtb_ext.BidderSynacormedia: synacormedia.Builder, + openrtb_ext.BidderTaboola: taboola.Builder, openrtb_ext.BidderTappx: tappx.Builder, openrtb_ext.BidderTelaria: telaria.Builder, openrtb_ext.BidderTrafficGate: trafficgate.Builder, @@ -295,7 +333,6 @@ 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: yahoossp.Builder, openrtb_ext.BidderZeroClickFraud: zeroclickfraud.Builder, } } diff --git a/exchange/adapter_util.go b/exchange/adapter_util.go index c3d5f0ecf4b..544fe936c7c 100644 --- a/exchange/adapter_util.go +++ b/exchange/adapter_util.go @@ -11,7 +11,9 @@ import ( ) func BuildAdapters(client *http.Client, cfg *config.Configuration, infos config.BidderInfos, me metrics.MetricsEngine) (map[openrtb_ext.BidderName]AdaptedBidder, []error) { - bidders, errs := buildBidders(cfg.Adapters, infos, newAdapterBuilders()) + server := config.Server{ExternalUrl: cfg.ExternalURL, GvlID: cfg.GDPR.HostVendorID, DataCenter: cfg.DataCenter} + bidders, errs := buildBidders(infos, newAdapterBuilders(), server) + if len(errs) > 0 { return nil, errs } @@ -19,38 +21,34 @@ func BuildAdapters(client *http.Client, cfg *config.Configuration, infos config. exchangeBidders := make(map[openrtb_ext.BidderName]AdaptedBidder, len(bidders)) for bidderName, bidder := range bidders { info := infos[string(bidderName)] - exchangeBidder := AdaptBidder(bidder, client, cfg, me, bidderName, info.Debug) + exchangeBidder := AdaptBidder(bidder, client, cfg, me, bidderName, info.Debug, info.EndpointCompression) exchangeBidder = addValidatedBidderMiddleware(exchangeBidder) exchangeBidders[bidderName] = exchangeBidder } return exchangeBidders, nil } -func buildBidders(adapterConfig map[string]config.Adapter, infos config.BidderInfos, builders map[openrtb_ext.BidderName]adapters.Builder) (map[openrtb_ext.BidderName]adapters.Bidder, []error) { +func buildBidders(infos config.BidderInfos, builders map[openrtb_ext.BidderName]adapters.Builder, server config.Server) (map[openrtb_ext.BidderName]adapters.Bidder, []error) { bidders := make(map[openrtb_ext.BidderName]adapters.Bidder) var errs []error - for bidder, cfg := range adapterConfig { + for bidder, info := range infos { bidderName, bidderNameFound := openrtb_ext.NormalizeBidderName(bidder) if !bidderNameFound { errs = append(errs, fmt.Errorf("%v: unknown bidder", bidder)) continue } - info, infoFound := infos[string(bidderName)] - if !infoFound { - errs = append(errs, fmt.Errorf("%v: bidder info not found", bidder)) - continue - } - builder, builderFound := builders[bidderName] if !builderFound { errs = append(errs, fmt.Errorf("%v: builder not registered", bidder)) continue } - if info.Enabled { - bidderInstance, builderErr := builder(bidderName, cfg) + if info.IsEnabled() { + adapterInfo := buildAdapterInfo(info) + bidderInstance, builderErr := builder(bidderName, adapterInfo, server) + if builderErr != nil { errs = append(errs, fmt.Errorf("%v: %v", bidder, builderErr)) continue @@ -58,16 +56,25 @@ func buildBidders(adapterConfig map[string]config.Adapter, infos config.BidderIn bidders[bidderName] = adapters.BuildInfoAwareBidder(bidderInstance, info) } } - return bidders, errs } +func buildAdapterInfo(bidderInfo config.BidderInfo) config.Adapter { + adapter := config.Adapter{} + adapter.Endpoint = bidderInfo.Endpoint + adapter.ExtraAdapterInfo = bidderInfo.ExtraAdapterInfo + adapter.PlatformID = bidderInfo.PlatformID + adapter.AppSecret = bidderInfo.AppSecret + adapter.XAPI = bidderInfo.XAPI + return adapter +} + // GetActiveBidders returns a map of all active bidder names. func GetActiveBidders(infos config.BidderInfos) map[string]openrtb_ext.BidderName { activeBidders := make(map[string]openrtb_ext.BidderName) for name, info := range infos { - if info.Enabled { + if info.IsEnabled() { activeBidders[name] = openrtb_ext.BidderName(name) } } @@ -78,13 +85,15 @@ func GetActiveBidders(infos config.BidderInfos) map[string]openrtb_ext.BidderNam // GetDisabledBiddersErrorMessages returns a map of error messages for disabled bidders. func GetDisabledBiddersErrorMessages(infos config.BidderInfos) map[string]string { disabledBidders := map[string]string{ - "lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`, - "adagio": `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`, - "somoaudience": `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`, + "lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`, + "adagio": `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`, + "somoaudience": `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`, + "yssp": `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahoossp" in your configuration.`, + "andbeyondmedia": `Bidder "andbeyondmedia" is no longer available in Prebid Server. If you're looking to use the AndBeyond.Media SSP adapter, please rename it to "beyondmedia" in your configuration.`, } for name, info := range infos { - if !info.Enabled { + if info.Disabled { msg := fmt.Sprintf(`Bidder "%s" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`, name) disabledBidders[name] = msg } diff --git a/exchange/adapter_util_test.go b/exchange/adapter_util_test.go index 2423b019ad3..d98b1e11f02 100644 --- a/exchange/adapter_util_test.go +++ b/exchange/adapter_util_test.go @@ -5,7 +5,7 @@ import ( "net/http" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/appnexus" "github.com/prebid/prebid-server/adapters/rubicon" @@ -16,67 +16,61 @@ import ( ) var ( - infoEnabled = config.BidderInfo{Enabled: true} - infoDisabled = config.BidderInfo{Enabled: false} + infoEnabled = config.BidderInfo{Disabled: false} + infoDisabled = config.BidderInfo{Disabled: true} ) func TestBuildAdapters(t *testing.T) { client := &http.Client{} metricEngine := &metrics.NilMetricsEngine{} - appnexusBidder, _ := appnexus.Builder(openrtb_ext.BidderAppnexus, config.Adapter{}) + appnexusBidder, _ := appnexus.Builder(openrtb_ext.BidderAppnexus, config.Adapter{}, config.Server{}) appnexusBidderWithInfo := adapters.BuildInfoAwareBidder(appnexusBidder, infoEnabled) - appnexusBidderAdapted := AdaptBidder(appnexusBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderAppnexus, nil) + appnexusBidderAdapted := AdaptBidder(appnexusBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderAppnexus, nil, "") appnexusValidated := addValidatedBidderMiddleware(appnexusBidderAdapted) - rubiconBidder, _ := rubicon.Builder(openrtb_ext.BidderRubicon, config.Adapter{}) + rubiconBidder, _ := rubicon.Builder(openrtb_ext.BidderRubicon, config.Adapter{}, config.Server{}) rubiconBidderWithInfo := adapters.BuildInfoAwareBidder(rubiconBidder, infoEnabled) - rubiconBidderAdapted := AdaptBidder(rubiconBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderRubicon, nil) - rubiconbidderValidated := addValidatedBidderMiddleware(rubiconBidderAdapted) + rubiconBidderAdapted := AdaptBidder(rubiconBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderRubicon, nil, "") + rubiconBidderValidated := addValidatedBidderMiddleware(rubiconBidderAdapted) testCases := []struct { description string - adapterConfig map[string]config.Adapter bidderInfos map[string]config.BidderInfo expectedBidders map[openrtb_ext.BidderName]AdaptedBidder expectedErrors []error }{ { description: "No Bidders", - adapterConfig: map[string]config.Adapter{}, bidderInfos: map[string]config.BidderInfo{}, expectedBidders: map[openrtb_ext.BidderName]AdaptedBidder{}, }, { - description: "One Bidder", - adapterConfig: map[string]config.Adapter{"appnexus": {}}, - bidderInfos: map[string]config.BidderInfo{"appnexus": infoEnabled}, + description: "One Bidder", + bidderInfos: map[string]config.BidderInfo{"appnexus": infoEnabled}, expectedBidders: map[openrtb_ext.BidderName]AdaptedBidder{ openrtb_ext.BidderAppnexus: appnexusValidated, }, }, { - description: "Many Bidders", - adapterConfig: map[string]config.Adapter{"appnexus": {}, "rubicon": {}}, - bidderInfos: map[string]config.BidderInfo{"appnexus": infoEnabled, "rubicon": infoEnabled}, + description: "Many Bidders", + bidderInfos: map[string]config.BidderInfo{"appnexus": infoEnabled, "rubicon": infoEnabled}, expectedBidders: map[openrtb_ext.BidderName]AdaptedBidder{ openrtb_ext.BidderAppnexus: appnexusValidated, - openrtb_ext.BidderRubicon: rubiconbidderValidated, + openrtb_ext.BidderRubicon: rubiconBidderValidated, }, }, { - description: "Invalid - Builder Errors", - adapterConfig: map[string]config.Adapter{"appnexus": {}, "unknown": {}}, - bidderInfos: map[string]config.BidderInfo{}, + description: "Invalid - Builder Errors", + bidderInfos: map[string]config.BidderInfo{"unknown": {}, "appNexus": {}}, expectedErrors: []error{ - errors.New("appnexus: bidder info not found"), errors.New("unknown: unknown bidder"), }, }, } + cfg := &config.Configuration{} for _, test := range testCases { - cfg := &config.Configuration{Adapters: test.adapterConfig} bidders, errs := BuildAdapters(client, cfg, test.bidderInfos, metricEngine) assert.Equal(t, test.expectedBidders, bidders, test.description+":bidders") assert.ElementsMatch(t, test.expectedErrors, errs, test.description+":errors") @@ -91,97 +85,73 @@ func TestBuildBidders(t *testing.T) { rubiconBidder := fakeBidder{"b"} rubiconBuilder := fakeBuilder{rubiconBidder, nil}.Builder + server := config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"} + testCases := []struct { description string - adapterConfig map[string]config.Adapter bidderInfos map[string]config.BidderInfo builders map[openrtb_ext.BidderName]adapters.Builder expectedBidders map[openrtb_ext.BidderName]adapters.Bidder expectedErrors []error }{ { - description: "Invalid - Unknown Bidder", - adapterConfig: map[string]config.Adapter{"unknown": {}}, - bidderInfos: map[string]config.BidderInfo{"unknown": infoEnabled}, - builders: map[openrtb_ext.BidderName]adapters.Builder{openrtb_ext.BidderAppnexus: appnexusBuilder}, + description: "Invalid - Unknown Bidder", + bidderInfos: map[string]config.BidderInfo{"unknown": infoEnabled}, + builders: map[openrtb_ext.BidderName]adapters.Builder{openrtb_ext.BidderAppnexus: appnexusBuilder}, expectedErrors: []error{ errors.New("unknown: unknown bidder"), }, }, { - description: "Invalid - No Bidder Info", - adapterConfig: map[string]config.Adapter{"appnexus": {}}, - bidderInfos: map[string]config.BidderInfo{}, - builders: map[openrtb_ext.BidderName]adapters.Builder{openrtb_ext.BidderAppnexus: appnexusBuilder}, - expectedErrors: []error{ - errors.New("appnexus: bidder info not found"), - }, - }, - { - description: "Invalid - No Builder", - adapterConfig: map[string]config.Adapter{"appnexus": {}}, - bidderInfos: map[string]config.BidderInfo{"appnexus": infoEnabled}, - builders: map[openrtb_ext.BidderName]adapters.Builder{}, + description: "Invalid - No Builder", + bidderInfos: map[string]config.BidderInfo{"appnexus": infoEnabled}, + builders: map[openrtb_ext.BidderName]adapters.Builder{}, expectedErrors: []error{ errors.New("appnexus: builder not registered"), }, }, { - description: "Success - Builder Error", - adapterConfig: map[string]config.Adapter{"appnexus": {}}, - bidderInfos: map[string]config.BidderInfo{"appnexus": infoEnabled}, - builders: map[openrtb_ext.BidderName]adapters.Builder{openrtb_ext.BidderAppnexus: appnexusBuilderWithError}, + description: "Success - Builder Error", + bidderInfos: map[string]config.BidderInfo{"appnexus": infoEnabled}, + builders: map[openrtb_ext.BidderName]adapters.Builder{openrtb_ext.BidderAppnexus: appnexusBuilderWithError}, expectedErrors: []error{ errors.New("appnexus: anyError"), }, }, { - description: "Success - None", - adapterConfig: map[string]config.Adapter{}, - bidderInfos: map[string]config.BidderInfo{}, - builders: map[openrtb_ext.BidderName]adapters.Builder{}, + description: "Success - None", + bidderInfos: map[string]config.BidderInfo{}, + builders: map[openrtb_ext.BidderName]adapters.Builder{}, }, { - description: "Success - One", - adapterConfig: map[string]config.Adapter{"appnexus": {}}, - bidderInfos: map[string]config.BidderInfo{"appnexus": infoEnabled}, - builders: map[openrtb_ext.BidderName]adapters.Builder{openrtb_ext.BidderAppnexus: appnexusBuilder}, + description: "Success - One", + bidderInfos: map[string]config.BidderInfo{"appnexus": infoEnabled}, + builders: map[openrtb_ext.BidderName]adapters.Builder{openrtb_ext.BidderAppnexus: appnexusBuilder}, expectedBidders: map[openrtb_ext.BidderName]adapters.Bidder{ openrtb_ext.BidderAppnexus: adapters.BuildInfoAwareBidder(appnexusBidder, infoEnabled), }, }, { - description: "Success - Many", - adapterConfig: map[string]config.Adapter{"appnexus": {}, "rubicon": {}}, - bidderInfos: map[string]config.BidderInfo{"appnexus": infoEnabled, "rubicon": infoEnabled}, - builders: map[openrtb_ext.BidderName]adapters.Builder{openrtb_ext.BidderAppnexus: appnexusBuilder, openrtb_ext.BidderRubicon: rubiconBuilder}, + description: "Success - Many", + bidderInfos: map[string]config.BidderInfo{"appnexus": infoEnabled, "rubicon": infoEnabled}, + builders: map[openrtb_ext.BidderName]adapters.Builder{openrtb_ext.BidderAppnexus: appnexusBuilder, openrtb_ext.BidderRubicon: rubiconBuilder}, expectedBidders: map[openrtb_ext.BidderName]adapters.Bidder{ openrtb_ext.BidderAppnexus: adapters.BuildInfoAwareBidder(appnexusBidder, infoEnabled), openrtb_ext.BidderRubicon: adapters.BuildInfoAwareBidder(rubiconBidder, infoEnabled), }, }, { - description: "Success - Ignores Disabled", - adapterConfig: map[string]config.Adapter{"appnexus": {}, "rubicon": {}}, - bidderInfos: map[string]config.BidderInfo{"appnexus": infoDisabled, "rubicon": infoEnabled}, - builders: map[openrtb_ext.BidderName]adapters.Builder{openrtb_ext.BidderAppnexus: appnexusBuilder, openrtb_ext.BidderRubicon: rubiconBuilder}, + description: "Success - Ignores Disabled", + bidderInfos: map[string]config.BidderInfo{"appnexus": infoDisabled, "rubicon": infoEnabled}, + builders: map[openrtb_ext.BidderName]adapters.Builder{openrtb_ext.BidderAppnexus: appnexusBuilder, openrtb_ext.BidderRubicon: rubiconBuilder}, expectedBidders: map[openrtb_ext.BidderName]adapters.Bidder{ openrtb_ext.BidderRubicon: adapters.BuildInfoAwareBidder(rubiconBidder, infoEnabled), }, }, - { - description: "Success - Ignores Adapter Config Case", - adapterConfig: map[string]config.Adapter{"AppNexus": {}}, - bidderInfos: map[string]config.BidderInfo{"appnexus": infoEnabled}, - builders: map[openrtb_ext.BidderName]adapters.Builder{openrtb_ext.BidderAppnexus: appnexusBuilder}, - expectedBidders: map[openrtb_ext.BidderName]adapters.Bidder{ - openrtb_ext.BidderAppnexus: adapters.BuildInfoAwareBidder(appnexusBidder, infoEnabled), - }, - }, } for _, test := range testCases { - bidders, errs := buildBidders(test.adapterConfig, test.bidderInfos, test.builders) + bidders, errs := buildBidders(test.bidderInfos, test.builders, server) // For Test Setup Convenience if test.expectedBidders == nil { @@ -237,38 +207,46 @@ func TestGetDisabledBiddersErrorMessages(t *testing.T) { description: "None", bidderInfos: map[string]config.BidderInfo{}, expected: map[string]string{ - "lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`, - "adagio": `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`, - "somoaudience": `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`, + "lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`, + "adagio": `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`, + "somoaudience": `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`, + "yssp": `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahoossp" in your configuration.`, + "andbeyondmedia": `Bidder "andbeyondmedia" is no longer available in Prebid Server. If you're looking to use the AndBeyond.Media SSP adapter, please rename it to "beyondmedia" in your configuration.`, }, }, { description: "Enabled", bidderInfos: map[string]config.BidderInfo{"appnexus": infoEnabled}, expected: map[string]string{ - "lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`, - "adagio": `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`, - "somoaudience": `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`, + "lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`, + "adagio": `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`, + "somoaudience": `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`, + "yssp": `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahoossp" in your configuration.`, + "andbeyondmedia": `Bidder "andbeyondmedia" is no longer available in Prebid Server. If you're looking to use the AndBeyond.Media SSP adapter, please rename it to "beyondmedia" in your configuration.`, }, }, { description: "Disabled", bidderInfos: map[string]config.BidderInfo{"appnexus": infoDisabled}, expected: map[string]string{ - "lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`, - "adagio": `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`, - "somoaudience": `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`, - "appnexus": `Bidder "appnexus" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`, + "lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`, + "adagio": `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`, + "somoaudience": `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`, + "yssp": `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahoossp" in your configuration.`, + "appnexus": `Bidder "appnexus" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`, + "andbeyondmedia": `Bidder "andbeyondmedia" is no longer available in Prebid Server. If you're looking to use the AndBeyond.Media SSP adapter, please rename it to "beyondmedia" in your configuration.`, }, }, { description: "Mixed", bidderInfos: map[string]config.BidderInfo{"appnexus": infoDisabled, "openx": infoEnabled}, expected: map[string]string{ - "lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`, - "adagio": `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`, - "somoaudience": `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`, - "appnexus": `Bidder "appnexus" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`, + "lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`, + "adagio": `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`, + "somoaudience": `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`, + "yssp": `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahoossp" in your configuration.`, + "appnexus": `Bidder "appnexus" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`, + "andbeyondmedia": `Bidder "andbeyondmedia" is no longer available in Prebid Server. If you're looking to use the AndBeyond.Media SSP adapter, please rename it to "beyondmedia" in your configuration.`, }, }, } @@ -296,6 +274,6 @@ type fakeBuilder struct { err error } -func (b fakeBuilder) Builder(name openrtb_ext.BidderName, cfg config.Adapter) (adapters.Bidder, error) { +func (b fakeBuilder) Builder(name openrtb_ext.BidderName, cfg config.Adapter, server config.Server) (adapters.Bidder, error) { return b.bidder, b.err } diff --git a/exchange/auction.go b/exchange/auction.go index c8aff684e41..32a89cb5e5a 100644 --- a/exchange/auction.go +++ b/exchange/auction.go @@ -11,7 +11,7 @@ import ( "time" uuid "github.com/gofrs/uuid" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/prebid_cache_client" diff --git a/exchange/auction_test.go b/exchange/auction_test.go index 455ae5018e8..8f06491442f 100644 --- a/exchange/auction_test.go +++ b/exchange/auction_test.go @@ -5,13 +5,13 @@ import ( "encoding/json" "encoding/xml" "fmt" - "io/ioutil" + "os" "path/filepath" "regexp" "strconv" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/prebid_cache_client" @@ -101,7 +101,7 @@ func TestBuildCacheString(t *testing.T) { // customcachekey.json test here verifies custom cache key not used for non-vast video func TestCacheJSON(t *testing.T) { for _, dir := range []string{"cachetest", "customcachekeytest", "impcustomcachekeytest", "eventscachetest"} { - if specFiles, err := ioutil.ReadDir(dir); err == nil { + if specFiles, err := os.ReadDir(dir); err == nil { for _, specFile := range specFiles { fileName := filepath.Join(dir, specFile.Name()) fileDisplayName := "exchange/" + fileName @@ -170,7 +170,7 @@ func TestIsDebugOverrideEnabled(t *testing.T) { // 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) + specData, err := os.ReadFile(filename) if err != nil { return nil, fmt.Errorf("Failed to read file %s: %v", filename, err) } diff --git a/exchange/bidder.go b/exchange/bidder.go index 74a239981b7..a565606beb0 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -2,25 +2,28 @@ package exchange import ( "bytes" + "compress/gzip" "context" "crypto/tls" "encoding/json" "errors" "fmt" - "io/ioutil" + "io" "net/http" "net/http/httptrace" "regexp" + "strings" "time" "github.com/golang/glog" "github.com/prebid/prebid-server/config/util" "github.com/prebid/prebid-server/currency" + "github.com/prebid/prebid-server/experiment/adscert" "github.com/prebid/prebid-server/version" - nativeRequests "github.com/mxmCherry/openrtb/v15/native1/request" - nativeResponse "github.com/mxmCherry/openrtb/v15/native1/response" - "github.com/mxmCherry/openrtb/v15/openrtb2" + nativeRequests "github.com/prebid/openrtb/v17/native1/request" + nativeResponse "github.com/prebid/openrtb/v17/native1/response" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -51,7 +54,15 @@ 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, bidderRequest BidderRequest, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error) + requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes) ([]*pbsOrtbSeatBid, []error) +} + +// bidRequestOptions holds additional options for bid request execution to maintain clean code and reasonable number of parameters +type bidRequestOptions struct { + accountDebugAllowed bool + headerDebugAllowed bool + addCallSignHeader bool + bidAdjustments map[string]float64 } const ImpIdReqBody = "Stored bid response for impression id: " @@ -93,22 +104,30 @@ type pbsOrtbSeatBid struct { // httpCalls is the list of debugging info. It should only be populated if the request.test == 1. // This will become response.ext.debug.httpcalls.{bidder} on the final Response. httpCalls []*openrtb_ext.ExtHttpCall + // seat defines whom these extra bids belong to. + seat string } +// Possible values of compression types Prebid Server can support for bidder compression +const ( + Gzip string = "GZIP" +) + // AdaptBidder converts an adapters.Bidder into an exchange.AdaptedBidder. // // The name refers to the "Adapter" architecture pattern, and should not be confused with a Prebid "Adapter" // (which is being phased out and replaced by Bidder for OpenRTB auctions) -func AdaptBidder(bidder adapters.Bidder, client *http.Client, cfg *config.Configuration, me metrics.MetricsEngine, name openrtb_ext.BidderName, debugInfo *config.DebugInfo) AdaptedBidder { +func AdaptBidder(bidder adapters.Bidder, client *http.Client, cfg *config.Configuration, me metrics.MetricsEngine, name openrtb_ext.BidderName, debugInfo *config.DebugInfo, endpointCompression string) AdaptedBidder { return &bidderAdapter{ Bidder: bidder, BidderName: name, Client: client, me: me, config: bidderAdapterConfig{ - Debug: cfg.Debug, - DisableConnMetrics: cfg.Metrics.Disabled.AdapterConnectionMetrics, - DebugInfo: config.DebugInfo{Allow: parseDebugInfo(debugInfo)}, + Debug: cfg.Debug, + DisableConnMetrics: cfg.Metrics.Disabled.AdapterConnectionMetrics, + DebugInfo: config.DebugInfo{Allow: parseDebugInfo(debugInfo)}, + EndpointCompression: endpointCompression, }, } } @@ -129,12 +148,13 @@ type bidderAdapter struct { } type bidderAdapterConfig struct { - Debug config.Debug - DisableConnMetrics bool - DebugInfo config.DebugInfo + Debug config.Debug + DisableConnMetrics bool + DebugInfo config.DebugInfo + EndpointCompression string } -func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error) { +func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes) ([]*pbsOrtbSeatBid, []error) { var reqData []*adapters.RequestData var errs []error @@ -164,6 +184,20 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde if reqInfo.GlobalPrivacyControlHeader == "1" { reqData[i].Headers.Add("Sec-GPC", reqInfo.GlobalPrivacyControlHeader) } + if bidRequestOptions.addCallSignHeader { + startSignRequestTime := time.Now() + signatureMessage, err := adsCertSigner.Sign(reqData[i].Uri, reqData[i].Body) + bidder.me.RecordAdsCertSignTime(time.Since(startSignRequestTime)) + if err != nil { + bidder.me.RecordAdsCertReq(false) + errs = append(errs, &errortypes.Warning{Message: fmt.Sprintf("AdsCert signer is enabled but cannot sign the request: %s", err.Error())}) + } + if err == nil && len(signatureMessage) > 0 { + reqData[i].Headers.Add(adscert.SignHeader, signatureMessage) + bidder.me.RecordAdsCertReq(true) + } + } + } // Make any HTTP requests in parallel. // If the bidder only needs to make one, save some cycles by just using the current one. @@ -193,10 +227,13 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde } defaultCurrency := "USD" - seatBid := &pbsOrtbSeatBid{ - bids: make([]*pbsOrtbBid, 0, dataLen), - currency: defaultCurrency, - httpCalls: make([]*openrtb_ext.ExtHttpCall, 0, dataLen), + seatBidMap := map[openrtb_ext.BidderName]*pbsOrtbSeatBid{ + bidderRequest.BidderName: { + bids: make([]*pbsOrtbBid, 0, dataLen), + currency: defaultCurrency, + httpCalls: make([]*openrtb_ext.ExtHttpCall, 0, dataLen), + seat: string(bidderRequest.BidderName), + }, } // If the bidder made multiple requests, we still want them to enter as many bids as possible... @@ -208,12 +245,12 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde // - headerDebugAllowed (debug override header specified correct) - it overrides all other debug restrictions // - account debug is allowed // - bidder debug is allowed - if headerDebugAllowed { - seatBid.httpCalls = append(seatBid.httpCalls, makeExt(httpInfo)) + if bidRequestOptions.headerDebugAllowed { + seatBidMap[bidderRequest.BidderName].httpCalls = append(seatBidMap[bidderRequest.BidderName].httpCalls, makeExt(httpInfo)) } else { - if accountDebugAllowed { + if bidRequestOptions.accountDebugAllowed { if bidder.config.DebugInfo.Allow { - seatBid.httpCalls = append(seatBid.httpCalls, makeExt(httpInfo)) + seatBidMap[bidderRequest.BidderName].httpCalls = append(seatBidMap[bidderRequest.BidderName].httpCalls, makeExt(httpInfo)) } else { debugDisabledWarning := errortypes.Warning{ WarningCode: errortypes.BidderLevelDebugDisabledWarningCode, @@ -244,7 +281,7 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde var err error for _, bidReqCur := range bidderRequest.BidRequest.Cur { if conversionRate, err = conversions.GetRate(bidResponse.Currency, bidReqCur); err == nil { - seatBid.currency = bidReqCur + seatBidMap[bidderRequest.BidderName].currency = bidReqCur break } } @@ -275,7 +312,11 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde reqBody := string(httpInfo.request.Body) re := regexp.MustCompile(ImpIdReqBody) reqBodySplit := re.Split(reqBody, -1) - bidResponse.Bids[i].Bid.ImpID = reqBodySplit[1] + reqImpId := reqBodySplit[1] + // replace impId if "replaceimpid" is true or not specified + if bidderRequest.ImpReplaceImpId[reqImpId] { + bidResponse.Bids[i].Bid.ImpID = reqImpId + } } } } @@ -283,12 +324,52 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde if err == nil { // Conversion rate found, using it for conversion for i := 0; i < len(bidResponse.Bids); i++ { + if bidResponse.Bids[i].BidMeta == nil { + bidResponse.Bids[i].BidMeta = &openrtb_ext.ExtBidPrebidMeta{} + } + bidResponse.Bids[i].BidMeta.AdapterCode = bidderRequest.BidderName.String() + + bidderName := bidderRequest.BidderName + if bidResponse.Bids[i].Seat != "" { + bidderName = bidResponse.Bids[i].Seat + } + + if valid, err := alternateBidderCodes.IsValidBidderCode(bidderRequest.BidderName.String(), bidderName.String()); !valid { + if err != nil { + err = &errortypes.Warning{ + WarningCode: errortypes.AlternateBidderCodeWarningCode, + Message: err.Error(), + } + errs = append(errs, err) + } + continue + } + + adjustmentFactor := 1.0 + if givenAdjustment, ok := bidRequestOptions.bidAdjustments[bidderName.String()]; ok { + adjustmentFactor = givenAdjustment + } else if givenAdjustment, ok := bidRequestOptions.bidAdjustments[bidderRequest.BidderName.String()]; ok { + adjustmentFactor = givenAdjustment + } + originalBidCpm := 0.0 if bidResponse.Bids[i].Bid != nil { originalBidCpm = bidResponse.Bids[i].Bid.Price - bidResponse.Bids[i].Bid.Price = bidResponse.Bids[i].Bid.Price * bidAdjustment * conversionRate + bidResponse.Bids[i].Bid.Price = bidResponse.Bids[i].Bid.Price * adjustmentFactor * conversionRate + } + + if _, ok := seatBidMap[bidderName]; !ok { + // Initalize seatBidMap entry as this is first extra bid with seat bidderName + seatBidMap[bidderName] = &pbsOrtbSeatBid{ + bids: make([]*pbsOrtbBid, 0, dataLen), + currency: seatBidMap[bidderRequest.BidderName].currency, + // Do we need to fill httpCalls for this?. Can we refer one from adaptercode for debugging? + httpCalls: seatBidMap[bidderRequest.BidderName].httpCalls, + seat: bidderName.String(), + } } - seatBid.bids = append(seatBid.bids, &pbsOrtbBid{ + + seatBidMap[bidderName].bids = append(seatBidMap[bidderName].bids, &pbsOrtbBid{ bid: bidResponse.Bids[i].Bid, bidMeta: bidResponse.Bids[i].BidMeta, bidType: bidResponse.Bids[i].BidType, @@ -307,7 +388,13 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde errs = append(errs, httpInfo.err) } } - return seatBid, errs + + seatBids := make([]*pbsOrtbSeatBid, 0, len(seatBidMap)) + for _, seatBid := range seatBidMap { + seatBids = append(seatBids, seatBid) + } + + return seatBids, errs } func addNativeTypes(bid *openrtb2.Bid, request *openrtb2.BidRequest) (*nativeResponse.Response, []error) { @@ -426,7 +513,16 @@ func (bidder *bidderAdapter) doRequest(ctx context.Context, req *adapters.Reques } func (bidder *bidderAdapter) doRequestImpl(ctx context.Context, req *adapters.RequestData, logger util.LogMsg) *httpCallInfo { - httpReq, err := http.NewRequest(req.Method, req.Uri, bytes.NewBuffer(req.Body)) + var requestBody []byte + + switch strings.ToUpper(bidder.config.EndpointCompression) { + case Gzip: + requestBody = compressToGZIP(req.Body) + req.Headers.Set("Content-Encoding", "gzip") + default: + requestBody = req.Body + } + httpReq, err := http.NewRequest(req.Method, req.Uri, bytes.NewBuffer(requestBody)) if err != nil { return &httpCallInfo{ request: req, @@ -465,7 +561,7 @@ func (bidder *bidderAdapter) doRequestImpl(ctx context.Context, req *adapters.Re } } - respBody, err := ioutil.ReadAll(httpResp.Body) + respBody, err := io.ReadAll(httpResp.Body) if err != nil { return &httpCallInfo{ request: req, @@ -597,3 +693,11 @@ func prepareStoredResponse(impId string, bidResp json.RawMessage) *httpCallInfo } return respData } + +func compressToGZIP(requestBody []byte) []byte { + var b bytes.Buffer + w := gzip.NewWriter(&b) + w.Write([]byte(requestBody)) + w.Close() + return b.Bytes() +} diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index 719af3048f4..24b0b76e612 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -7,22 +7,24 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" + "io" "net/http" "net/http/httptest" "net/http/httptrace" + "sort" "strings" "testing" "time" "github.com/golang/glog" - nativeRequests "github.com/mxmCherry/openrtb/v15/native1/request" - nativeResponse "github.com/mxmCherry/openrtb/v15/native1/response" - "github.com/mxmCherry/openrtb/v15/openrtb2" + nativeRequests "github.com/prebid/openrtb/v17/native1/request" + nativeResponse "github.com/prebid/openrtb/v17/native1/response" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/experiment/adscert" "github.com/prebid/prebid-server/metrics" metricsConfig "github.com/prebid/prebid-server/metrics/config" "github.com/prebid/prebid-server/openrtb_ext" @@ -54,7 +56,7 @@ func TestSingleBidder(t *testing.T) { requestHeaders := http.Header{} requestHeaders.Add("Content-Type", "application/json") - bidAdjustment := 2.0 + bidAdjustments := map[string]float64{"test": 2.0} firstInitialPrice := 3.0 secondInitialPrice := 4.0 @@ -91,14 +93,144 @@ func TestSingleBidder(t *testing.T) { } bidderImpl.bidResponse = mockBidderResponse - bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, 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)) bidderReq := BidderRequest{ BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}}, BidderName: "test", } - seatBid, errs := bidder.requestBid(ctx, bidderReq, bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, false) + bidReqOptions := bidRequestOptions{ + accountDebugAllowed: true, + headerDebugAllowed: false, + addCallSignHeader: false, + bidAdjustments: bidAdjustments, + } + seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}) + assert.Len(t, seatBids, 1) + seatBid := seatBids[0] + + // Make sure the goodSingleBidder was called with the expected arguments. + if bidderImpl.httpResponse == nil { + t.Errorf("The Bidder should be called with the server's response.") + } + if bidderImpl.httpResponse.StatusCode != respStatus { + t.Errorf("Bad response status. Expected %d, got %d", respStatus, bidderImpl.httpResponse.StatusCode) + } + if string(bidderImpl.httpResponse.Body) != respBody { + t.Errorf("Bad response body. Expected %s, got %s", respBody, string(bidderImpl.httpResponse.Body)) + } + + // Make sure the returned values are what we expect + if len(errortypes.FatalOnly(errs)) != 0 { + t.Errorf("bidder.Bid returned %d errors. Expected 0", len(errs)) + } + + if !test.debugInfo.Allow && len(errortypes.WarningOnly(errs)) != 1 { + t.Errorf("bidder.Bid returned %d warnings. Expected 1", len(errs)) + } + if len(seatBid.bids) != len(mockBidderResponse.Bids) { + t.Fatalf("Expected %d bids. Got %d", len(mockBidderResponse.Bids), len(seatBid.bids)) + } + for index, typedBid := range mockBidderResponse.Bids { + if typedBid.Bid != seatBid.bids[index].bid { + t.Errorf("Bid %d did not point to the same bid returned by the Bidder.", index) + } + if typedBid.BidType != seatBid.bids[index].bidType { + t.Errorf("Bid %d did not have the right type. Expected %s, got %s", index, typedBid.BidType, seatBid.bids[index].bidType) + } + if typedBid.DealPriority != seatBid.bids[index].dealPriority { + t.Errorf("Bid %d did not have the right deal priority. Expected %s, got %s", index, typedBid.BidType, seatBid.bids[index].bidType) + } + } + bidAdjustment := bidAdjustments["test"] + if mockBidderResponse.Bids[0].Bid.Price != bidAdjustment*firstInitialPrice { + t.Errorf("Bid[0].Price was not adjusted properly. Expected %f, got %f", bidAdjustment*firstInitialPrice, mockBidderResponse.Bids[0].Bid.Price) + } + if mockBidderResponse.Bids[1].Bid.Price != bidAdjustment*secondInitialPrice { + t.Errorf("Bid[1].Price was not adjusted properly. Expected %f, got %f", bidAdjustment*secondInitialPrice, mockBidderResponse.Bids[1].Bid.Price) + } + if len(seatBid.httpCalls) != test.httpCallsLen { + t.Errorf("The bidder shouldn't log HttpCalls when request.test == 0. Found %d", len(seatBid.httpCalls)) + } + for index, bid := range seatBid.bids { + assert.NotEqual(t, mockBidderResponse.Bids[index].Bid.Price, bid.originalBidCPM, "The bid price was adjusted, so the originally bid CPM should be different") + } + } +} + +func TestSingleBidderGzip(t *testing.T) { + type aTest struct { + debugInfo *config.DebugInfo + httpCallsLen int + } + + testCases := []*aTest{ + {&config.DebugInfo{Allow: false}, 0}, + {&config.DebugInfo{Allow: true}, 1}, + } + + respStatus := 200 + respBody := "{\"bid\":false}" + server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody)) + defer server.Close() + + requestHeaders := http.Header{} + requestHeaders.Add("Content-Type", "application/json") + + bidAdjustments := map[string]float64{"test": 2.0} + firstInitialPrice := 3.0 + secondInitialPrice := 4.0 + + bidderImpl := &goodSingleBidder{ + httpRequest: &adapters.RequestData{ + Method: "POST", + Uri: server.URL, + Body: []byte(`{"key":"val"}`), + Headers: http.Header{}, + }, + bidResponse: nil, + } + + ctx := context.Background() + + for _, test := range testCases { + mockBidderResponse := &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + Price: firstInitialPrice, + }, + BidType: openrtb_ext.BidTypeBanner, + DealPriority: 4, + }, + { + Bid: &openrtb2.Bid{ + Price: secondInitialPrice, + }, + BidType: openrtb_ext.BidTypeVideo, + DealPriority: 5, + }, + }, + } + bidderImpl.bidResponse = mockBidderResponse + + bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, test.debugInfo, "GZIP") + currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) + + bidderReq := BidderRequest{ + BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}}, + BidderName: "test", + } + bidReqOptions := bidRequestOptions{ + accountDebugAllowed: true, + headerDebugAllowed: false, + addCallSignHeader: false, + bidAdjustments: bidAdjustments, + } + seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}) + assert.Len(t, seatBids, 1) + seatBid := seatBids[0] // Make sure the goodSingleBidder was called with the expected arguments. if bidderImpl.httpResponse == nil { @@ -133,6 +265,7 @@ func TestSingleBidder(t *testing.T) { t.Errorf("Bid %d did not have the right deal priority. Expected %s, got %s", index, typedBid.BidType, seatBid.bids[index].bidType) } } + bidAdjustment := bidAdjustments["test"] if mockBidderResponse.Bids[0].Bid.Price != bidAdjustment*firstInitialPrice { t.Errorf("Bid[0].Price was not adjusted properly. Expected %f, got %f", bidAdjustment*firstInitialPrice, mockBidderResponse.Bids[0].Bid.Price) } @@ -142,6 +275,10 @@ func TestSingleBidder(t *testing.T) { if len(seatBid.httpCalls) != test.httpCallsLen { t.Errorf("The bidder shouldn't log HttpCalls when request.test == 0. Found %d", len(seatBid.httpCalls)) } + if test.debugInfo.Allow && len(seatBid.httpCalls) > 0 { + assert.Equalf(t, "gzip", seatBid.httpCalls[0].RequestHeaders["Content-Encoding"][0], "Mismatched headers") + assert.Equalf(t, "{\"key\":\"val\"}", seatBid.httpCalls[0].RequestBody, "Mismatched request bodies") + } for index, bid := range seatBid.bids { assert.NotEqual(t, mockBidderResponse.Bids[index].Bid.Price, bid.originalBidCPM, "The bid price was adjusted, so the originally bid CPM should be different") } @@ -177,14 +314,21 @@ func TestRequestBidRemovesSensitiveHeaders(t *testing.T) { debugInfo := &config.DebugInfo{Allow: true} ctx := context.Background() - bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, 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)) bidderReq := BidderRequest{ BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}}, BidderName: "test", } - seatBid, errs := bidder.requestBid(ctx, bidderReq, 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, false) + bidAdjustments := map[string]float64{"test": 1} + bidReqOptions := bidRequestOptions{ + accountDebugAllowed: true, + headerDebugAllowed: false, + addCallSignHeader: false, + bidAdjustments: bidAdjustments, + } + seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}) expectedHttpCalls := []*openrtb_ext.ExtHttpCall{ { @@ -197,7 +341,8 @@ func TestRequestBidRemovesSensitiveHeaders(t *testing.T) { } assert.Empty(t, errs) - assert.ElementsMatch(t, seatBid.httpCalls, expectedHttpCalls) + assert.Len(t, seatBids, 1) + assert.ElementsMatch(t, seatBids[0].httpCalls, expectedHttpCalls) } func TestSetGPCHeader(t *testing.T) { @@ -222,13 +367,20 @@ func TestSetGPCHeader(t *testing.T) { debugInfo := &config.DebugInfo{Allow: true} ctx := context.Background() - bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, 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)) bidderReq := BidderRequest{ BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}}, BidderName: "test", } - seatBid, errs := bidder.requestBid(ctx, bidderReq, 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, true, false) + bidAdjustments := map[string]float64{"test": 1} + bidReqOptions := bidRequestOptions{ + accountDebugAllowed: true, + headerDebugAllowed: false, + addCallSignHeader: false, + bidAdjustments: bidAdjustments, + } + seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}) expectedHttpCall := []*openrtb_ext.ExtHttpCall{ { @@ -241,7 +393,8 @@ func TestSetGPCHeader(t *testing.T) { } assert.Empty(t, errs) - assert.ElementsMatch(t, seatBid.httpCalls, expectedHttpCall) + assert.Len(t, seatBids, 1) + assert.ElementsMatch(t, seatBids[0].httpCalls, expectedHttpCall) } func TestSetGPCHeaderNil(t *testing.T) { @@ -263,14 +416,21 @@ func TestSetGPCHeaderNil(t *testing.T) { debugInfo := &config.DebugInfo{Allow: true} ctx := context.Background() - bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, 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)) bidderReq := BidderRequest{ BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}}, BidderName: "test", } - seatBid, errs := bidder.requestBid(ctx, bidderReq, 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, true, false) + bidAdjustments := map[string]float64{"test": 1} + bidReqOptions := bidRequestOptions{ + accountDebugAllowed: true, + headerDebugAllowed: false, + addCallSignHeader: false, + bidAdjustments: bidAdjustments, + } + seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}) expectedHttpCall := []*openrtb_ext.ExtHttpCall{ { @@ -283,7 +443,8 @@ func TestSetGPCHeaderNil(t *testing.T) { } assert.Empty(t, errs) - assert.ElementsMatch(t, seatBid.httpCalls, expectedHttpCall) + assert.Len(t, seatBids, 1) + assert.ElementsMatch(t, seatBids[0].httpCalls, expectedHttpCall) } // TestMultiBidder makes sure all the requests get sent, and the responses processed. @@ -326,23 +487,30 @@ func TestMultiBidder(t *testing.T) { }}, bidResponse: mockBidderResponse, } - bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, 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)) bidderReq := BidderRequest{ BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}}, BidderName: "test", } - seatBid, errs := bidder.requestBid(context.Background(), bidderReq, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, true) + bidAdjustments := map[string]float64{"test": 1.0} + bidReqOptions := bidRequestOptions{ + accountDebugAllowed: true, + headerDebugAllowed: true, + addCallSignHeader: false, + bidAdjustments: bidAdjustments, + } + seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}) - if seatBid == nil { + if len(seatBids) != 1 { t.Fatalf("SeatBid should exist, because bids exist.") } if len(errs) != 1+len(bidderImpl.httpRequests) { t.Errorf("Expected %d errors. Got %d", 1+len(bidderImpl.httpRequests), len(errs)) } - if len(seatBid.bids) != len(bidderImpl.httpResponses)*len(mockBidderResponse.Bids) { - t.Errorf("Expected %d bids. Got %d", len(bidderImpl.httpResponses)*len(mockBidderResponse.Bids), len(seatBid.bids)) + if len(seatBids[0].bids) != len(bidderImpl.httpResponses)*len(mockBidderResponse.Bids) { + t.Errorf("Expected %d bids. Got %d", len(bidderImpl.httpResponses)*len(mockBidderResponse.Bids), len(seatBids[0].bids)) } } @@ -685,7 +853,7 @@ func TestMultiCurrencies(t *testing.T) { ) // Execute: - bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil) + bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, "") currencyConverter := currency.NewRateConverter( &http.Client{}, mockedHTTPServer.URL, @@ -698,15 +866,23 @@ func TestMultiCurrencies(t *testing.T) { BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}}, BidderName: openrtb_ext.BidderAppnexus, } - seatBid, errs := bidder.requestBid( + bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 1} + seatBids, errs := bidder.requestBid( context.Background(), bidderReq, - 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, - true, - true, + &adscert.NilSigner{}, + bidRequestOptions{ + accountDebugAllowed: true, + headerDebugAllowed: true, + addCallSignHeader: false, + bidAdjustments: bidAdjustments, + }, + openrtb_ext.ExtAlternateBidderCodes{}, ) + assert.Len(t, seatBids, 1) + seatBid := seatBids[0] // Verify: resultLightBids := make([]bid, len(seatBid.bids)) @@ -842,21 +1018,29 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { } // Execute: - bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, 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)) bidderReq := BidderRequest{ BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}}, BidderName: "test", } - seatBid, errs := bidder.requestBid( + bidAdjustments := map[string]float64{"test": 1} + seatBids, errs := bidder.requestBid( context.Background(), bidderReq, - 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, - true, - true, + &adscert.NilSigner{}, + bidRequestOptions{ + accountDebugAllowed: true, + headerDebugAllowed: true, + addCallSignHeader: false, + bidAdjustments: bidAdjustments, + }, + openrtb_ext.ExtAlternateBidderCodes{}, ) + assert.Len(t, seatBids, 1) + seatBid := seatBids[0] // Verify: assert.Equal(t, false, (seatBid == nil && tc.expectedBidsCount != 0), tc.description) @@ -1007,7 +1191,7 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) { } // Execute: - bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil) + bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, "") currencyConverter := currency.NewRateConverter( &http.Client{}, mockedHTTPServer.URL, @@ -1017,15 +1201,23 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) { BidRequest: &openrtb2.BidRequest{Cur: tc.bidRequestCurrencies, Imp: []openrtb2.Imp{{ID: "impId"}}}, BidderName: "test", } - seatBid, errs := bidder.requestBid( + bidAdjustments := map[string]float64{"test": 1} + seatBids, errs := bidder.requestBid( context.Background(), bidderReq, - 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, - true, - false, + &adscert.NilSigner{}, + bidRequestOptions{ + accountDebugAllowed: true, + headerDebugAllowed: false, + addCallSignHeader: false, + bidAdjustments: bidAdjustments, + }, + openrtb_ext.ExtAlternateBidderCodes{}, ) + assert.Len(t, seatBids, 1) + seatBid := seatBids[0] // Verify: if tc.expectedError { @@ -1318,25 +1510,32 @@ func TestMobileNativeTypes(t *testing.T) { }, bidResponse: tc.mockBidderResponse, } - bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, 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)) bidderReq := BidderRequest{ BidRequest: tc.mockBidderRequest, BidderName: "test", } + bidAdjustments := map[string]float64{"test": 1.0} seatBids, _ := bidder.requestBid( context.Background(), bidderReq, - 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, - true, - true, + &adscert.NilSigner{}, + bidRequestOptions{ + accountDebugAllowed: true, + headerDebugAllowed: true, + addCallSignHeader: false, + bidAdjustments: bidAdjustments, + }, + openrtb_ext.ExtAlternateBidderCodes{}, ) + assert.Len(t, seatBids, 1) var actualValue string - for _, bid := range seatBids.bids { + for _, bid := range seatBids[0].bids { actualValue = bid.bid.AdM assert.JSONEq(t, tc.expectedValue, actualValue, tc.description) } @@ -1350,17 +1549,18 @@ func TestRequestBidsStoredBidResponses(t *testing.T) { defer server.Close() bidRespId1 := json.RawMessage(`{"id": "resp_id1", "seatbid": [{"bid": [{"id": "bid_id1"}], "seat": "testBidder1"}], "bidid": "123", "cur": "USD"}`) - bidRespId2 := json.RawMessage(`{"id": "resp_id2", "seatbid": [{"bid": [{"id": "bid_id2_1"},{"id": "bid_id2_2"}], "seat": "testBidder2"}], "bidid": "124", "cur": "USD"}`) + bidRespId2 := json.RawMessage(`{"id": "resp_id2", "seatbid": [{"bid": [{"id": "bid_id2_1", "impid": "bid1impid1"},{"id": "bid_id2_2", "impid": "bid2impid2"}], "seat": "testBidder2"}], "bidid": "124", "cur": "USD"}`) testCases := []struct { description string mockBidderRequest *openrtb2.BidRequest bidderStoredResponses map[string]json.RawMessage + impReplaceImpId map[string]bool expectedBidIds []string expectedImpIds []string }{ { - description: "Single imp with stored bid response", + description: "Single imp with stored bid response, replace impid is true", mockBidderRequest: &openrtb2.BidRequest{ Imp: nil, App: &openrtb2.App{}, @@ -1368,11 +1568,14 @@ func TestRequestBidsStoredBidResponses(t *testing.T) { bidderStoredResponses: map[string]json.RawMessage{ "bidResponseId1": bidRespId1, }, + impReplaceImpId: map[string]bool{ + "bidResponseId1": true, + }, expectedBidIds: []string{"bid_id1"}, expectedImpIds: []string{"bidResponseId1"}, }, { - description: "Single imp with multiple stored bid responses", + description: "Single imp with multiple stored bid responses, replace impid is true", mockBidderRequest: &openrtb2.BidRequest{ Imp: nil, App: &openrtb2.App{}, @@ -1380,49 +1583,99 @@ func TestRequestBidsStoredBidResponses(t *testing.T) { bidderStoredResponses: map[string]json.RawMessage{ "bidResponseId2": bidRespId2, }, + impReplaceImpId: map[string]bool{ + "bidResponseId2": true, + }, expectedBidIds: []string{"bid_id2_1", "bid_id2_2"}, expectedImpIds: []string{"bidResponseId2", "bidResponseId2"}, }, + { + description: "Single imp with multiple stored bid responses, replace impid is false", + mockBidderRequest: &openrtb2.BidRequest{ + Imp: nil, + App: &openrtb2.App{}, + }, + bidderStoredResponses: map[string]json.RawMessage{ + "bidResponseId2": bidRespId2, + }, + impReplaceImpId: map[string]bool{ + "bidResponseId2": false, + }, + expectedBidIds: []string{"bid_id2_1", "bid_id2_2"}, + expectedImpIds: []string{"bid1impid1", "bid2impid2"}, + }, + { + description: "Two imp with multiple stored bid responses, replace impid is true and false", + mockBidderRequest: &openrtb2.BidRequest{ + Imp: nil, + App: &openrtb2.App{}, + }, + bidderStoredResponses: map[string]json.RawMessage{ + "bidResponseId1": bidRespId1, + "bidResponseId2": bidRespId2, + }, + impReplaceImpId: map[string]bool{ + "bidResponseId1": true, + "bidResponseId2": false, + }, + expectedBidIds: []string{"bid_id2_1", "bid_id2_2", "bid_id1"}, + expectedImpIds: []string{"bid1impid1", "bid2impid2", "bidResponseId1"}, + }, } for _, tc := range testCases { bidderImpl := &goodSingleBidderWithStoredBidResp{} - bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, 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)) bidderReq := BidderRequest{ BidRequest: tc.mockBidderRequest, BidderName: openrtb_ext.BidderAppnexus, BidderStoredResponses: tc.bidderStoredResponses, + ImpReplaceImpId: tc.impReplaceImpId, } + bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 1.0} seatBids, _ := bidder.requestBid( context.Background(), bidderReq, - 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, - true, - true, + &adscert.NilSigner{}, + bidRequestOptions{ + accountDebugAllowed: true, + headerDebugAllowed: true, + addCallSignHeader: false, + bidAdjustments: bidAdjustments, + }, + openrtb_ext.ExtAlternateBidderCodes{}, ) + assert.Len(t, seatBids, 1) - assert.Len(t, seatBids.bids, len(tc.expectedBidIds), "Incorrect bids number for test case ", tc.description) - for index, bid := range seatBids.bids { - assert.Equal(t, tc.expectedBidIds[index], bid.bid.ID, tc.description) - assert.Equal(t, tc.expectedImpIds[index], bid.bid.ImpID, tc.description) + assert.Len(t, seatBids[0].bids, len(tc.expectedBidIds), "Incorrect bids number for test case ", tc.description) + for _, bid := range seatBids[0].bids { + assert.Contains(t, tc.expectedBidIds, bid.bid.ID, tc.description) + assert.Contains(t, tc.expectedImpIds, bid.bid.ImpID, tc.description) } } } func TestErrorReporting(t *testing.T) { - bidder := AdaptBidder(&bidRejector{}, nil, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil) + bidder := AdaptBidder(&bidRejector{}, nil, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, "") currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) bidderReq := BidderRequest{ BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}}, BidderName: "test", } - bids, errs := bidder.requestBid(context.Background(), bidderReq, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, false) + bidAdjustments := map[string]float64{"test": 1.0} + bidReqOptions := bidRequestOptions{ + accountDebugAllowed: true, + headerDebugAllowed: false, + addCallSignHeader: false, + bidAdjustments: bidAdjustments, + } + bids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}) if bids != nil { t.Errorf("There should be no seatbid if no http requests are returned.") } @@ -1621,7 +1874,7 @@ func TestCallRecordAdapterConnections(t *testing.T) { defer server.Close() // declare requestBid parameters - bidAdjustment := 2.0 + bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 2.0} bidderImpl := &goodSingleBidder{ httpRequest: &adapters.RequestData{ @@ -1641,14 +1894,20 @@ func TestCallRecordAdapterConnections(t *testing.T) { metrics.On("RecordAdapterConnections", expectedAdapterName, false, mock.MatchedBy(compareConnWaitTime)).Once() // Run requestBid using an http.Client with a mock handler - bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, metrics, openrtb_ext.BidderAppnexus, nil) + bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, metrics, openrtb_ext.BidderAppnexus, nil, "") currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) bidderReq := BidderRequest{ BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}}, BidderName: openrtb_ext.BidderAppnexus, } - _, errs := bidder.requestBid(context.Background(), bidderReq, bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, true) + bidReqOptions := bidRequestOptions{ + accountDebugAllowed: true, + headerDebugAllowed: true, + addCallSignHeader: false, + bidAdjustments: bidAdjustments, + } + _, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}) // Assert no errors assert.Equal(t, 0, len(errs), "bidder.requestBid returned errors %v \n", errs) @@ -1667,7 +1926,7 @@ func (DNSDoneTripper) RoundTrip(req *http.Request) (*http.Response, error) { resp := &http.Response{ StatusCode: 200, - Body: ioutil.NopCloser(strings.NewReader("postBody")), + Body: io.NopCloser(strings.NewReader("postBody")), } return resp, nil @@ -1683,7 +1942,7 @@ func (TLSHandshakeTripper) RoundTrip(req *http.Request) (*http.Response, error) resp := &http.Response{ StatusCode: 200, - Body: ioutil.NopCloser(strings.NewReader("postBody")), + Body: io.NopCloser(strings.NewReader("postBody")), } return resp, nil @@ -1846,9 +2105,59 @@ func TestPrepareStoredResponse(t *testing.T) { assert.Equal(t, []byte(`{"id": "resp_id1"}`), result.response.Body, "incorrect response body") } +func TestRequestBidsWithAdsCertsSigner(t *testing.T) { + respStatus := 200 + respBody := `{"bid":false}` + server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody)) + defer server.Close() + + requestHeaders := http.Header{} + requestHeaders.Add("Content-Type", "application/json") + + bidderImpl := &goodSingleBidder{ + httpRequest: &adapters.RequestData{ + Method: "POST", + Uri: server.URL, + Body: []byte(`{"key":"val"}`), + Headers: http.Header{}, + }, + bidResponse: nil, + } + bidderImpl.bidResponse = &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "bidId", + }, + BidType: openrtb_ext.BidTypeBanner, + DealPriority: 4, + }, + }, + } + + bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: false}, "") + currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) + + bidderReq := BidderRequest{ + BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}}, + BidderName: "test", + } + ctx := context.Background() + bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 2.0} + bidReqOptions := bidRequestOptions{ + accountDebugAllowed: false, + headerDebugAllowed: false, + addCallSignHeader: true, + bidAdjustments: bidAdjustments, + } + _, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}) + + assert.Empty(t, errs, "no errors should be returned") +} + func wrapWithBidderInfo(bidder adapters.Bidder) adapters.Bidder { bidderInfo := config.BidderInfo{ - Enabled: true, + Disabled: false, Capabilities: &config.CapabilitiesInfo{ App: &config.PlatformInfo{ MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}, @@ -1973,3 +2282,557 @@ func (bidder *notifyingBidder) MakeBids(internalRequest *openrtb2.BidRequest, ex func (bidder *notifyingBidder) MakeTimeoutNotification(req *adapters.RequestData) (*adapters.RequestData, []error) { return &bidder.notifyRequest, nil } + +func TestExtraBid(t *testing.T) { + respStatus := 200 + respBody := "{\"bid\":false}" + server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody)) + defer server.Close() + + requestHeaders := http.Header{} + requestHeaders.Add("Content-Type", "application/json") + + bidderImpl := &goodSingleBidder{ + httpRequest: &adapters.RequestData{ + Method: "POST", + Uri: server.URL, + Body: []byte("{\"key\":\"val\"}"), + Headers: http.Header{}, + }, + bidResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "pubmaticImp1", + }, + BidType: openrtb_ext.BidTypeBanner, + DealPriority: 4, + Seat: "pubmatic", + }, + { + Bid: &openrtb2.Bid{ + ID: "groupmImp1", + }, + BidType: openrtb_ext.BidTypeVideo, + DealPriority: 5, + Seat: "groupm", + }, + }, + }, + } + + wantSeatBids := []*pbsOrtbSeatBid{ + { + httpCalls: []*openrtb_ext.ExtHttpCall{}, + bids: []*pbsOrtbBid{{ + bid: &openrtb2.Bid{ID: "groupmImp1"}, + dealPriority: 5, + bidType: openrtb_ext.BidTypeVideo, + originalBidCur: "USD", + bidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, + }}, + seat: "groupm", + currency: "USD", + }, + { + httpCalls: []*openrtb_ext.ExtHttpCall{}, + bids: []*pbsOrtbBid{{ + bid: &openrtb2.Bid{ID: "pubmaticImp1"}, + dealPriority: 4, + bidType: openrtb_ext.BidTypeBanner, + originalBidCur: "USD", + bidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, + }}, + seat: string(openrtb_ext.BidderPubmatic), + currency: "USD", + }, + } + + bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{}, "") + currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) + + bidderReq := BidderRequest{ + BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}}, + BidderName: openrtb_ext.BidderPubmatic, + } + + bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 2.0} + bidReqOptions := bidRequestOptions{ + accountDebugAllowed: false, + headerDebugAllowed: false, + addCallSignHeader: true, + bidAdjustments: bidAdjustments, + } + + seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions, + openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + string(openrtb_ext.BidderPubmatic): { + Enabled: true, + AllowedBidderCodes: []string{"groupm"}, + }, + }, + }) + assert.Nil(t, errs) + assert.Len(t, seatBids, 2) + sort.Slice(seatBids, func(i, j int) bool { + return len(seatBids[i].seat) < len(seatBids[j].seat) + }) + assert.Equal(t, wantSeatBids, seatBids) +} + +func TestExtraBidWithAlternateBidderCodeDisabled(t *testing.T) { + respStatus := 200 + respBody := "{\"bid\":false}" + server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody)) + defer server.Close() + + requestHeaders := http.Header{} + requestHeaders.Add("Content-Type", "application/json") + + bidderImpl := &goodSingleBidder{ + httpRequest: &adapters.RequestData{ + Method: "POST", + Uri: server.URL, + Body: []byte("{\"key\":\"val\"}"), + Headers: http.Header{}, + }, + bidResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "pubmaticImp1", + }, + BidType: openrtb_ext.BidTypeBanner, + DealPriority: 4, + Seat: "pubmatic", + }, + { + Bid: &openrtb2.Bid{ + ID: "groupmImp1", + }, + BidType: openrtb_ext.BidTypeVideo, + DealPriority: 5, + Seat: "groupm-rejected", + }, + { + Bid: &openrtb2.Bid{ + ID: "groupmImp2", + }, + BidType: openrtb_ext.BidTypeVideo, + DealPriority: 5, + Seat: "groupm-allowed", + }, + }, + }, + } + + wantSeatBids := []*pbsOrtbSeatBid{ + { + httpCalls: []*openrtb_ext.ExtHttpCall{}, + bids: []*pbsOrtbBid{{ + bid: &openrtb2.Bid{ID: "groupmImp2"}, + dealPriority: 5, + bidType: openrtb_ext.BidTypeVideo, + originalBidCur: "USD", + bidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, + }}, + seat: "groupm-allowed", + currency: "USD", + }, + { + httpCalls: []*openrtb_ext.ExtHttpCall{}, + bids: []*pbsOrtbBid{{ + bid: &openrtb2.Bid{ID: "pubmaticImp1"}, + dealPriority: 4, + bidType: openrtb_ext.BidTypeBanner, + originalBidCur: "USD", + bidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, + }}, + seat: string(openrtb_ext.BidderPubmatic), + currency: "USD", + }, + } + wantErrs := []error{ + &errortypes.Warning{ + WarningCode: errortypes.AlternateBidderCodeWarningCode, + Message: `invalid biddercode "groupm-rejected" sent by adapter "pubmatic"`, + }, + } + + bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{}, "") + currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) + + bidderReq := BidderRequest{ + BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}}, + BidderName: openrtb_ext.BidderPubmatic, + } + bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 2.0} + bidReqOptions := bidRequestOptions{ + accountDebugAllowed: false, + headerDebugAllowed: false, + addCallSignHeader: true, + bidAdjustments: bidAdjustments, + } + + seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions, + openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + string(openrtb_ext.BidderPubmatic): { + Enabled: true, + AllowedBidderCodes: []string{"groupm-allowed"}, + }, + }, + }) + assert.Equal(t, wantErrs, errs) + assert.Len(t, seatBids, 2) + assert.ElementsMatch(t, wantSeatBids, seatBids) +} + +func TestExtraBidWithBidAdjustments(t *testing.T) { + respStatus := 200 + respBody := "{\"bid\":false}" + server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody)) + defer server.Close() + + requestHeaders := http.Header{} + requestHeaders.Add("Content-Type", "application/json") + + bidderImpl := &goodSingleBidder{ + httpRequest: &adapters.RequestData{ + Method: "POST", + Uri: server.URL, + Body: []byte("{\"key\":\"val\"}"), + Headers: http.Header{}, + }, + bidResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "pubmaticImp1", + Price: 3, + }, + BidType: openrtb_ext.BidTypeBanner, + DealPriority: 4, + Seat: "pubmatic", + }, + { + Bid: &openrtb2.Bid{ + ID: "groupmImp1", + Price: 7, + }, + BidType: openrtb_ext.BidTypeVideo, + DealPriority: 5, + Seat: "groupm", + }, + }, + }, + } + + wantSeatBids := []*pbsOrtbSeatBid{ + { + httpCalls: []*openrtb_ext.ExtHttpCall{}, + bids: []*pbsOrtbBid{{ + bid: &openrtb2.Bid{ + ID: "groupmImp1", + Price: 21, + }, + dealPriority: 5, + bidType: openrtb_ext.BidTypeVideo, + originalBidCPM: 7, + originalBidCur: "USD", + bidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, + }}, + seat: "groupm", + currency: "USD", + }, + { + httpCalls: []*openrtb_ext.ExtHttpCall{}, + bids: []*pbsOrtbBid{{ + bid: &openrtb2.Bid{ + ID: "pubmaticImp1", + Price: 6, + }, + dealPriority: 4, + bidType: openrtb_ext.BidTypeBanner, + originalBidCur: "USD", + originalBidCPM: 3, + bidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, + }}, + seat: string(openrtb_ext.BidderPubmatic), + currency: "USD", + }, + } + + bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{}, "") + currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) + + bidderReq := BidderRequest{ + BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}}, + BidderName: openrtb_ext.BidderPubmatic, + } + bidAdjustments := map[string]float64{ + string(openrtb_ext.BidderPubmatic): 2, + string(openrtb_ext.BidderGroupm): 3, + } + + bidReqOptions := bidRequestOptions{ + accountDebugAllowed: false, + headerDebugAllowed: false, + addCallSignHeader: true, + bidAdjustments: bidAdjustments, + } + + seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions, + openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + string(openrtb_ext.BidderPubmatic): { + Enabled: true, + AllowedBidderCodes: []string{"groupm"}, + }, + }, + }) + assert.Nil(t, errs) + assert.Len(t, seatBids, 2) + sort.Slice(seatBids, func(i, j int) bool { + return len(seatBids[i].seat) < len(seatBids[j].seat) + }) + assert.Equal(t, wantSeatBids, seatBids) +} + +func TestExtraBidWithBidAdjustmentsUsingAdapterCode(t *testing.T) { + respStatus := 200 + respBody := "{\"bid\":false}" + server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody)) + defer server.Close() + + requestHeaders := http.Header{} + requestHeaders.Add("Content-Type", "application/json") + + bidderImpl := &goodSingleBidder{ + httpRequest: &adapters.RequestData{ + Method: "POST", + Uri: server.URL, + Body: []byte("{\"key\":\"val\"}"), + Headers: http.Header{}, + }, + bidResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "pubmaticImp1", + Price: 3, + }, + BidType: openrtb_ext.BidTypeBanner, + DealPriority: 4, + Seat: "pubmatic", + }, + { + Bid: &openrtb2.Bid{ + ID: "groupmImp1", + Price: 7, + }, + BidType: openrtb_ext.BidTypeVideo, + DealPriority: 5, + Seat: "groupm", + }, + }, + }, + } + + wantSeatBids := []*pbsOrtbSeatBid{ + { + httpCalls: []*openrtb_ext.ExtHttpCall{}, + bids: []*pbsOrtbBid{{ + bid: &openrtb2.Bid{ + ID: "groupmImp1", + Price: 14, + }, + dealPriority: 5, + bidType: openrtb_ext.BidTypeVideo, + originalBidCPM: 7, + originalBidCur: "USD", + bidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, + }}, + seat: "groupm", + currency: "USD", + }, + { + httpCalls: []*openrtb_ext.ExtHttpCall{}, + bids: []*pbsOrtbBid{{ + bid: &openrtb2.Bid{ + ID: "pubmaticImp1", + Price: 6, + }, + dealPriority: 4, + bidType: openrtb_ext.BidTypeBanner, + originalBidCur: "USD", + originalBidCPM: 3, + bidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, + }}, + seat: string(openrtb_ext.BidderPubmatic), + currency: "USD", + }, + } + + bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{}, "") + currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) + + bidderReq := BidderRequest{ + BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}}, + BidderName: openrtb_ext.BidderPubmatic, + } + bidAdjustments := map[string]float64{ + string(openrtb_ext.BidderPubmatic): 2, + } + + bidReqOptions := bidRequestOptions{ + accountDebugAllowed: false, + headerDebugAllowed: false, + addCallSignHeader: true, + bidAdjustments: bidAdjustments, + } + + seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions, + openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + string(openrtb_ext.BidderPubmatic): { + Enabled: true, + AllowedBidderCodes: []string{"groupm"}, + }, + }, + }) + assert.Nil(t, errs) + assert.Len(t, seatBids, 2) + sort.Slice(seatBids, func(i, j int) bool { + return len(seatBids[i].seat) < len(seatBids[j].seat) + }) + assert.Equal(t, wantSeatBids, seatBids) +} + +func TestExtraBidWithMultiCurrencies(t *testing.T) { + respStatus := 200 + respBody := "{\"bid\":false}" + server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody)) + defer server.Close() + + requestHeaders := http.Header{} + requestHeaders.Add("Content-Type", "application/json") + + bidderImpl := &goodSingleBidder{ + httpRequest: &adapters.RequestData{ + Method: "POST", + Uri: server.URL, + Body: []byte("{\"key\":\"val\"}"), + Headers: http.Header{}, + }, + bidResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "pubmaticImp1", + Price: 3, + }, + BidType: openrtb_ext.BidTypeBanner, + DealPriority: 4, + Seat: "pubmatic", + }, + { + Bid: &openrtb2.Bid{ + ID: "groupmImp1", + Price: 7, + }, + BidType: openrtb_ext.BidTypeVideo, + DealPriority: 5, + Seat: "groupm", + }, + }, + }, + } + + wantSeatBids := []*pbsOrtbSeatBid{ + { + httpCalls: []*openrtb_ext.ExtHttpCall{}, + bids: []*pbsOrtbBid{{ + bid: &openrtb2.Bid{ + ID: "groupmImp1", + Price: 571.5994430039375, + }, + dealPriority: 5, + bidType: openrtb_ext.BidTypeVideo, + originalBidCPM: 7, + originalBidCur: "USD", + bidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, + }}, + seat: "groupm", + currency: "INR", + }, + { + httpCalls: []*openrtb_ext.ExtHttpCall{}, + bids: []*pbsOrtbBid{{ + bid: &openrtb2.Bid{ + ID: "pubmaticImp1", + Price: 244.97118985883034, + }, + dealPriority: 4, + bidType: openrtb_ext.BidTypeBanner, + originalBidCPM: 3, + originalBidCur: "USD", + bidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, + }}, + seat: string(openrtb_ext.BidderPubmatic), + currency: "INR", + }, + } + + mockedHTTPServer := httptest.NewServer(http.HandlerFunc( + func(rw http.ResponseWriter, req *http.Request) { + rw.Write([]byte(`{"dataAsOf":"2022-11-24T00:00:00.000Z","generatedAt":"2022-11-24T15:00:46.363Z","conversions":{"USD":{"USD":1,"INR":81.65706328627678}}}`)) + rw.WriteHeader(http.StatusOK) + }), + ) + + // Execute: + bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, "") + currencyConverter := currency.NewRateConverter( + &http.Client{}, + mockedHTTPServer.URL, + time.Duration(24)*time.Hour, + ) + time.Sleep(time.Duration(500) * time.Millisecond) + currencyConverter.Run() + + bidderReq := BidderRequest{ + BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}, Cur: []string{"INR"}}, + BidderName: openrtb_ext.BidderPubmatic, + } + + bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 2.0} + bidReqOptions := bidRequestOptions{ + accountDebugAllowed: false, + headerDebugAllowed: false, + addCallSignHeader: true, + bidAdjustments: bidAdjustments, + } + + seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions, + openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + string(openrtb_ext.BidderPubmatic): { + Enabled: true, + AllowedBidderCodes: []string{"groupm"}, + }, + }, + }) + assert.Nil(t, errs) + assert.Len(t, seatBids, 2) + sort.Slice(seatBids, func(i, j int) bool { + return len(seatBids[i].seat) < len(seatBids[j].seat) + }) + assert.Equal(t, wantSeatBids, seatBids) +} diff --git a/exchange/bidder_validate_bids.go b/exchange/bidder_validate_bids.go index ab1d3904c8e..30395491f49 100644 --- a/exchange/bidder_validate_bids.go +++ b/exchange/bidder_validate_bids.go @@ -6,9 +6,11 @@ import ( "fmt" "strings" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/currency" + "github.com/prebid/prebid-server/experiment/adscert" + "github.com/prebid/prebid-server/openrtb_ext" goCurrency "golang.org/x/text/currency" ) @@ -27,12 +29,14 @@ type validatedBidder struct { bidder AdaptedBidder } -func (v *validatedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error) { - seatBid, errs := v.bidder.requestBid(ctx, bidderRequest, bidAdjustment, conversions, reqInfo, accountDebugAllowed, headerDebugAllowed) - if validationErrors := removeInvalidBids(bidderRequest.BidRequest, seatBid); len(validationErrors) > 0 { - errs = append(errs, validationErrors...) +func (v *validatedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes) ([]*pbsOrtbSeatBid, []error) { + seatBids, errs := v.bidder.requestBid(ctx, bidderRequest, conversions, reqInfo, adsCertSigner, bidRequestOptions, alternateBidderCodes) + for _, seatBid := range seatBids { + if validationErrors := removeInvalidBids(bidderRequest.BidRequest, seatBid); len(validationErrors) > 0 { + errs = append(errs, validationErrors...) + } } - return seatBid, errs + return seatBids, errs } // validateBids will run some validation checks on the returned bids and excise any invalid bids diff --git a/exchange/bidder_validate_bids_test.go b/exchange/bidder_validate_bids_test.go index 364726c2a45..2a8f5658b35 100644 --- a/exchange/bidder_validate_bids_test.go +++ b/exchange/bidder_validate_bids_test.go @@ -4,16 +4,17 @@ import ( "context" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/currency" + "github.com/prebid/prebid-server/experiment/adscert" "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) func TestAllValidBids(t *testing.T) { var bidder AdaptedBidder = addValidatedBidderMiddleware(&mockAdaptedBidder{ - bidResponse: &pbsOrtbSeatBid{ + bidResponse: []*pbsOrtbSeatBid{{ bids: []*pbsOrtbBid{ { bid: &openrtb2.Bid{ @@ -50,19 +51,27 @@ func TestAllValidBids(t *testing.T) { }, }, }, - }) + }}) bidderReq := BidderRequest{ BidRequest: &openrtb2.BidRequest{}, BidderName: openrtb_ext.BidderAppnexus, } - seatBid, errs := bidder.requestBid(context.Background(), bidderReq, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false) - assert.Len(t, seatBid.bids, 4) + bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 1.0} + bidReqOptions := bidRequestOptions{ + accountDebugAllowed: true, + headerDebugAllowed: false, + addCallSignHeader: false, + bidAdjustments: bidAdjustments, + } + seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}) + assert.Len(t, seatBids, 1) + assert.Len(t, seatBids[0].bids, 4) assert.Len(t, errs, 0) } func TestAllBadBids(t *testing.T) { bidder := addValidatedBidderMiddleware(&mockAdaptedBidder{ - bidResponse: &pbsOrtbSeatBid{ + bidResponse: []*pbsOrtbSeatBid{{ bids: []*pbsOrtbBid{ { bid: &openrtb2.Bid{ @@ -112,19 +121,27 @@ func TestAllBadBids(t *testing.T) { {}, }, }, - }) + }}) bidderReq := BidderRequest{ BidRequest: &openrtb2.BidRequest{}, BidderName: openrtb_ext.BidderAppnexus, } - seatBid, errs := bidder.requestBid(context.Background(), bidderReq, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false) - assert.Len(t, seatBid.bids, 0) + bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 1.0} + bidReqOptions := bidRequestOptions{ + accountDebugAllowed: true, + headerDebugAllowed: false, + addCallSignHeader: false, + bidAdjustments: bidAdjustments, + } + seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}) + assert.Len(t, seatBids, 1) + assert.Len(t, seatBids[0].bids, 0) assert.Len(t, errs, 7) } func TestMixedBids(t *testing.T) { bidder := addValidatedBidderMiddleware(&mockAdaptedBidder{ - bidResponse: &pbsOrtbSeatBid{ + bidResponse: []*pbsOrtbSeatBid{{ bids: []*pbsOrtbBid{ { bid: &openrtb2.Bid{ @@ -185,13 +202,21 @@ func TestMixedBids(t *testing.T) { {}, }, }, - }) + }}) bidderReq := BidderRequest{ BidRequest: &openrtb2.BidRequest{}, BidderName: openrtb_ext.BidderAppnexus, } - seatBid, errs := bidder.requestBid(context.Background(), bidderReq, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false) - assert.Len(t, seatBid.bids, 3) + bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 1.0} + bidReqOptions := bidRequestOptions{ + accountDebugAllowed: true, + headerDebugAllowed: false, + addCallSignHeader: false, + bidAdjustments: bidAdjustments, + } + seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}) + assert.Len(t, seatBids, 1) + assert.Len(t, seatBids[0].bids, 3) assert.Len(t, errs, 5) } @@ -291,11 +316,11 @@ func TestCurrencyBids(t *testing.T) { }, } bidder := addValidatedBidderMiddleware(&mockAdaptedBidder{ - bidResponse: &pbsOrtbSeatBid{ + bidResponse: []*pbsOrtbSeatBid{{ currency: tc.brpCur, bids: bids, }, - }) + }}) expectedValidBids := len(bids) expectedErrs := 0 @@ -311,17 +336,25 @@ func TestCurrencyBids(t *testing.T) { } bidderRequest := BidderRequest{BidRequest: request, BidderName: openrtb_ext.BidderAppnexus} - seatBid, errs := bidder.requestBid(context.Background(), bidderRequest, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false) - assert.Len(t, seatBid.bids, expectedValidBids) + bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 1.0} + bidReqOptions := bidRequestOptions{ + accountDebugAllowed: true, + headerDebugAllowed: false, + addCallSignHeader: false, + bidAdjustments: bidAdjustments, + } + seatBids, errs := bidder.requestBid(context.Background(), bidderRequest, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}) + assert.Len(t, seatBids, 1) + assert.Len(t, seatBids[0].bids, expectedValidBids) assert.Len(t, errs, expectedErrs) } } type mockAdaptedBidder struct { - bidResponse *pbsOrtbSeatBid + bidResponse []*pbsOrtbSeatBid errorResponse []error } -func (b *mockAdaptedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error) { +func (b *mockAdaptedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestMetadata bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes) ([]*pbsOrtbSeatBid, []error) { return b.bidResponse, b.errorResponse } diff --git a/exchange/events_test.go b/exchange/events_test.go index 887122a687e..922624e86f4 100644 --- a/exchange/events_test.go +++ b/exchange/events_test.go @@ -3,7 +3,7 @@ package exchange import ( "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/exchange/exchange.go b/exchange/exchange.go index d2362107e57..3e5fb1f5b82 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -18,8 +18,10 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/experiment/adscert" "github.com/prebid/prebid-server/firstpartydata" "github.com/prebid/prebid-server/gdpr" + "github.com/prebid/prebid-server/hooks/hookexecution" "github.com/prebid/prebid-server/metrics" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/prebid_cache_client" @@ -31,7 +33,8 @@ import ( "github.com/buger/jsonparser" "github.com/gofrs/uuid" "github.com/golang/glog" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" + "github.com/prebid/openrtb/v17/openrtb3" ) type extCacheInstructions struct { @@ -57,14 +60,17 @@ type exchange struct { me metrics.MetricsEngine cache prebid_cache_client.Client cacheTime time.Duration - vendorListFetcher gdpr.VendorListFetcher + gdprPermsBuilder gdpr.PermissionsBuilder + tcf2ConfigBuilder gdpr.TCF2ConfigBuilder currencyConverter *currency.RateConverter externalURL string gdprDefaultValue gdpr.Signal privacyConfig config.Privacy categoriesFetcher stored_requests.CategoryFetcher bidIDGenerator BidIDGenerator - gvlVendorIDs map[openrtb_ext.BidderName]uint16 + hostSChainNode *openrtb2.SupplyChainNode + adsCertSigner adscert.Signer + server config.Server } // Container to pass out response ext data from the GetAllBids goroutines back into the main thread @@ -78,9 +84,9 @@ type seatResponseExtra struct { } type bidResponseWrapper struct { - adapterBids *pbsOrtbSeatBid - adapterExtra *seatResponseExtra - bidder openrtb_ext.BidderName + adapterSeatBids []*pbsOrtbSeatBid + adapterExtra *seatResponseExtra + bidder openrtb_ext.BidderName } type BidIDGenerator interface { @@ -111,7 +117,7 @@ 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, syncersByBidder map[string]usersync.Syncer, metricsEngine metrics.MetricsEngine, infos config.BidderInfos, vendorListFetcher gdpr.VendorListFetcher, 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, gdprPermsBuilder gdpr.PermissionsBuilder, tcf2CfgBuilder gdpr.TCF2ConfigBuilder, currencyConverter *currency.RateConverter, categoriesFetcher stored_requests.CategoryFetcher, adsCertSigner adscert.Signer) Exchange { bidderToSyncerKey := map[string]string{} for bidder, syncer := range syncersByBidder { bidderToSyncerKey[bidder] = syncer.Key() @@ -131,7 +137,8 @@ func NewExchange(adapters map[openrtb_ext.BidderName]AdaptedBidder, cache prebid categoriesFetcher: categoriesFetcher, currencyConverter: currencyConverter, externalURL: cfg.ExternalURL, - vendorListFetcher: vendorListFetcher, + gdprPermsBuilder: gdprPermsBuilder, + tcf2ConfigBuilder: tcf2CfgBuilder, me: metricsEngine, gdprDefaultValue: gdprDefaultValue, privacyConfig: config.Privacy{ @@ -140,13 +147,16 @@ func NewExchange(adapters map[openrtb_ext.BidderName]AdaptedBidder, cache prebid LMT: cfg.LMT, }, bidIDGenerator: &bidIDGenerator{cfg.GenerateBidID}, - gvlVendorIDs: infos.ToGVLVendorIDMap(), + hostSChainNode: cfg.HostSChainNode, + adsCertSigner: adsCertSigner, + server: config.Server{ExternalUrl: cfg.ExternalURL, GvlID: cfg.GDPR.HostVendorID, DataCenter: cfg.DataCenter}, } } type ImpExtInfo struct { EchoVideoAttrs bool StoredImp []byte + Passthrough json.RawMessage } // AuctionRequest holds the bid request for the auction @@ -168,10 +178,11 @@ type AuctionRequest struct { FirstPartyData map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData // map of imp id to stored response StoredAuctionResponses stored_responses.ImpsWithBidResponses - TCF2ConfigBuilder gdpr.TCF2ConfigBuilder - GDPRPermissionsBuilder gdpr.PermissionsBuilder // map of imp id to bidder to stored response - StoredBidResponses stored_responses.ImpBidderStoredResp + StoredBidResponses stored_responses.ImpBidderStoredResp + BidderImpReplaceImpID stored_responses.BidderImpReplaceImpID + PubID string + HookExecutor hookexecution.StageExecutor } // BidderRequest holds the bidder specific request and all other @@ -182,9 +193,15 @@ type BidderRequest struct { BidderCoreName openrtb_ext.BidderName BidderLabels metrics.AdapterLabels BidderStoredResponses map[string]json.RawMessage + ImpReplaceImpId map[string]bool } func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *DebugLog) (*openrtb2.BidResponse, error) { + reject := r.HookExecutor.ExecuteProcessedAuctionStage(r.BidRequestWrapper.BidRequest) + if reject != nil { + return nil, reject + } + var errs []error // rebuild/resync the request in the request wrapper. if err := r.BidRequestWrapper.RebuildRequest(); err != nil { @@ -194,6 +211,9 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * if err != nil { return nil, err } + if !e.server.Empty() { + requestExt.Prebid.Server = &openrtb_ext.ExtRequestPrebidServer{ExternalUrl: e.server.ExternalUrl, GvlID: e.server.GvlID, DataCenter: e.server.DataCenter} + } cacheInstructions := getExtCacheInstructions(requestExt) targData := getExtTargetData(requestExt, &cacheInstructions) @@ -209,6 +229,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * } r.ResolvedBidRequest = resolvedBidReq } + e.me.RecordDebugRequest(responseDebugAllow || accountDebugAllow, r.PubID) if r.RequestType == metrics.ReqTypeORTB2Web || r.RequestType == metrics.ReqTypeORTB2App { //Extract First party data for auction endpoint only @@ -244,12 +265,14 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * gdprDefaultValue := e.parseGDPRDefaultValue(r.BidRequestWrapper.BidRequest) // Slice of BidRequests, each a copy of the original cleaned to only contain bidder data for the named bidder - tcf2Cfg := r.TCF2ConfigBuilder(e.privacyConfig.GDPR.TCF2, r.Account.GDPR) - gdprPerms := r.GDPRPermissionsBuilder(e.privacyConfig.GDPR, tcf2Cfg, e.gvlVendorIDs, e.vendorListFetcher) - bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(ctx, r, requestExt, e.bidderToSyncerKey, e.me, gdprDefaultValue, gdprPerms, e.privacyConfig, tcf2Cfg) + bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(ctx, r, requestExt, e.bidderToSyncerKey, e.me, gdprDefaultValue, e.privacyConfig, e.gdprPermsBuilder, e.tcf2ConfigBuilder, e.hostSChainNode) e.me.RecordRequestPrivacy(privacyLabels) + if len(r.StoredAuctionResponses) > 0 || len(r.StoredBidResponses) > 0 { + e.me.RecordStoredResponse(r.PubID) + } + // If we need to cache bids, then it will take some time to call prebid cache. // We should reduce the amount of time the bidders have, to compensate. auctionCtx, cancel := e.makeAuctionContext(ctx, cacheInstructions.cacheBids) @@ -275,7 +298,16 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * } else { // List of bidders we have requests for. liveAdapters = listBiddersWithRequests(bidderRequests) - adapterBids, adapterExtra, anyBidsReturned = e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, accountDebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride) + + //This will be used to validate bids + var alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes + if requestExt != nil && requestExt.Prebid.AlternateBidderCodes != nil { + alternateBidderCodes = *requestExt.Prebid.AlternateBidderCodes + } else if r.Account.AlternateBidderCodes != nil { + alternateBidderCodes = *r.Account.AlternateBidderCodes + } + + adapterBids, adapterExtra, anyBidsReturned = e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, accountDebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride, alternateBidderCodes, requestExt.Prebid.Experiment) } var auc *auction @@ -312,7 +344,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * if targData != nil { // A non-nil auction is only needed if targeting is active. (It is used below this block to extract cache keys) - auc = newAuction(adapterBids, len(r.BidRequestWrapper.BidRequest.Imp), targData.preferDeals) + auc = newAuction(adapterBids, len(r.BidRequestWrapper.Imp), targData.preferDeals) auc.setRoundedPrices(targData.priceGranularity) if requestExt.Prebid.SupportDeals { @@ -320,7 +352,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * errs = append(errs, dealErrs...) } - bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, r, responseDebugAllow, errs) + bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, r, responseDebugAllow, requestExt.Prebid.Passthrough, errs) if debugLog.DebugEnabledOrOverridden { if bidRespExtBytes, err := json.Marshal(bidResponseExt); err == nil { debugLog.Data.Response = string(bidRespExtBytes) @@ -338,9 +370,9 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * targData.setTargeting(auc, r.BidRequestWrapper.BidRequest.App != nil, bidCategory, r.Account.TruncateTargetAttribute) } - bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, r, responseDebugAllow, errs) + bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, r, responseDebugAllow, requestExt.Prebid.Passthrough, errs) } else { - bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, r, responseDebugAllow, errs) + bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, r, responseDebugAllow, requestExt.Prebid.Passthrough, errs) if debugLog.DebugEnabledOrOverridden { @@ -482,7 +514,9 @@ func (e *exchange) getAllBids( conversions currency.Conversions, accountDebugAllowed bool, globalPrivacyControlHeader string, - headerDebugAllowed bool) ( + headerDebugAllowed bool, + alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, + experiment *openrtb_ext.Experiment) ( map[openrtb_ext.BidderName]*pbsOrtbSeatBid, map[openrtb_ext.BidderName]*seatResponseExtra, bool) { // Set up pointers to the bid results @@ -507,39 +541,43 @@ func (e *exchange) getAllBids( }() start := time.Now() - adjustmentFactor := 1.0 - if givenAdjustment, ok := bidAdjustments[string(bidderRequest.BidderName)]; ok { - adjustmentFactor = givenAdjustment - } reqInfo := adapters.NewExtraRequestInfo(conversions) reqInfo.PbsEntryPoint = bidderRequest.BidderLabels.RType reqInfo.GlobalPrivacyControlHeader = globalPrivacyControlHeader - bids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest, adjustmentFactor, conversions, &reqInfo, accountDebugAllowed, headerDebugAllowed) + bidReqOptions := bidRequestOptions{ + accountDebugAllowed: accountDebugAllowed, + headerDebugAllowed: headerDebugAllowed, + addCallSignHeader: isAdsCertEnabled(experiment, e.bidderInfo[string(bidderRequest.BidderName)]), + bidAdjustments: bidAdjustments, + } + seatBids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest, conversions, &reqInfo, e.adsCertSigner, bidReqOptions, alternateBidderCodes) // Add in time reporting elapsed := time.Since(start) - brw.adapterBids = bids + brw.adapterSeatBids = seatBids // Structure to record extra tracking data generated during bidding ae := new(seatResponseExtra) ae.ResponseTimeMillis = int(elapsed / time.Millisecond) - if bids != nil { - ae.HttpCalls = bids.httpCalls + if len(seatBids) != 0 { + ae.HttpCalls = seatBids[0].httpCalls } // Timing statistics e.me.RecordAdapterTime(bidderRequest.BidderLabels, time.Since(start)) - bidderRequest.BidderLabels.AdapterBids = bidsToMetric(brw.adapterBids) + bidderRequest.BidderLabels.AdapterBids = bidsToMetric(brw.adapterSeatBids) bidderRequest.BidderLabels.AdapterErrors = errorsToMetric(err) // Append any bid validation errors to the error list ae.Errors = errsToBidderErrors(err) ae.Warnings = errsToBidderWarnings(err) brw.adapterExtra = ae - if bids != nil { - for _, bid := range bids.bids { - var cpm = float64(bid.bid.Price * 1000) - e.me.RecordAdapterPrice(bidderRequest.BidderLabels, cpm) - e.me.RecordAdapterBidReceived(bidderRequest.BidderLabels, bid.bidType, bid.bid.AdM != "") + for _, seatBid := range seatBids { + if seatBid != nil { + for _, bid := range seatBid.bids { + var cpm = float64(bid.bid.Price * 1000) + e.me.RecordAdapterPrice(bidderRequest.BidderLabels, cpm) + e.me.RecordAdapterBidReceived(bidderRequest.BidderLabels, bid.bidType, bid.bid.AdM != "") + } } } chBids <- brw @@ -551,8 +589,14 @@ func (e *exchange) getAllBids( brw := <-chBids //if bidder returned no bids back - remove bidder from further processing - if brw.adapterBids != nil && len(brw.adapterBids.bids) != 0 { - adapterBids[brw.bidder] = brw.adapterBids + for _, seatBid := range brw.adapterSeatBids { + if seatBid != nil && len(seatBid.bids) != 0 { + if _, ok := adapterBids[openrtb_ext.BidderName(seatBid.seat)]; ok { + adapterBids[openrtb_ext.BidderName(seatBid.seat)].bids = append(adapterBids[openrtb_ext.BidderName(seatBid.seat)].bids, seatBid.bids...) + } else { + adapterBids[openrtb_ext.BidderName(seatBid.seat)] = seatBid + } + } } //but we need to add all bidders data to adapterExtra to have metrics and other metadata adapterExtra[brw.bidder] = brw.adapterExtra @@ -596,11 +640,13 @@ func (e *exchange) recoverSafely(bidderRequests []BidderRequest, } } -func bidsToMetric(bids *pbsOrtbSeatBid) metrics.AdapterBid { - if bids == nil || len(bids.bids) == 0 { - return metrics.AdapterBidNone +func bidsToMetric(seatBids []*pbsOrtbSeatBid) metrics.AdapterBid { + for _, seatBid := range seatBids { + if seatBid != nil && len(seatBid.bids) != 0 { + return metrics.AdapterBidPresent + } } - return metrics.AdapterBidPresent + return metrics.AdapterBidNone } func errorsToMetric(errs []error) map[metrics.AdapterError]struct{} { @@ -619,6 +665,8 @@ func errorsToMetric(errs []error) map[metrics.AdapterError]struct{} { ret[metrics.AdapterErrorBadServerResponse] = s case errortypes.FailedToRequestBidsErrorCode: ret[metrics.AdapterErrorFailedToRequestBids] = s + case errortypes.AlternateBidderCodeWarningCode: + ret[metrics.AdapterErrorValidation] = s default: ret[metrics.AdapterErrorUnknown] = s } @@ -652,25 +700,25 @@ 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, impExtInfoMap map[string]ImpExtInfo, errList []error) (*openrtb2.BidResponse, error) { +func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_ext.BidderName, adapterSeatBids 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 bidResponse.ID = bidRequest.ID if len(liveAdapters) == 0 { // signal "Invalid Request" if no valid bidders. - bidResponse.NBR = openrtb2.NoBidReasonCode.Ptr(openrtb2.NoBidReasonCodeInvalidRequest) + bidResponse.NBR = openrtb3.NoBidInvalidRequest.Ptr() } // Create the SeatBids. We use a zero sized slice so that we can append non-zero seat bids, and not include seatBid // objects for seatBids without any bids. Preallocate the max possible size to avoid reallocating the array as we go. seatBids := make([]openrtb2.SeatBid, 0, len(liveAdapters)) - for _, a := range liveAdapters { + for a, adapterSeatBids := range adapterSeatBids { //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, impExtInfoMap) + if adapterSeatBids != nil && len(adapterSeatBids.bids) > 0 { + sb := e.makeSeatBid(adapterSeatBids, a, adapterExtra, auc, returnCreative, impExtInfoMap) seatBids = append(seatBids, *sb) - bidResponse.Cur = adapterBids[a].currency + bidResponse.Cur = adapterSeatBids.currency } } @@ -917,8 +965,7 @@ func getPrimaryAdServer(adServerId int) (string, error) { } // Extract all the data from the SeatBids and build the ExtBidResponse -func (e *exchange) makeExtBidResponse(adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, r AuctionRequest, debugInfo bool, errList []error) *openrtb_ext.ExtBidResponse { - +func (e *exchange) makeExtBidResponse(adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, r AuctionRequest, debugInfo bool, passthrough json.RawMessage, errList []error) *openrtb_ext.ExtBidResponse { bidResponseExt := &openrtb_ext.ExtBidResponse{ Errors: make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage, len(adapterBids)), Warnings: make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage, len(adapterBids)), @@ -938,6 +985,13 @@ func (e *exchange) makeExtBidResponse(adapterBids map[openrtb_ext.BidderName]*pb } } + if passthrough != nil { + if bidResponseExt.Prebid == nil { + bidResponseExt.Prebid = &openrtb_ext.ExtResponsePrebid{} + } + bidResponseExt.Prebid.Passthrough = passthrough + } + for bidderName, responseExtra := range adapterExtra { if debugInfo && len(responseExtra.HttpCalls) > 0 { @@ -1046,20 +1100,22 @@ func makeBidExtJSON(ext json.RawMessage, prebid *openrtb_ext.ExtBidPrebid, impEx } 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 - } - //handler for case where EchoVideoAttrs is true, but video data is not found - if len(videoData) > 0 { - extMap[openrtb_ext.StoredRequestAttributes] = json.RawMessage(videoData) + // ext.prebid.storedrequestattributes and ext.prebid.passthrough + if impExtInfo, ok := impExtInfoMap[impId]; ok { + prebid.Passthrough = impExtInfoMap[impId].Passthrough + if 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) + } } } - + extMap[openrtb_ext.PrebidExtKey] = prebid return json.Marshal(extMap) } @@ -1178,7 +1234,7 @@ func buildStoredAuctionResponse(storedAuctionResponses map[string]json.RawMessag } else { //create new seat bid and add it to live adapters liveAdapters = append(liveAdapters, bidderName) - newSeatBid := pbsOrtbSeatBid{bidsToAdd, "", nil} + newSeatBid := pbsOrtbSeatBid{bidsToAdd, "", nil, ""} adapterBids[bidderName] = &newSeatBid } @@ -1187,3 +1243,9 @@ func buildStoredAuctionResponse(storedAuctionResponses map[string]json.RawMessag return adapterBids, liveAdapters, nil } + +func isAdsCertEnabled(experiment *openrtb_ext.Experiment, info config.BidderInfo) bool { + requestAdsCertEnabled := experiment != nil && experiment.AdsCert != nil && experiment.AdsCert.Enabled + bidderAdsCertEnabled := info.Experiment.AdsCert.Enabled + return requestAdsCertEnabled && bidderAdsCertEnabled +} diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index e06ec9d2d95..a1c70ee38f7 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -6,18 +6,21 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" + "io" "net/http" "net/http/httptest" + "os" + "reflect" "regexp" + "sort" "strconv" "strings" "testing" "time" "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/go-gdpr/vendorlist" + "github.com/prebid/openrtb/v17/openrtb2" + "github.com/prebid/prebid-server/hooks/hookexecution" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" jsonpatch "gopkg.in/evanphx/json-patch.v4" @@ -26,6 +29,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/experiment/adscert" "github.com/prebid/prebid-server/gdpr" "github.com/prebid/prebid-server/metrics" metricsConf "github.com/prebid/prebid-server/metrics/config" @@ -49,13 +53,12 @@ func TestNewExchange(t *testing.T) { CacheURL: config.Cache{ ExpectedTimeMillis: 20, }, - Adapters: blankAdapterConfig(knownAdapters), GDPR: config.GDPR{ EEACountries: []string{"FIN", "FRA", "GUF"}, }, } - biddersInfo, err := config.LoadBidderInfoFromDisk("../static/bidder-info", cfg.Adapters, openrtb_ext.BuildBidderStringSlice()) + biddersInfo, err := config.LoadBidderInfoFromDisk("../static/bidder-info") if err != nil { t.Fatal(err) } @@ -66,11 +69,22 @@ func TestNewExchange(t *testing.T) { } currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - vendorListFetcher := func(ctx context.Context, id uint16) (vendorlist.VendorList, error) { return nil, nil } - e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, vendorListFetcher, currencyConverter, nilCategoryFetcher{}).(*exchange) + + gdprPermsBuilder := fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + }, + }.Builder + tcf2ConfigBuilder := fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + }.Builder + + e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, tcf2ConfigBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}).(*exchange) for _, bidderName := range knownAdapters { if _, ok := e.adapterMap[bidderName]; !ok { - t.Errorf("NewExchange produced an Exchange without bidder %s", bidderName) + if biddersInfo[string(bidderName)].IsEnabled() { + t.Errorf("NewExchange produced an Exchange without bidder %s", bidderName) + } } } if e.cacheTime != time.Duration(cfg.CacheURL.ExpectedTimeMillis)*time.Millisecond { @@ -81,33 +95,26 @@ func TestNewExchange(t *testing.T) { // The objective is to get to execute e.buildBidResponse(ctx.Background(), liveA... ) (*openrtb2.BidResponse, error) // and check whether the returned request successfully prints any '&' characters as it should // To do so, we: -// 1) Write the endpoint adapter URL with an '&' character into a new config,Configuration struct -// as specified in https://github.com/prebid/prebid-server/issues/465 -// 2) Initialize a new exchange with said configuration -// 3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs including the -// sample request as specified in https://github.com/prebid/prebid-server/issues/465 -// 4) Build a BidResponse struct using exchange.buildBidResponse(ctx.Background(), liveA... ) -// 5) Assert we have no '&' characters in the response that exchange.buildBidResponse returns +// 1. Write the endpoint adapter URL with an '&' character into a new config,Configuration struct +// as specified in https://github.com/prebid/prebid-server/issues/465 +// 2. Initialize a new exchange with said configuration +// 3. Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs including the +// sample request as specified in https://github.com/prebid/prebid-server/issues/465 +// 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 - cfg := &config.Configuration{ - Adapters: make(map[string]config.Adapter, 1), - } - cfg.Adapters["appnexus"] = config.Adapter{ - Endpoint: "http://ib.adnxs.com/openrtb2?query1&query2", //Note the '&' character in there - } + cfg := &config.Configuration{} + biddersInfo := config.BidderInfos{"appnexus": config.BidderInfo{Endpoint: "http://ib.adnxs.com/openrtb2?query1&query2"}} //Note the '&' character in there // 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)) - defer server.Close() - biddersInfo, err := config.LoadBidderInfoFromDisk("../static/bidder-info", cfg.Adapters, openrtb_ext.BuildBidderStringSlice()) - if err != nil { - t.Fatal(err) - } + defer server.Close() adapters, adaptersErr := BuildAdapters(server.Client(), cfg, biddersInfo, &metricsConf.NilMetricsEngine{}) if adaptersErr != nil { @@ -115,8 +122,17 @@ func TestCharacterEscape(t *testing.T) { } currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - vendorListFetcher := func(ctx context.Context, id uint16) (vendorlist.VendorList, error) { return nil, nil } - e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, vendorListFetcher, currencyConverter, nilCategoryFetcher{}).(*exchange) + + gdprPermsBuilder := fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + }, + }.Builder + tcf2ConfigBuilder := fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + }.Builder + + e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, tcf2ConfigBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}).(*exchange) // 3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs //liveAdapters []openrtb_ext.BidderName, @@ -289,7 +305,7 @@ func TestDebugBehaviour(t *testing.T) { 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}}`), + Ext: json.RawMessage(`{"prebid":{"bidder":{"appnexus": {"placementId": 1}}}}`), }}, Site: &openrtb2.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)}, Device: &openrtb2.Device{UA: "curl/7.54.0", IP: "::1"}, @@ -311,7 +327,14 @@ func TestDebugBehaviour(t *testing.T) { e.cache = &wellBehavedCache{} e.me = &metricsConf.NilMetricsEngine{} - e.vendorListFetcher = gdpr.NewVendorListFetcher(context.Background(), config.GDPR{}, &http.Client{}, gdpr.VendorListURLMaker) + e.gdprPermsBuilder = fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + }, + }.Builder + e.tcf2ConfigBuilder = fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + }.Builder e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) e.categoriesFetcher = categoriesFetcher @@ -321,7 +344,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.NilMetricsEngine{}, 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 @@ -333,12 +356,11 @@ func TestDebugBehaviour(t *testing.T) { } auctionRequest := AuctionRequest{ - BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: bidRequest}, - Account: config.Account{DebugAllow: test.debugData.accountLevelDebugAllowed}, - UserSyncs: &emptyUsersync{}, - StartTime: time.Now(), - GDPRPermissionsBuilder: mockGDPRPermissionsBuilder, - TCF2ConfigBuilder: mockTCF2ConfigBuilder, + BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: bidRequest}, + Account: config.Account{DebugAllow: test.debugData.accountLevelDebugAllowed}, + UserSyncs: &emptyUsersync{}, + StartTime: time.Now(), + HookExecutor: &hookexecution.EmptyHookExecutor{}, } if test.generateWarnings { var errL []error @@ -461,7 +483,7 @@ func TestTwoBiddersDebugDisabledAndEnabled(t *testing.T) { httpRequest: &adapters.RequestData{ Method: "POST", Uri: server.URL, - Body: []byte("{\"key\":\"val\"}"), + Body: []byte(`{"key":"val"}`), Headers: http.Header{}, }, bidResponse: &adapters.BidderResponse{}, @@ -470,7 +492,14 @@ func TestTwoBiddersDebugDisabledAndEnabled(t *testing.T) { e := new(exchange) e.cache = &wellBehavedCache{} e.me = &metricsConf.NilMetricsEngine{} - e.vendorListFetcher = gdpr.NewVendorListFetcher(context.Background(), config.GDPR{}, &http.Client{}, gdpr.VendorListURLMaker) + e.gdprPermsBuilder = fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + }, + }.Builder + e.tcf2ConfigBuilder = fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + }.Builder e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) e.categoriesFetcher = categoriesFetcher @@ -482,7 +511,7 @@ func TestTwoBiddersDebugDisabledAndEnabled(t *testing.T) { Imp: []openrtb2.Imp{{ ID: "some-impression-id", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 300, H: 600}}}, - Ext: json.RawMessage(`{"telaria": {"placementId": 1}, "appnexus": {"placementid": 2}}`), + Ext: json.RawMessage(`{"prebid":{"bidder":{"telaria": {"placementId": 1}, "appnexus": {"placementid": 2}}}}`), }}, Site: &openrtb2.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)}, Device: &openrtb2.Device{UA: "curl/7.54.0", IP: "::1"}, @@ -493,17 +522,16 @@ func TestTwoBiddersDebugDisabledAndEnabled(t *testing.T) { bidRequest.Ext = json.RawMessage(`{"prebid":{"debug":true}}`) auctionRequest := AuctionRequest{ - BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: bidRequest}, - Account: config.Account{DebugAllow: true}, - UserSyncs: &emptyUsersync{}, - StartTime: time.Now(), - GDPRPermissionsBuilder: mockGDPRPermissionsBuilder, - TCF2ConfigBuilder: mockTCF2ConfigBuilder, + BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: bidRequest}, + Account: config.Account{DebugAllow: true}, + UserSyncs: &emptyUsersync{}, + StartTime: time.Now(), + HookExecutor: &hookexecution.EmptyHookExecutor{}, } e.adapterMap = map[openrtb_ext.BidderName]AdaptedBidder{ - 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}), + 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) @@ -536,12 +564,10 @@ func TestTwoBiddersDebugDisabledAndEnabled(t *testing.T) { if !testCase.bidder2DebugEnabled { assert.Nil(t, actualExt.Debug.HttpCalls["telaria"], "ext.debug.resolvedrequest field is expected to be included in this outBidResponse.Ext and not be nil") } - if testCase.bidder1DebugEnabled && testCase.bidder2DebugEnabled { assert.Equal(t, 2, len(actualExt.Debug.HttpCalls), "With bidder level debug enable option for both bidders http calls should have 2 elements") } } - } func TestOverrideWithCustomCurrency(t *testing.T) { @@ -628,7 +654,14 @@ func TestOverrideWithCustomCurrency(t *testing.T) { e := new(exchange) e.cache = &wellBehavedCache{} e.me = &metricsConf.NilMetricsEngine{} - e.vendorListFetcher = gdpr.NewVendorListFetcher(context.Background(), config.GDPR{}, &http.Client{}, gdpr.VendorListURLMaker) + e.gdprPermsBuilder = fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + }, + }.Builder + e.tcf2ConfigBuilder = fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + }.Builder e.currencyConverter = mockCurrencyConverter e.categoriesFetcher = categoriesFetcher e.bidIDGenerator = &mockBidIDGenerator{false, false} @@ -639,7 +672,7 @@ func TestOverrideWithCustomCurrency(t *testing.T) { 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}}`), + Ext: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placementId":1}}}}`), }}, Site: &openrtb2.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)}, } @@ -657,7 +690,7 @@ func TestOverrideWithCustomCurrency(t *testing.T) { } e.adapterMap = map[openrtb_ext.BidderName]AdaptedBidder{ - openrtb_ext.BidderAppnexus: AdaptBidder(oneDollarBidBidder, mockAppnexusBidService.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil), + openrtb_ext.BidderAppnexus: AdaptBidder(oneDollarBidBidder, mockAppnexusBidService.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, ""), } // Set custom rates in extension @@ -667,11 +700,10 @@ func TestOverrideWithCustomCurrency(t *testing.T) { mockBidRequest.Cur = []string{test.in.bidRequestCurrency} auctionRequest := AuctionRequest{ - BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: mockBidRequest}, - Account: config.Account{}, - UserSyncs: &emptyUsersync{}, - GDPRPermissionsBuilder: mockGDPRPermissionsBuilder, - TCF2ConfigBuilder: mockTCF2ConfigBuilder, + BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: mockBidRequest}, + Account: config.Account{}, + UserSyncs: &emptyUsersync{}, + HookExecutor: &hookexecution.EmptyHookExecutor{}, } // Run test @@ -720,18 +752,23 @@ func TestAdapterCurrency(t *testing.T) { mockBidder := &mockBidder{} mockBidder.On("MakeRequests", mock.Anything, mock.Anything).Return([]*adapters.RequestData(nil), []error(nil)) - vendorListFetcher := gdpr.NewVendorListFetcher(context.Background(), config.GDPR{}, &http.Client{}, gdpr.VendorListURLMaker) - // Initialize Real Exchange e := exchange{ - cache: &wellBehavedCache{}, - me: &metricsConf.NilMetricsEngine{}, - vendorListFetcher: vendorListFetcher, + cache: &wellBehavedCache{}, + me: &metricsConf.NilMetricsEngine{}, + gdprPermsBuilder: fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + }, + }.Builder, + tcf2ConfigBuilder: fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + }.Builder, currencyConverter: currencyConverter, categoriesFetcher: nilCategoryFetcher{}, bidIDGenerator: &mockBidIDGenerator{false, false}, adapterMap: map[openrtb_ext.BidderName]AdaptedBidder{ - openrtb_ext.BidderName("foo"): AdaptBidder(mockBidder, nil, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderName("foo"), nil), + openrtb_ext.BidderName("foo"): AdaptBidder(mockBidder, nil, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderName("foo"), nil, ""), }, } @@ -741,7 +778,7 @@ func TestAdapterCurrency(t *testing.T) { 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}}`), + Ext: json.RawMessage(`{"prebid":{"bidder":{"foo":{"placementId":1}}}}`), }}, Site: &openrtb2.Site{ Page: "prebid.org", @@ -753,11 +790,10 @@ func TestAdapterCurrency(t *testing.T) { // Run Auction auctionRequest := AuctionRequest{ - BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: request}, - Account: config.Account{}, - UserSyncs: &emptyUsersync{}, - GDPRPermissionsBuilder: mockGDPRPermissionsBuilder, - TCF2ConfigBuilder: mockTCF2ConfigBuilder, + BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: request}, + Account: config.Account{}, + UserSyncs: &emptyUsersync{}, + HookExecutor: &hookexecution.EmptyHookExecutor{}, } response, err := e.HoldAuction(context.Background(), auctionRequest, &DebugLog{}) assert.NoError(t, err) @@ -990,20 +1026,6 @@ func TestReturnCreativeEndToEnd(t *testing.T) { testCases []aTest expectError bool }{ - { - groupDesc: "Invalid or malformed bidRequest Ext, expect error in these scenarios", - testCases: []aTest{ - { - desc: "Malformed ext in bidRequest", - inExt: json.RawMessage(`malformed`), - }, - { - desc: "empty cache field", - inExt: json.RawMessage(`{"prebid":{"cache":{}}}`), - }, - }, - expectError: true, - }, { groupDesc: "Valid bidRequest Ext but no returnCreative value specified, default to returning creative", testCases: []aTest{ @@ -1114,11 +1136,18 @@ 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.NilMetricsEngine{}, 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.NilMetricsEngine{} - e.vendorListFetcher = gdpr.NewVendorListFetcher(context.Background(), config.GDPR{}, &http.Client{}, gdpr.VendorListURLMaker) + e.gdprPermsBuilder = fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + }, + }.Builder + e.tcf2ConfigBuilder = fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + }.Builder e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) e.categoriesFetcher = categoriesFetcher e.bidIDGenerator = &mockBidIDGenerator{false, false} @@ -1129,7 +1158,7 @@ func TestReturnCreativeEndToEnd(t *testing.T) { 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}}`), + Ext: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placementId":1}}}}`), }}, Site: &openrtb2.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)}, } @@ -1140,11 +1169,10 @@ func TestReturnCreativeEndToEnd(t *testing.T) { mockBidRequest.Ext = test.inExt auctionRequest := AuctionRequest{ - BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: mockBidRequest}, - Account: config.Account{}, - UserSyncs: &emptyUsersync{}, - GDPRPermissionsBuilder: mockGDPRPermissionsBuilder, - TCF2ConfigBuilder: mockTCF2ConfigBuilder, + BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: mockBidRequest}, + Account: config.Account{}, + UserSyncs: &emptyUsersync{}, + HookExecutor: &hookexecution.EmptyHookExecutor{}, } // Run test @@ -1184,11 +1212,6 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { bidderName := openrtb_ext.BidderName("appnexus") cfg := &config.Configuration{ - Adapters: map[string]config.Adapter{ - string(bidderName): { - Endpoint: "http://ib.adnxs.com/endpoint", - }, - }, CacheURL: config.Cache{ Host: "www.internalprebidcache.net", }, @@ -1201,13 +1224,14 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { adapterList := make([]openrtb_ext.BidderName, 0, 2) syncerKeys := []string{} - testEngine := metricsConf.NewMetricsEngine(cfg, adapterList, syncerKeys) + var moduleStageNames map[string][]string + testEngine := metricsConf.NewMetricsEngine(cfg, adapterList, syncerKeys, moduleStageNames) // 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() - biddersInfo, err := config.LoadBidderInfoFromDisk("../static/bidder-info", cfg.Adapters, openrtb_ext.BuildBidderStringSlice()) + biddersInfo, err := config.LoadBidderInfoFromDisk("../static/bidder-info") if err != nil { t.Fatal(err) } @@ -1218,8 +1242,17 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { } currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) pbc := pbc.NewClient(&http.Client{}, &cfg.CacheURL, &cfg.ExtCacheURL, testEngine) - vendorListFetcher := gdpr.NewVendorListFetcher(context.Background(), config.GDPR{}, &http.Client{}, gdpr.VendorListURLMaker) - e := NewExchange(adapters, pbc, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, vendorListFetcher, currencyConverter, nilCategoryFetcher{}).(*exchange) + + gdprPermsBuilder := fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + }, + }.Builder + tcf2ConfigBuilder := fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + }.Builder + + e := NewExchange(adapters, pbc, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, tcf2ConfigBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}).(*exchange) // 3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs liveAdapters := []openrtb_ext.BidderName{bidderName} @@ -1407,11 +1440,11 @@ 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.NilMetricsEngine{}, 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.NilMetricsEngine{} - e.vendorListFetcher = gdpr.NewVendorListFetcher(context.Background(), config.GDPR{}, &http.Client{}, gdpr.VendorListURLMaker) + e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) //Run tests @@ -1555,14 +1588,13 @@ func TestGetBidCacheInfo(t *testing.T) { func TestBidResponseCurrency(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"} + cfg := &config.Configuration{} handlerNoBidServer := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) } server := httptest.NewServer(http.HandlerFunc(handlerNoBidServer)) defer server.Close() - biddersInfo, err := config.LoadBidderInfoFromDisk("../static/bidder-info", cfg.Adapters, openrtb_ext.BuildBidderStringSlice()) + biddersInfo, err := config.LoadBidderInfoFromDisk("../static/bidder-info") if err != nil { t.Fatal(err) } @@ -1573,8 +1605,17 @@ func TestBidResponseCurrency(t *testing.T) { } currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - vendorListFetcher := gdpr.NewVendorListFetcher(context.Background(), config.GDPR{}, &http.Client{}, gdpr.VendorListURLMaker) - e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, vendorListFetcher, currencyConverter, nilCategoryFetcher{}).(*exchange) + + gdprPermsBuilder := fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + }, + }.Builder + tcf2ConfigBuilder := fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + }.Builder + + e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, tcf2ConfigBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}).(*exchange) liveAdapters := make([]openrtb_ext.BidderName, 1) liveAdapters[0] = "appnexus" @@ -1710,11 +1751,29 @@ func TestBidResponseCurrency(t *testing.T) { 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"} + cfg := &config.Configuration{} - vendorListFetcher := gdpr.NewVendorListFetcher(context.Background(), config.GDPR{}, &http.Client{}, gdpr.VendorListURLMaker) - e := NewExchange(nil, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, nil, vendorListFetcher, nil, nilCategoryFetcher{}).(*exchange) + gdprPermsBuilder := fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + }, + }.Builder + tcf2ConfigBuilder := fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + }.Builder + + noBidHandler := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) } + server := httptest.NewServer(http.HandlerFunc(noBidHandler)) + defer server.Close() + + biddersInfo := config.BidderInfos{"appnexus": config.BidderInfo{Endpoint: "http://ib.adnxs.com"}} + + adapters, adaptersErr := BuildAdapters(server.Client(), cfg, biddersInfo, &metricsConf.NilMetricsEngine{}) + if adaptersErr != nil { + t.Fatalf("Error intializing adapters: %v", adaptersErr) + } + + e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, nil, gdprPermsBuilder, tcf2ConfigBuilder, nil, nilCategoryFetcher{}, &adscert.NilSigner{}).(*exchange) liveAdapters := make([]openrtb_ext.BidderName, 1) liveAdapters[0] = "appnexus" @@ -1749,9 +1808,10 @@ func TestBidResponseImpExtInfo(t *testing.T) { impExtInfo := make(map[string]ImpExtInfo, 1) impExtInfo["some-impression-id"] = ImpExtInfo{ true, - []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`)} + []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), + json.RawMessage(`{"imp_passthrough_val": 1}`)} - expectedBidResponseExt := `{"origbidcpm":0,"prebid":{"type":"video"},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]}}` + expectedBidResponseExt := `{"origbidcpm":0,"prebid":{"type":"video","passthrough":{"imp_passthrough_val":1}},"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)) @@ -1776,25 +1836,9 @@ func TestRaceIntegration(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(noBidServer)) defer server.Close() - cfg := &config.Configuration{ - Adapters: make(map[string]config.Adapter), - } - for _, bidder := range openrtb_ext.CoreBidderNames() { - cfg.Adapters[strings.ToLower(string(bidder))] = config.Adapter{ - Endpoint: server.URL, - } - } - cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderAudienceNetwork))] = config.Adapter{ - Endpoint: server.URL, - AppSecret: "any", - PlatformID: "abc", - } - cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderBeachfront))] = config.Adapter{ - Endpoint: server.URL, - ExtraAdapterInfo: "{\"video_endpoint\":\"" + server.URL + "\"}", - } + cfg := &config.Configuration{} - biddersInfo, err := config.LoadBidderInfoFromDisk("../static/bidder-info", cfg.Adapters, openrtb_ext.BuildBidderStringSlice()) + biddersInfo, err := config.LoadBidderInfoFromDisk("../static/bidder-info") if err != nil { t.Fatal(err) } @@ -1807,16 +1851,24 @@ func TestRaceIntegration(t *testing.T) { currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) auctionRequest := AuctionRequest{ - BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: getTestBuildRequest(t)}, - Account: config.Account{}, - UserSyncs: &emptyUsersync{}, - GDPRPermissionsBuilder: mockGDPRPermissionsBuilder, - TCF2ConfigBuilder: mockTCF2ConfigBuilder, + BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: getTestBuildRequest(t)}, + Account: config.Account{}, + UserSyncs: &emptyUsersync{}, + HookExecutor: &hookexecution.EmptyHookExecutor{}, } debugLog := DebugLog{} - vendorListFetcher := gdpr.NewVendorListFetcher(context.Background(), config.GDPR{}, &http.Client{}, gdpr.VendorListURLMaker) - ex := NewExchange(adapters, &wellBehavedCache{}, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, vendorListFetcher, currencyConverter, &nilCategoryFetcher{}).(*exchange) + + gdprPermsBuilder := fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + }, + }.Builder + tcf2CfgBuilder := fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + }.Builder + + ex := NewExchange(adapters, &wellBehavedCache{}, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, tcf2CfgBuilder, currencyConverter, &nilCategoryFetcher{}, &adscert.NilSigner{}).(*exchange) _, err = ex.HoldAuction(context.Background(), auctionRequest, &debugLog) if err != nil { t.Errorf("HoldAuction returned unexpected error: %v", err) @@ -1894,10 +1946,9 @@ func TestPanicRecovery(t *testing.T) { CacheURL: config.Cache{ ExpectedTimeMillis: 20, }, - Adapters: blankAdapterConfig(openrtb_ext.CoreBidderNames()), } - biddersInfo, err := config.LoadBidderInfoFromDisk("../static/bidder-info", cfg.Adapters, openrtb_ext.BuildBidderStringSlice()) + biddersInfo, err := config.LoadBidderInfoFromDisk("../static/bidder-info") if err != nil { t.Fatal(err) } @@ -1908,8 +1959,17 @@ func TestPanicRecovery(t *testing.T) { } currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - vendorListFetcher := gdpr.NewVendorListFetcher(context.Background(), config.GDPR{}, &http.Client{}, gdpr.VendorListURLMaker) - e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, vendorListFetcher, currencyConverter, nilCategoryFetcher{}).(*exchange) + + gdprPermsBuilder := fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + }, + }.Builder + tcf2ConfigBuilder := fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + }.Builder + + e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, tcf2ConfigBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}).(*exchange) chBids := make(chan *bidResponseWrapper, 1) panicker := func(bidderRequest BidderRequest, conversions currency.Conversions) { @@ -1955,17 +2015,9 @@ func TestPanicRecoveryHighLevel(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(noBidServer)) defer server.Close() - cfg := &config.Configuration{ - Adapters: make(map[string]config.Adapter), - } - for _, bidder := range openrtb_ext.CoreBidderNames() { - cfg.Adapters[strings.ToLower(string(bidder))] = config.Adapter{ - Endpoint: server.URL, - } - } - cfg.Adapters["audiencenetwork"] = config.Adapter{Disabled: true} + cfg := &config.Configuration{} - biddersInfo, err := config.LoadBidderInfoFromDisk("../static/bidder-info", cfg.Adapters, openrtb_ext.BuildBidderStringSlice()) + biddersInfo, err := config.LoadBidderInfoFromDisk("../static/bidder-info") if err != nil { t.Fatal(err) } @@ -1982,8 +2034,15 @@ func TestPanicRecoveryHighLevel(t *testing.T) { t.Errorf("Failed to create a category Fetcher: %v", error) } - vendorListFetcher := gdpr.NewVendorListFetcher(context.Background(), config.GDPR{}, &http.Client{}, gdpr.VendorListURLMaker) - e := NewExchange(adapters, &mockCache{}, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, vendorListFetcher, currencyConverter, categoriesFetcher).(*exchange) + gdprPermsBuilder := fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + }, + }.Builder + tcf2ConfigBuilder := fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + }.Builder + e := NewExchange(adapters, &mockCache{}, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, tcf2ConfigBuilder, currencyConverter, categoriesFetcher, &adscert.NilSigner{}).(*exchange) e.adapterMap[openrtb_ext.BidderBeachfront] = panicingAdapter{} e.adapterMap[openrtb_ext.BidderAppnexus] = panicingAdapter{} @@ -2017,11 +2076,10 @@ func TestPanicRecoveryHighLevel(t *testing.T) { } auctionRequest := AuctionRequest{ - BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: request}, - Account: config.Account{}, - UserSyncs: &emptyUsersync{}, - GDPRPermissionsBuilder: mockGDPRPermissionsBuilder, - TCF2ConfigBuilder: mockTCF2ConfigBuilder, + BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: request}, + Account: config.Account{}, + UserSyncs: &emptyUsersync{}, + HookExecutor: &hookexecution.EmptyHookExecutor{}, } debugLog := DebugLog{} _, err = e.HoldAuction(context.Background(), auctionRequest, &debugLog) @@ -2050,7 +2108,7 @@ func TestTimeoutComputation(t *testing.T) { // TestExchangeJSON executes tests for all the *.json files in exchangetest. func TestExchangeJSON(t *testing.T) { - if specFiles, err := ioutil.ReadDir("./exchangetest"); err == nil { + if specFiles, err := os.ReadDir("./exchangetest"); err == nil { for _, specFile := range specFiles { fileName := "./exchangetest/" + specFile.Name() fileDisplayName := "exchange/exchangetest/" + specFile.Name() @@ -2066,7 +2124,7 @@ func TestExchangeJSON(t *testing.T) { // LoadFile reads and parses a file as a test case. If something goes wrong, it returns an error. func loadFile(filename string) (*exchangeSpec, error) { - specData, err := ioutil.ReadFile(filename) + specData, err := os.ReadFile(filename) if err != nil { return nil, fmt.Errorf("Failed to read file %s: %v", filename, err) } @@ -2118,7 +2176,7 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { if spec.BidIDGenerator != nil { *bidIdGenerator = *spec.BidIDGenerator } - ex := newExchangeForTests(t, filename, spec.OutgoingRequests, aliases, privacyConfig, bidIdGenerator) + ex := newExchangeForTests(t, filename, spec.OutgoingRequests, aliases, privacyConfig, bidIdGenerator, spec.HostSChainFlag) biddersInAuction := findBiddersInAuction(t, filename, &spec.IncomingRequest.OrtbRequest) debugLog := &DebugLog{} if spec.DebugLog != nil { @@ -2126,6 +2184,16 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { debugLog.Regexp = regexp.MustCompile(`[<>]`) } + // Passthrough JSON Testing + impExtInfoMap := make(map[string]ImpExtInfo) + if spec.PassthroughFlag { + impPassthrough, impID, err := getInfoFromImp(&openrtb_ext.RequestWrapper{BidRequest: &spec.IncomingRequest.OrtbRequest}) + if err != nil { + t.Errorf("%s: Exchange returned an unexpected error. Got %s", filename, err.Error()) + } + impExtInfoMap[impID] = ImpExtInfo{Passthrough: impPassthrough} + } + auctionRequest := AuctionRequest{ BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: &spec.IncomingRequest.OrtbRequest}, Account: config.Account{ @@ -2133,10 +2201,11 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { EventsEnabled: spec.EventsEnabled, DebugAllow: true, }, - UserSyncs: mockIdFetcher(spec.IncomingRequest.Usersyncs), - GDPRPermissionsBuilder: mockGDPRPermissionsBuilder, - TCF2ConfigBuilder: mockTCF2ConfigBuilder, + UserSyncs: mockIdFetcher(spec.IncomingRequest.Usersyncs), + ImpExtInfoMap: impExtInfoMap, + HookExecutor: &hookexecution.EmptyHookExecutor{}, } + if spec.StartTime > 0 { auctionRequest.StartTime = time.Unix(0, spec.StartTime*1e+6) } @@ -2185,6 +2254,37 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { //compare debug info assert.JSONEq(t, string(bid.Ext), string(spec.Response.Ext), "Debug info modified") } + + if spec.PassthroughFlag { + expectedPassthough := "" + actualPassthrough := "" + if bid.Ext != nil { + actualBidRespExt := &openrtb_ext.ExtBidResponse{} + if err := json.Unmarshal(bid.Ext, actualBidRespExt); err != nil { + assert.NoError(t, err, fmt.Sprintf("Error when unmarshalling: %s", err)) + } + if actualBidRespExt.Prebid != nil { + actualPassthrough = string(actualBidRespExt.Prebid.Passthrough) + } + } + if spec.Response.Ext != nil { + expectedBidRespExt := &openrtb_ext.ExtBidResponse{} + if err := json.Unmarshal(spec.Response.Ext, expectedBidRespExt); err != nil { + assert.NoError(t, err, fmt.Sprintf("Error when unmarshalling: %s", err)) + } + if expectedBidRespExt.Prebid != nil { + expectedPassthough = string(expectedBidRespExt.Prebid.Passthrough) + } + } + + // special handling since JSONEq fails if either parameters is an empty string instead of json + if expectedPassthough == "" || actualPassthrough == "" { + assert.Equal(t, expectedPassthough, actualPassthrough, "Expected bid response extension is incorrect") + } else { + assert.JSONEq(t, expectedPassthough, actualPassthrough, "Expected bid response extension is incorrect") + } + + } } func findBiddersInAuction(t *testing.T, context string, req *openrtb2.BidRequest) []string { @@ -2224,7 +2324,7 @@ func extractResponseTimes(t *testing.T, context string, bid *openrtb2.BidRespons } } -func newExchangeForTests(t *testing.T, filename string, expectations map[string]*bidderSpec, aliases map[string]string, privacyConfig config.Privacy, bidIDGenerator BidIDGenerator) Exchange { +func newExchangeForTests(t *testing.T, filename string, expectations map[string]*bidderSpec, aliases map[string]string, privacyConfig config.Privacy, bidIDGenerator BidIDGenerator, hostSChainFlag bool) Exchange { bidderAdapters := make(map[openrtb_ext.BidderName]AdaptedBidder, len(expectations)) bidderInfos := make(config.BidderInfos, len(expectations)) for _, bidderName := range openrtb_ext.CoreBidderNames() { @@ -2262,7 +2362,14 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string] t.Fatalf("Failed to create a category Fetcher: %v", error) } - vendorListFetcher := gdpr.NewVendorListFetcher(context.Background(), config.GDPR{}, &http.Client{}, gdpr.VendorListURLMaker) + gdprPermsBuilder := fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + }, + }.Builder + tcf2ConfigBuilder := fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(privacyConfig.GDPR.TCF2, config.AccountGDPR{}), + }.Builder bidderToSyncerKey := map[string]string{} for _, bidderName := range openrtb_ext.CoreBidderNames() { @@ -2274,20 +2381,32 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string] gdprDefaultValue = gdpr.SignalNo } + hostSChainNode := &openrtb2.SupplyChainNode{} + if hostSChainFlag { + hostSChainNode = &openrtb2.SupplyChainNode{ + ASI: "pbshostcompany.com", SID: "00001", RID: "BidRequest", HP: openrtb2.Int8Ptr(1), + } + } else { + hostSChainNode = nil + } + return &exchange{ adapterMap: bidderAdapters, - me: metricsConf.NewMetricsEngine(&config.Configuration{}, openrtb_ext.CoreBidderNames(), nil), + me: metricsConf.NewMetricsEngine(&config.Configuration{}, openrtb_ext.CoreBidderNames(), nil, nil), cache: &wellBehavedCache{}, cacheTime: 0, - vendorListFetcher: vendorListFetcher, currencyConverter: currency.NewRateConverter(&http.Client{}, "", time.Duration(0)), gdprDefaultValue: gdprDefaultValue, + gdprPermsBuilder: gdprPermsBuilder, + tcf2ConfigBuilder: tcf2ConfigBuilder, privacyConfig: privacyConfig, categoriesFetcher: categoriesFetcher, bidderInfo: bidderInfos, bidderToSyncerKey: bidderToSyncerKey, externalURL: "http://localhost", bidIDGenerator: bidIDGenerator, + hostSChainNode: hostSChainNode, + server: config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "Datacenter"}, } } @@ -3501,18 +3620,18 @@ func TestMakeBidExtJSON(t *testing.T) { { 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"]}}`)}}, + extBidPrebid: openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video"), Meta: &openrtb_ext.ExtBidPrebidMeta{BrandName: "foo"}, Passthrough: nil}, + impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}}, origbidcpm: 10.0000, origbidcur: "USD", - expectedBidExt: `{"prebid":{"meta": {"brandName": "foo"}, "type":"video"},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, + expectedBidExt: `{"prebid":{"meta": {"brandName": "foo"}, "passthrough":{"imp_passthrough_val":1}, "type":"video"}, "storedrequestattributes":{"h":480,"mimes":["video/mp4"]},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, expectedErrMessage: "", }, { - description: "Valid extension, non empty extBidPrebid, valid imp ext info, meta from response", + description: "Valid extension, non empty extBidPrebid, valid imp ext info, meta from response, imp passthrough is nil", 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"]}}`)}}, + impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), nil}}, origbidcpm: 10.0000, origbidcur: "USD", expectedBidExt: `{"prebid":{"meta": {"brandName": "foo"}, "type":"video"},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, @@ -3522,16 +3641,16 @@ func TestMakeBidExtJSON(t *testing.T) { 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"]}}`)}}, + impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}}, origbidcpm: 0, - expectedBidExt: `{"origbidcpm": 0,"prebid":{"type":"video"},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]}}`, + expectedBidExt: `{"origbidcpm": 0,"prebid":{"passthrough":{"imp_passthrough_val":1}, "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"]}}`)}}, + impExtInfo: map[string]ImpExtInfo{"another_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}}, origbidcpm: 10.0000, origbidcur: "USD", expectedBidExt: `{"prebid":{"type":"video"},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, @@ -3543,8 +3662,8 @@ func TestMakeBidExtJSON(t *testing.T) { extBidPrebid: openrtb_ext.ExtBidPrebid{}, origbidcpm: 10.0000, origbidcur: "USD", - 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}, "origbidcpm": 10, "origbidcur": "USD"}`, + impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}}, + expectedBidExt: `{"prebid":{"passthrough":{"imp_passthrough_val":1}, "type":""},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -3563,8 +3682,8 @@ func TestMakeBidExtJSON(t *testing.T) { extBidPrebid: openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video")}, origbidcpm: 10.0000, origbidcur: "USD", - impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"banner":{"h":480}}`)}}, - expectedBidExt: `{"prebid":{"type":"video"},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, + impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"banner":{"h":480}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}}, + expectedBidExt: `{"prebid":{"passthrough":{"imp_passthrough_val":1}, "type":"video"},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -3573,8 +3692,8 @@ func TestMakeBidExtJSON(t *testing.T) { extBidPrebid: openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video")}, origbidcpm: 10.0000, origbidcur: "USD", - impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"banner":{"h":480}}`)}}, - expectedBidExt: `{"prebid":{"type":"video"}, "origbidcpm": 10, "origbidcur": "USD"}`, + impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"banner":{"h":480}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}}, + expectedBidExt: `{"prebid":{"passthrough":{"imp_passthrough_val":1}, "type":"video"}, "origbidcpm": 10, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -3583,8 +3702,8 @@ func TestMakeBidExtJSON(t *testing.T) { extBidPrebid: openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video")}, origbidcpm: 10.0000, origbidcur: "USD", - 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"]}, "origbidcpm": 10, "origbidcur": "USD"}`, + impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}}, + expectedBidExt: `{"prebid":{"passthrough":{"imp_passthrough_val":1}, "type":"video"}, "storedrequestattributes":{"h":480,"mimes":["video/mp4"]}, "origbidcpm": 10, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -3652,7 +3771,7 @@ func TestMakeBidExtJSON(t *testing.T) { 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"]}}`)}}, + impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), json.RawMessage(`"prebid": {"passthrough": {"imp_passthrough_val": some_val}}"`)}}, expectedBidExt: ``, expectedErrMessage: "invalid character", }, @@ -3660,7 +3779,7 @@ func TestMakeBidExtJSON(t *testing.T) { 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":{!}}`)}}, + impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{!}}`), nil}}, expectedBidExt: ``, expectedErrMessage: "invalid character", }, @@ -3695,10 +3814,17 @@ func TestStoredAuctionResponses(t *testing.T) { e := new(exchange) e.cache = &wellBehavedCache{} e.me = &metricsConf.NilMetricsEngine{} - e.vendorListFetcher = gdpr.NewVendorListFetcher(context.Background(), config.GDPR{}, &http.Client{}, gdpr.VendorListURLMaker) e.categoriesFetcher = categoriesFetcher e.bidIDGenerator = &mockBidIDGenerator{false, false} e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) + e.gdprPermsBuilder = fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + }, + }.Builder + e.tcf2ConfigBuilder = fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + }.Builder // Define mock incoming bid requeset mockBidRequest := &openrtb2.BidRequest{ @@ -3749,10 +3875,8 @@ func TestStoredAuctionResponses(t *testing.T) { Account: config.Account{}, UserSyncs: &emptyUsersync{}, StoredAuctionResponses: test.storedAuctionResp, - GDPRPermissionsBuilder: mockGDPRPermissionsBuilder, - TCF2ConfigBuilder: mockTCF2ConfigBuilder, + HookExecutor: &hookexecution.EmptyHookExecutor{}, } - // Run test outBidResponse, err := e.HoldAuction(context.Background(), auctionRequest, &DebugLog{}) if test.errorExpected { @@ -3924,10 +4048,17 @@ func TestAuctionDebugEnabled(t *testing.T) { e := new(exchange) e.cache = &wellBehavedCache{} e.me = &metricsConf.NilMetricsEngine{} - e.vendorListFetcher = gdpr.NewVendorListFetcher(context.Background(), config.GDPR{}, &http.Client{}, gdpr.VendorListURLMaker) e.categoriesFetcher = categoriesFetcher e.bidIDGenerator = &mockBidIDGenerator{false, false} e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) + e.gdprPermsBuilder = fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + }, + }.Builder + e.tcf2ConfigBuilder = fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + }.Builder ctx := context.Background() @@ -3937,13 +4068,12 @@ func TestAuctionDebugEnabled(t *testing.T) { } auctionRequest := AuctionRequest{ - BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: bidRequest}, - Account: config.Account{DebugAllow: false}, - UserSyncs: &emptyUsersync{}, - StartTime: time.Now(), - RequestType: metrics.ReqTypeORTB2Web, - GDPRPermissionsBuilder: mockGDPRPermissionsBuilder, - TCF2ConfigBuilder: mockTCF2ConfigBuilder, + BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: bidRequest}, + Account: config.Account{DebugAllow: false}, + UserSyncs: &emptyUsersync{}, + StartTime: time.Now(), + RequestType: metrics.ReqTypeORTB2Web, + HookExecutor: &hookexecution.EmptyHookExecutor{}, } debugLog := &DebugLog{DebugOverride: true, DebugEnabledOrOverridden: true} @@ -3959,6 +4089,315 @@ func TestAuctionDebugEnabled(t *testing.T) { } +func TestPassExperimentConfigsToHoldAuction(t *testing.T) { + noBidServer := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) } + server := httptest.NewServer(http.HandlerFunc(noBidServer)) + defer server.Close() + + cfg := &config.Configuration{} + + biddersInfo, err := config.LoadBidderInfoFromDisk("../static/bidder-info") + if err != nil { + t.Fatal(err) + } + biddersInfo["appnexus"] = config.BidderInfo{ + Endpoint: "test.com", + Capabilities: &config.CapabilitiesInfo{ + Site: &config.PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeVideo}, + }, + }, + Experiment: config.BidderInfoExperiment{AdsCert: config.BidderAdsCert{Enabled: true}}} + + signer := MockSigner{} + + 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)) + + gdprPermsBuilder := fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + }, + }.Builder + tcf2ConfigBuilder := fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + }.Builder + + e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, tcf2ConfigBuilder, currencyConverter, nilCategoryFetcher{}, &signer).(*exchange) + + // 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(`{"prebid":{"bidder":{"appnexus":{"placementId":1}}}}`), + }}, + Site: &openrtb2.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)}, + Ext: json.RawMessage(`{"prebid":{"experiment":{"adscert":{"enabled": true}}}}`), + } + + auctionRequest := AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: mockBidRequest}, + Account: config.Account{}, + UserSyncs: &emptyUsersync{}, + HookExecutor: &hookexecution.EmptyHookExecutor{}, + } + + debugLog := DebugLog{} + _, err = e.HoldAuction(context.Background(), auctionRequest, &debugLog) + + assert.NoError(t, err, "unexpected error occured") + assert.Equal(t, "test.com", signer.data, "incorrect signer data") +} + +func TestCallSignHeader(t *testing.T) { + type aTest struct { + description string + experiment openrtb_ext.Experiment + bidderInfo config.BidderInfo + expectedResult bool + } + var nilExperiment openrtb_ext.Experiment + + testCases := []aTest{ + { + description: "both experiment.adsCert enabled for request and for bidder ", + experiment: openrtb_ext.Experiment{AdsCert: &openrtb_ext.AdsCert{Enabled: true}}, + bidderInfo: config.BidderInfo{Experiment: config.BidderInfoExperiment{AdsCert: config.BidderAdsCert{Enabled: true}}}, + expectedResult: true, + }, + { + description: "experiment is not defined in request, bidder config adsCert enabled", + experiment: nilExperiment, + bidderInfo: config.BidderInfo{Experiment: config.BidderInfoExperiment{AdsCert: config.BidderAdsCert{Enabled: true}}}, + expectedResult: false, + }, + { + description: "experiment.adsCert is not defined in request, bidder config adsCert enabled", + experiment: openrtb_ext.Experiment{AdsCert: nil}, + bidderInfo: config.BidderInfo{Experiment: config.BidderInfoExperiment{AdsCert: config.BidderAdsCert{Enabled: true}}}, + expectedResult: false, + }, + { + description: "experiment.adsCert is disabled in request, bidder config adsCert enabled", + experiment: openrtb_ext.Experiment{AdsCert: &openrtb_ext.AdsCert{Enabled: false}}, + bidderInfo: config.BidderInfo{Experiment: config.BidderInfoExperiment{AdsCert: config.BidderAdsCert{Enabled: true}}}, + expectedResult: false, + }, + { + description: "experiment.adsCert is enabled in request, bidder config adsCert disabled", + experiment: openrtb_ext.Experiment{AdsCert: &openrtb_ext.AdsCert{Enabled: true}}, + bidderInfo: config.BidderInfo{Experiment: config.BidderInfoExperiment{AdsCert: config.BidderAdsCert{Enabled: false}}}, + expectedResult: false, + }, + { + description: "experiment.adsCert is disabled in request, bidder config adsCert disabled", + experiment: openrtb_ext.Experiment{AdsCert: &openrtb_ext.AdsCert{Enabled: false}}, + bidderInfo: config.BidderInfo{Experiment: config.BidderInfoExperiment{AdsCert: config.BidderAdsCert{Enabled: false}}}, + expectedResult: false, + }, + } + for _, test := range testCases { + result := isAdsCertEnabled(&test.experiment, test.bidderInfo) + assert.Equal(t, test.expectedResult, result, "incorrect result returned") + } + +} + +/* +TestOverrideConfigAlternateBidderCodesWithRequestValues makes sure that the correct alternabiddercodes list is forwarded to the adapters and only the approved bids are returned in auction response. + +1. request.ext.prebid.alternatebiddercodes has priority over the content of config.Account.Alternatebiddercodes. + +2. request is updated with config.Account.Alternatebiddercodes values if request.ext.prebid.alternatebiddercodes is empty or not specified. + +3. request.ext.prebid.alternatebiddercodes is given priority over config.Account.Alternatebiddercodes if both are specified. +*/ +func TestOverrideConfigAlternateBidderCodesWithRequestValues(t *testing.T) { + type testIn struct { + config config.Configuration + requestExt json.RawMessage + } + type testResults struct { + expectedSeats []string + } + + testCases := []struct { + desc string + in testIn + expected testResults + }{ + { + desc: "alternatebiddercode defined neither in config nor in the request", + in: testIn{ + config: config.Configuration{}, + }, + expected: testResults{ + expectedSeats: []string{"pubmatic"}, + }, + }, + { + desc: "alternatebiddercode defined in config and not in request", + in: testIn{ + config: config.Configuration{ + AccountDefaults: config.Account{ + AlternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "pubmatic": { + Enabled: true, + AllowedBidderCodes: []string{"groupm"}, + }, + }, + }, + }, + }, + requestExt: json.RawMessage(`{}`), + }, + expected: testResults{ + expectedSeats: []string{"pubmatic", "groupm"}, + }, + }, + { + desc: "alternatebiddercode defined in request and not in config", + in: testIn{ + requestExt: json.RawMessage(`{"prebid": {"alternatebiddercodes": {"enabled": true, "bidders": {"pubmatic": {"enabled": true, "allowedbiddercodes": ["appnexus"]}}}}}`), + }, + expected: testResults{ + expectedSeats: []string{"pubmatic", "appnexus"}, + }, + }, + { + desc: "alternatebiddercode defined in both config and in request", + in: testIn{ + config: config.Configuration{ + AccountDefaults: config.Account{ + AlternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "pubmatic": { + Enabled: true, + AllowedBidderCodes: []string{"groupm"}, + }, + }, + }, + }, + }, + requestExt: json.RawMessage(`{"prebid": {"alternatebiddercodes": {"enabled": true, "bidders": {"pubmatic": {"enabled": true, "allowedbiddercodes": ["ix"]}}}}}`), + }, + expected: testResults{ + expectedSeats: []string{"pubmatic", "ix"}, + }, + }, + } + + // Init an exchange to run an auction from + noBidServer := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) } + mockPubMaticBidService := httptest.NewServer(http.HandlerFunc(noBidServer)) + defer mockPubMaticBidService.Close() + + categoriesFetcher, error := newCategoryFetcher("./test/category-mapping") + if error != nil { + t.Errorf("Failed to create a category Fetcher: %v", error) + } + + mockBidderRequestResponse := &goodSingleBidder{ + httpRequest: &adapters.RequestData{ + Method: "POST", + Uri: mockPubMaticBidService.URL, + Body: []byte("{\"key\":\"val\"}"), + Headers: http.Header{}, + }, + bidResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + {Bid: &openrtb2.Bid{ID: "1"}, Seat: ""}, + {Bid: &openrtb2.Bid{ID: "2"}, Seat: "pubmatic"}, + {Bid: &openrtb2.Bid{ID: "3"}, Seat: "appnexus"}, + {Bid: &openrtb2.Bid{ID: "4"}, Seat: "groupm"}, + {Bid: &openrtb2.Bid{ID: "5"}, Seat: "ix"}, + }, + Currency: "USD", + }, + } + + e := new(exchange) + e.cache = &wellBehavedCache{} + e.me = &metricsConf.NilMetricsEngine{} + e.gdprPermsBuilder = fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + }, + }.Builder + e.tcf2ConfigBuilder = fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + }.Builder + e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) + 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(`{"prebid":{"bidder":{"pubmatic": {"publisherId": 1}}}}`), + }}, + Site: &openrtb2.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)}, + } + + // Run tests + for _, test := range testCases { + e.adapterMap = map[openrtb_ext.BidderName]AdaptedBidder{ + openrtb_ext.BidderPubmatic: AdaptBidder(mockBidderRequestResponse, mockPubMaticBidService.Client(), &test.in.config, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderPubmatic, nil, ""), + } + + mockBidRequest.Ext = test.in.requestExt + + auctionRequest := AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: mockBidRequest}, + Account: test.in.config.AccountDefaults, + UserSyncs: &emptyUsersync{}, + HookExecutor: &hookexecution.EmptyHookExecutor{}, + } + + // Run test + outBidResponse, err := e.HoldAuction(context.Background(), auctionRequest, &DebugLog{}) + + // Assertions + assert.NoErrorf(t, err, "%s. HoldAuction error: %v \n", test.desc, err) + assert.NotNil(t, outBidResponse) + + // So 2 seatBids are expected as, + // the default "" and "pubmatic" bids will be in one seat and the extra-bids "groupm"/"appnexus"/"ix" in another seat. + assert.Len(t, outBidResponse.SeatBid, len(test.expected.expectedSeats), "%s. seatbid count miss-match\n", test.desc) + + for i, seatBid := range outBidResponse.SeatBid { + assert.Contains(t, test.expected.expectedSeats, seatBid.Seat, "%s. unexpected seatbid\n", test.desc) + + if seatBid.Seat == string(openrtb_ext.BidderPubmatic) { + assert.Len(t, outBidResponse.SeatBid[i].Bid, 2, "%s. unexpected bid count\n", test.desc) + } else { + assert.Len(t, outBidResponse.SeatBid[i].Bid, 1, "%s. unexpected bid count\n", test.desc) + } + } + } +} + +type MockSigner struct { + data string +} + +func (ms *MockSigner) Sign(destinationURL string, body []byte) (string, error) { + ms.data = destinationURL + return "mock data", nil +} + type exchangeSpec struct { GDPREnabled bool `json:"gdpr_enabled"` IncomingRequest exchangeRequest `json:"incomingRequest"` @@ -3972,6 +4411,8 @@ type exchangeSpec struct { StartTime int64 `json:"start_time_ms,omitempty"` BidIDGenerator *mockBidIDGenerator `json:"bidIDGenerator,omitempty"` RequestType *metrics.RequestType `json:"requestType,omitempty"` + PassthroughFlag bool `json:"passthrough_flag,omitempty"` + HostSChainFlag bool `json:"host_schain_flag,omitempty"` } type exchangeRequest struct { @@ -3992,12 +4433,12 @@ type bidderSpec struct { } type bidderRequest struct { - OrtbRequest openrtb2.BidRequest `json:"ortbRequest"` - BidAdjustment float64 `json:"bidAdjustment"` + OrtbRequest openrtb2.BidRequest `json:"ortbRequest"` + BidAdjustments map[string]float64 `json:"bidAdjustments"` } type bidderResponse struct { - SeatBid *bidderSeatBid `json:"pbsSeatBid,omitempty"` + SeatBids []*bidderSeatBid `json:"pbsSeatBids,omitempty"` Errors []string `json:"errors,omitempty"` HttpCalls []*openrtb_ext.ExtHttpCall `json:"httpCalls,omitempty"` } @@ -4007,13 +4448,15 @@ type bidderResponse struct { // JSON property tags on those types are contracts in prod. type bidderSeatBid struct { Bids []bidderBid `json:"pbsBids,omitempty"` + Seat string `json:"seat"` } // bidderBid is basically a subset of pbsOrtbBid from exchange/bidder.go. // See the comment on bidderSeatBid for more info. type bidderBid struct { - Bid *openrtb2.Bid `json:"ortbBid,omitempty"` - Type string `json:"bidType,omitempty"` + Bid *openrtb2.Bid `json:"ortbBid,omitempty"` + Type string `json:"bidType,omitempty"` + Meta *openrtb_ext.ExtBidPrebidMeta `json:"bidMeta,omitempty"` } type mockIdFetcher map[string]string @@ -4037,11 +4480,11 @@ type validatingBidder struct { mockResponses map[string]bidderResponse } -func (b *validatingBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (seatBid *pbsOrtbSeatBid, errs []error) { +func (b *validatingBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes) (seatBids []*pbsOrtbSeatBid, errs []error) { if expectedRequest, ok := b.expectations[string(bidderRequest.BidderName)]; ok { if expectedRequest != nil { - if expectedRequest.BidAdjustment != bidAdjustment { - b.t.Errorf("%s: Bidder %s got wrong bid adjustment. Expected %f, got %f", b.fileName, bidderRequest.BidderName, expectedRequest.BidAdjustment, bidAdjustment) + if !reflect.DeepEqual(expectedRequest.BidAdjustments, bidRequestOptions.bidAdjustments) { + b.t.Errorf("%s: Bidder %s got wrong bid adjustment. Expected %v, got %v", b.fileName, bidderRequest.BidderName, expectedRequest.BidAdjustments, bidRequestOptions.bidAdjustments) } diffOrtbRequests(b.t, fmt.Sprintf("Request to %s in %s", string(bidderRequest.BidderName), b.fileName), &expectedRequest.OrtbRequest, bidderRequest.BidRequest) } @@ -4050,25 +4493,34 @@ func (b *validatingBidder) requestBid(ctx context.Context, bidderRequest BidderR } if mockResponse, ok := b.mockResponses[string(bidderRequest.BidderName)]; ok { - if mockResponse.SeatBid != nil { - bids := make([]*pbsOrtbBid, len(mockResponse.SeatBid.Bids)) - for i := 0; i < len(bids); i++ { - bids[i] = &pbsOrtbBid{ - originalBidCPM: mockResponse.SeatBid.Bids[i].Bid.Price, - bid: mockResponse.SeatBid.Bids[i].Bid, - bidType: openrtb_ext.BidType(mockResponse.SeatBid.Bids[i].Type), + if len(mockResponse.SeatBids) != 0 { + for _, mockSeatBid := range mockResponse.SeatBids { + var bids []*pbsOrtbBid + + if len(mockSeatBid.Bids) != 0 { + bids = make([]*pbsOrtbBid, len(mockSeatBid.Bids)) + for i := 0; i < len(bids); i++ { + bids[i] = &pbsOrtbBid{ + originalBidCPM: mockSeatBid.Bids[i].Bid.Price, + bid: mockSeatBid.Bids[i].Bid, + bidType: openrtb_ext.BidType(mockSeatBid.Bids[i].Type), + bidMeta: mockSeatBid.Bids[i].Meta, + } + } } - } - seatBid = &pbsOrtbSeatBid{ - bids: bids, - httpCalls: mockResponse.HttpCalls, + seatBids = append(seatBids, &pbsOrtbSeatBid{ + bids: bids, + httpCalls: mockResponse.HttpCalls, + seat: mockSeatBid.Seat, + }) } } else { - seatBid = &pbsOrtbSeatBid{ + seatBids = []*pbsOrtbSeatBid{{ bids: nil, httpCalls: mockResponse.HttpCalls, - } + seat: string(bidderRequest.BidderName), + }} } for _, err := range mockResponse.Errors { @@ -4085,9 +4537,9 @@ type capturingRequestBidder struct { req *openrtb2.BidRequest } -func (b *capturingRequestBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (seatBid *pbsOrtbSeatBid, errs []error) { +func (b *capturingRequestBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes *openrtb_ext.ExtAlternateBidderCodes) (seatBid []*pbsOrtbSeatBid, errs []error) { b.req = bidderRequest.BidRequest - return &pbsOrtbSeatBid{}, nil + return []*pbsOrtbSeatBid{{}}, nil } func diffOrtbRequests(t *testing.T, description string, expected *openrtb2.BidRequest, actual *openrtb2.BidRequest) { @@ -4124,7 +4576,6 @@ func diffOrtbResponses(t *testing.T, description string, expected *openrtb2.BidR if err != nil { t.Fatalf("%s failed to marshal expected BidResponse into JSON. %v", description, err) } - assert.JSONEq(t, string(expectedJSON), string(actualJSON), description) } @@ -4135,6 +4586,11 @@ func mapifySeatBids(t *testing.T, context string, seatBids []openrtb2.SeatBid) m if _, ok := seatMap[seatName]; ok { t.Fatalf("%s: Contains duplicate Seat: %s", context, seatName) } else { + // The sequence of extra bids for same seat from different bidder is not guaranteed as we randomize the list of adapters + // This is w.r.t changes at exchange.go#561 (club bids from different bidders for same extra-bid) + sort.Slice(seatBids[i].Bid, func(x, y int) bool { + return seatBids[i].Bid[x].Price > seatBids[i].Bid[y].Price + }) seatMap[seatName] = &seatBids[i] } } @@ -4187,7 +4643,7 @@ func (e *emptyUsersync) HasAnyLiveSyncs() bool { type panicingAdapter struct{} -func (panicingAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (posb *pbsOrtbSeatBid, errs []error) { +func (panicingAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestMetadata bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes) (posb []*pbsOrtbSeatBid, errs []error) { panic("Panic! Panic! The world is ending!") } @@ -4218,7 +4674,7 @@ func (m *fakeCurrencyRatesHttpClient) Do(req *http.Request) (*http.Response, err return &http.Response{ Status: "200 OK", StatusCode: http.StatusOK, - Body: ioutil.NopCloser(strings.NewReader(m.responseBody)), + Body: io.NopCloser(strings.NewReader(m.responseBody)), }, nil } @@ -4239,12 +4695,21 @@ func (m *mockBidder) MakeBids(internalRequest *openrtb2.BidRequest, externalRequ return args.Get(0).(*adapters.BidderResponse), args.Get(1).([]error) } -func mockGDPRPermissionsBuilder(cfg config.GDPR, tcf2Config gdpr.TCF2ConfigReader, vendorIDs map[openrtb_ext.BidderName]uint16, fetcher gdpr.VendorListFetcher) gdpr.Permissions { - return &permissionsMock{allowAllBidders: true} -} +func getInfoFromImp(req *openrtb_ext.RequestWrapper) (json.RawMessage, string, error) { + bidRequest := req.BidRequest + imp := bidRequest.Imp[0] + impID := imp.ID + + var bidderExts map[string]json.RawMessage + if err := json.Unmarshal(imp.Ext, &bidderExts); err != nil { + return nil, "", err + } -func mockTCF2ConfigBuilder(hostConfig config.TCF2, accountConfig config.AccountGDPR) gdpr.TCF2ConfigReader { - return &config.TCF2{ - Enabled: hostConfig.Enabled, + var extPrebid openrtb_ext.ExtImpPrebid + if bidderExts[openrtb_ext.PrebidExtKey] != nil { + if err := json.Unmarshal(bidderExts[openrtb_ext.PrebidExtKey], &extPrebid); err != nil { + return nil, "", err + } } + return extPrebid.Passthrough, impID, nil } diff --git a/exchange/exchangetest/aliases.json b/exchange/exchangetest/aliases.json index 94dea60527f..b4222219396 100644 --- a/exchange/exchangetest/aliases.json +++ b/exchange/exchangetest/aliases.json @@ -9,14 +9,20 @@ { "id": "my-imp-id", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { - "appnexus": { - "placementId": 1 - }, - "districtm": { - "placementId": 2 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + }, + "districtm": { + "placementId": 2 + } + } } } } @@ -48,7 +54,9 @@ { "id": "my-imp-id", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { "bidder": { @@ -59,16 +67,19 @@ ], "ext": { "prebid": { - "aliases": { - "districtm": "appnexus" + "server": { + "datacenter": "Datacenter", + "externalurl": "http://hosturl.com", + "gvlid": 1 } } } - }, - "bidAdjustment": 1.0 + } }, "mockResponse": { - "errors": ["appnexus-error"] + "errors": [ + "appnexus-error" + ] } }, "districtm": { @@ -85,7 +96,9 @@ { "id": "my-imp-id", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { "bidder": { @@ -96,16 +109,19 @@ ], "ext": { "prebid": { - "aliases": { - "districtm": "appnexus" + "server": { + "datacenter": "Datacenter", + "externalurl": "http://hosturl.com", + "gvlid": 1 } } } - }, - "bidAdjustment": 1.0 + } }, "mockResponse": { - "errors": ["districtm-error"] + "errors": [ + "districtm-error" + ] } } }, @@ -114,10 +130,14 @@ "id": "some-request-id", "ext": { "errors": { - "appnexus": ["appnexus-error"], - "districtm": ["districtm-error"] + "appnexus": [ + "appnexus-error" + ], + "districtm": [ + "districtm-error" + ] } } } } -} +} \ No newline at end of file diff --git a/exchange/exchangetest/append-bidder-names.json b/exchange/exchangetest/append-bidder-names.json index 2c0d46e4dac..86688ed19cb 100644 --- a/exchange/exchangetest/append-bidder-names.json +++ b/exchange/exchangetest/append-bidder-names.json @@ -14,8 +14,12 @@ ] }, "ext": { - "appnexus": { - "placementId": 1 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } } } }, @@ -27,8 +31,12 @@ ] }, "ext": { - "appnexus": { - "placementId": 1 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } } } } @@ -54,42 +62,45 @@ "outgoingRequests": { "appnexus": { "mockResponse": { - "pbsSeatBid": { - "pbsBids": [ - { - "ortbBid": { - "id": "apn-bid", - "impid": "my-imp-id", - "price": 0.3, - "w": 200, - "h": 250, - "crid": "creative-1", - "cat": [ - "IAB1-1" - ] + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "cat": [ + "IAB1-1" + ] + }, + "bidType": "video", + "bidVideo": { + "duration": 30, + "PrimaryCategory": "" + } }, - "bidType": "video", - "bidVideo": { - "duration": 30, - "PrimaryCategory": "" + { + "ortbBid": { + "id": "apn-other-bid", + "impid": "imp-id-2", + "price": 0.6, + "w": 300, + "h": 500, + "crid": "creative-3", + "cat": [ + "IAB1-2" + ] + }, + "bidType": "video" } - }, - { - "ortbBid": { - "id": "apn-other-bid", - "impid": "imp-id-2", - "price": 0.6, - "w": 300, - "h": 500, - "crid": "creative-3", - "cat": [ - "IAB1-2" - ] - }, - "bidType": "video" - } - ] - } + ], + "seat": "appnexus" + } + ] } } }, @@ -179,8 +190,12 @@ ] }, "ext": { - "appnexus": { - "placementId": 1 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } } } }, @@ -192,8 +207,12 @@ ] }, "ext": { - "appnexus": { - "placementId": 1 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } } } } diff --git a/exchange/exchangetest/bid-consolidation-test.json b/exchange/exchangetest/bid-consolidation-test.json index 99a2a60a011..b38e9d69603 100644 --- a/exchange/exchangetest/bid-consolidation-test.json +++ b/exchange/exchangetest/bid-consolidation-test.json @@ -9,23 +9,31 @@ { "id": "my-imp-id", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { - "appnexus": { - "placementId": 1 - }, - "rubicon": { - "accountId": 1, - "siteId": 2, - "zoneId": 3 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + }, + "rubicon": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + } + } } } }, { "id": "imp-id-2", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { "appnexus": { @@ -48,51 +56,57 @@ "outgoingRequests": { "appnexus": { "mockResponse": { - "pbsSeatBid": { - "pbsBids": [ - { - "ortbBid": { - "id": "apn-bid", - "impid": "my-imp-id", - "price": 0.3, - "w": 200, - "h": 250, - "crid": "creative-1" - }, - "bidType": "video" - }, - { - "ortbBid": { - "id": "apn-other-bid", - "impid": "imp-id-2", - "price": 0.6, - "w": 300, - "h": 500, - "crid": "creative-3" + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1" + }, + "bidType": "video" }, - "bidType": "video" - } - ] - } + { + "ortbBid": { + "id": "apn-other-bid", + "impid": "imp-id-2", + "price": 0.6, + "w": 300, + "h": 500, + "crid": "creative-3" + }, + "bidType": "video" + } + ], + "seat": "appnexus" + } + ] } }, "rubicon": { "mockResponse": { - "pbsSeatBid": { - "pbsBids": [ - { - "ortbBid": { - "id": "rubi-bid", - "impid": "my-imp-id", - "price": 0.4, - "w": 200, - "h": 250, - "crid": "creative-2" - }, - "bidType": "video" - } - ] - } + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "rubi-bid", + "impid": "my-imp-id", + "price": 0.4, + "w": 200, + "h": 250, + "crid": "creative-2" + }, + "bidType": "video" + } + ], + "seat": "rubicon" + } + ] } } }, @@ -102,53 +116,57 @@ "seatbid": [ { "seat": "rubicon", - "bid": [{ - "id": "rubi-bid", - "impid": "my-imp-id", - "price": 0.4, - "w": 200, - "h": 250, - "crid": "creative-2", - "ext": { - "origbidcpm": 0.4, - "prebid": { - "type": "video" + "bid": [ + { + "id": "rubi-bid", + "impid": "my-imp-id", + "price": 0.4, + "w": 200, + "h": 250, + "crid": "creative-2", + "ext": { + "origbidcpm": 0.4, + "prebid": { + "type": "video" + } } } - }] + ] }, { "seat": "appnexus", - "bid": [{ - "id": "apn-bid", - "impid": "my-imp-id", - "price": 0.3, - "w": 200, - "h": 250, - "crid": "creative-1", - "ext": { - "origbidcpm": 0.3, - "prebid": { - "type": "video" + "bid": [ + { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "origbidcpm": 0.3, + "prebid": { + "type": "video" + } } - } - }, - { - "id": "apn-other-bid", - "impid": "imp-id-2", - "price": 0.6, - "w": 300, - "h": 500, - "crid": "creative-3", - "ext": { - "origbidcpm": 0.6, - "prebid": { - "type": "video" + }, + { + "id": "apn-other-bid", + "impid": "imp-id-2", + "price": 0.6, + "w": 300, + "h": 500, + "crid": "creative-3", + "ext": { + "origbidcpm": 0.6, + "prebid": { + "type": "video" + } } } - }] + ] } ] } } -} +} \ No newline at end of file diff --git a/exchange/exchangetest/bid-ext-prebid-collision.json b/exchange/exchangetest/bid-ext-prebid-collision.json index 3cc311ad045..49e4f8a8f22 100644 --- a/exchange/exchangetest/bid-ext-prebid-collision.json +++ b/exchange/exchangetest/bid-ext-prebid-collision.json @@ -1,23 +1,30 @@ { "description": "Verifies bid.ext values are left alone from the adapter, except for overwriting bid.ext.prebid if the adapter ext includes a collision.", - "incomingRequest": { "ortbRequest": { "id": "some-request-id", "site": { "page": "test.somepage.com" }, - "imp": [{ - "id": "my-imp-id", - "video": { - "mimes": ["video/mp4"] - }, - "ext": { - "appnexus": { - "placementId": 1 + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } + } } } - }] + ] } }, "outgoingRequests": { @@ -28,24 +35,60 @@ "site": { "page": "test.somepage.com" }, - "imp": [{ - "id": "my-imp-id", - "video": { - "mimes": ["video/mp4"] - }, - "ext": { - "bidder": { - "placementId": 1 + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } } } - }] - }, - "bidAdjustment": 1.0 + ] + } }, "mockResponse": { - "pbsSeatBid": { - "pbsBids": [{ - "ortbBid": { + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "someField": "someValue", + "origbidcpm": 0.3, + "prebid": { + "willBeOverwritten": "by core logic" + } + } + }, + "bidType": "video" + } + ], + "seat": "appnexus" + } + ] + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [ + { + "seat": "appnexus", + "bid": [ + { "id": "apn-bid", "impid": "my-imp-id", "price": 0.3, @@ -56,37 +99,13 @@ "someField": "someValue", "origbidcpm": 0.3, "prebid": { - "willBeOverwritten": "by core logic" + "type": "video" } } - }, - "bidType": "video" - }] - } - } - } - }, - "response": { - "bids": { - "id": "some-request-id", - "seatbid": [{ - "seat": "appnexus", - "bid": [{ - "id": "apn-bid", - "impid": "my-imp-id", - "price": 0.3, - "w": 200, - "h": 250, - "crid": "creative-1", - "ext": { - "someField": "someValue", - "origbidcpm": 0.3, - "prebid": { - "type": "video" } - } - }] - }] + ] + } + ] } } } \ No newline at end of file diff --git a/exchange/exchangetest/bid-ext.json b/exchange/exchangetest/bid-ext.json index c208f73cc77..69e7aba9f0c 100644 --- a/exchange/exchangetest/bid-ext.json +++ b/exchange/exchangetest/bid-ext.json @@ -1,23 +1,30 @@ { "description": "Verifies bid.ext values are left alone from the adapter, except for adding in bid.ext.prebid.", - "incomingRequest": { "ortbRequest": { "id": "some-request-id", "site": { "page": "test.somepage.com" }, - "imp": [{ - "id": "my-imp-id", - "video": { - "mimes": ["video/mp4"] - }, - "ext": { - "appnexus": { - "placementId": 1 + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } + } } } - }] + ] } }, "outgoingRequests": { @@ -28,24 +35,57 @@ "site": { "page": "test.somepage.com" }, - "imp": [{ - "id": "my-imp-id", - "video": { - "mimes": ["video/mp4"] - }, - "ext": { - "bidder": { - "placementId": 1 + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } } } - }] - }, - "bidAdjustment": 1.0 + ] + } }, "mockResponse": { - "pbsSeatBid": { - "pbsBids": [{ - "ortbBid": { + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "origbidcpm": 0.3, + "someField": "someValue" + } + }, + "bidType": "video" + } + ], + "seat": "appnexus" + } + ] + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [ + { + "seat": "appnexus", + "bid": [ + { "id": "apn-bid", "impid": "my-imp-id", "price": 0.3, @@ -54,36 +94,15 @@ "crid": "creative-1", "ext": { "origbidcpm": 0.3, - "someField": "someValue" + "someField": "someValue", + "prebid": { + "type": "video" + } } - }, - "bidType": "video" - }] - } - } - } - }, - "response": { - "bids": { - "id": "some-request-id", - "seatbid": [{ - "seat": "appnexus", - "bid": [{ - "id": "apn-bid", - "impid": "my-imp-id", - "price": 0.3, - "w": 200, - "h": 250, - "crid": "creative-1", - "ext": { - "origbidcpm": 0.3, - "someField": "someValue", - "prebid": { - "type": "video" } - } - }] - }] + ] + } + ] } } } \ No newline at end of file diff --git a/exchange/exchangetest/bid-id-invalid.json b/exchange/exchangetest/bid-id-invalid.json index 278331d4293..e71bb0a50cd 100644 --- a/exchange/exchangetest/bid-id-invalid.json +++ b/exchange/exchangetest/bid-id-invalid.json @@ -18,8 +18,12 @@ ] }, "ext": { - "appnexus": { - "placementId": 1 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } } } } @@ -45,28 +49,31 @@ "outgoingRequests": { "appnexus": { "mockResponse": { - "pbsSeatBid": { - "pbsBids": [ - { - "ortbBid": { - "id": "apn-bid", - "impid": "my-imp-id", - "price": 0.3, - "w": 200, - "h": 250, - "crid": "creative-1", - "cat": [ - "IAB1-1" - ] - }, - "bidType": "video", - "bidVideo": { - "duration": 30, - "PrimaryCategory": "" + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "cat": [ + "IAB1-1" + ] + }, + "bidType": "video", + "bidVideo": { + "duration": 30, + "PrimaryCategory": "" + } } - } - ] - } + ], + "seat": "appnexus" + } + ] } } }, @@ -125,8 +132,12 @@ ] }, "ext": { - "appnexus": { - "placementId": 1 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } } } } diff --git a/exchange/exchangetest/bid-id-valid.json b/exchange/exchangetest/bid-id-valid.json index f353514f31f..85ceb883f1b 100644 --- a/exchange/exchangetest/bid-id-valid.json +++ b/exchange/exchangetest/bid-id-valid.json @@ -18,8 +18,12 @@ ] }, "ext": { - "appnexus": { - "placementId": 1 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } } } } @@ -45,28 +49,31 @@ "outgoingRequests": { "appnexus": { "mockResponse": { - "pbsSeatBid": { - "pbsBids": [ - { - "ortbBid": { - "id": "apn-bid", - "impid": "my-imp-id", - "price": 0.3, - "w": 200, - "h": 250, - "crid": "creative-1", - "cat": [ - "IAB1-1" - ] - }, - "bidType": "video", - "bidVideo": { - "duration": 30, - "PrimaryCategory": "" + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "cat": [ + "IAB1-1" + ] + }, + "bidType": "video", + "bidVideo": { + "duration": 30, + "PrimaryCategory": "" + } } - } - ] - } + ], + "seat": "appnexus" + } + ] } } }, @@ -126,8 +133,12 @@ ] }, "ext": { - "appnexus": { - "placementId": 1 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } } } } diff --git a/exchange/exchangetest/bidadjustmentfactors.json b/exchange/exchangetest/bidadjustmentfactors.json index 301d98c1d0e..49ddde7d26e 100644 --- a/exchange/exchangetest/bidadjustmentfactors.json +++ b/exchange/exchangetest/bidadjustmentfactors.json @@ -9,14 +9,20 @@ { "id": "my-imp-id", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { - "appnexus": { - "placementId": 1 - }, - "districtm": { - "placementId": 2 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + }, + "districtm": { + "placementId": 2 + } + } } } } @@ -46,7 +52,9 @@ { "id": "my-imp-id", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { "bidder": { @@ -57,20 +65,23 @@ ], "ext": { "prebid": { - "aliases": { - "districtm": "appnexus" - }, - "bidadjustmentfactors": { - "appnexus": 2.5, - "districtm": 0.3 + "server": { + "datacenter": "Datacenter", + "externalurl": "http://hosturl.com", + "gvlid": 1 } } } }, - "bidAdjustment": 2.5 + "bidAdjustments": { + "appnexus": 2.5, + "districtm": 0.3 + } }, "mockResponse": { - "errors": ["appnexus-error"] + "errors": [ + "appnexus-error" + ] } }, "districtm": { @@ -84,7 +95,9 @@ { "id": "my-imp-id", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { "bidder": { @@ -95,20 +108,23 @@ ], "ext": { "prebid": { - "aliases": { - "districtm": "appnexus" - }, - "bidadjustmentfactors": { - "appnexus": 2.5, - "districtm": 0.3 + "server": { + "datacenter": "Datacenter", + "externalurl": "http://hosturl.com", + "gvlid": 1 } } } }, - "bidAdjustment": 0.3 + "bidAdjustments": { + "appnexus": 2.5, + "districtm": 0.3 + } }, "mockResponse": { - "errors": ["districtm-error"] + "errors": [ + "districtm-error" + ] } } }, @@ -117,10 +133,14 @@ "id": "some-request-id", "ext": { "errors": { - "appnexus": ["appnexus-error"], - "districtm": ["districtm-error"] + "appnexus": [ + "appnexus-error" + ], + "districtm": [ + "districtm-error" + ] } } } } -} +} \ No newline at end of file diff --git a/exchange/exchangetest/ccpa-featureflag-off.json b/exchange/exchangetest/ccpa-featureflag-off.json index d5445ccb6aa..d3080a4685d 100644 --- a/exchange/exchangetest/ccpa-featureflag-off.json +++ b/exchange/exchangetest/ccpa-featureflag-off.json @@ -6,17 +6,25 @@ "site": { "page": "test.somepage.com" }, - "imp": [{ - "id": "my-imp-id", - "video": { - "mimes": ["video/mp4"] - }, - "ext": { - "appnexus": { - "placementId": 1 + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } + } } } - }], + ], "regs": { "ext": { "us_privacy": "1-Y-" @@ -35,17 +43,21 @@ "site": { "page": "test.somepage.com" }, - "imp": [{ - "id": "my-imp-id", - "video": { - "mimes": ["video/mp4"] - }, - "ext": { - "bidder": { - "placementId": 1 + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } } } - }], + ], "regs": { "ext": { "us_privacy": "1-Y-" @@ -54,11 +66,12 @@ "user": { "buyeruid": "some-buyer-id" } - }, - "bidAdjustment": 1.0 + } }, "mockResponse": { - "errors": ["appnexus-error"] + "errors": [ + "appnexus-error" + ] } } } diff --git a/exchange/exchangetest/ccpa-featureflag-on.json b/exchange/exchangetest/ccpa-featureflag-on.json index 97a7c3a93f4..0c452098da8 100644 --- a/exchange/exchangetest/ccpa-featureflag-on.json +++ b/exchange/exchangetest/ccpa-featureflag-on.json @@ -6,17 +6,25 @@ "site": { "page": "test.somepage.com" }, - "imp": [{ - "id": "my-imp-id", - "video": { - "mimes": ["video/mp4"] - }, - "ext": { - "appnexus": { - "placementId": 1 + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } + } } } - }], + ], "regs": { "ext": { "us_privacy": "1-Y-" @@ -35,29 +43,33 @@ "site": { "page": "test.somepage.com" }, - "imp": [{ - "id": "my-imp-id", - "video": { - "mimes": ["video/mp4"] - }, - "ext": { - "bidder": { - "placementId": 1 + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } } } - }], + ], "regs": { "ext": { "us_privacy": "1-Y-" } }, - "user": { - } - }, - "bidAdjustment": 1.0 + "user": {} + } }, "mockResponse": { - "errors": ["appnexus-error"] + "errors": [ + "appnexus-error" + ] } } } diff --git a/exchange/exchangetest/ccpa-nosale-any-bidder.json b/exchange/exchangetest/ccpa-nosale-any-bidder.json index f7abd91f512..412aef92c39 100644 --- a/exchange/exchangetest/ccpa-nosale-any-bidder.json +++ b/exchange/exchangetest/ccpa-nosale-any-bidder.json @@ -6,17 +6,25 @@ "site": { "page": "test.somepage.com" }, - "imp": [{ - "id": "my-imp-id", - "video": { - "mimes": ["video/mp4"] - }, - "ext": { - "appnexus": { - "placementId": 1 + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } + } } } - }], + ], "regs": { "ext": { "us_privacy": "1-Y-" @@ -24,7 +32,9 @@ }, "ext": { "prebid": { - "nosale": ["*"] + "nosale": [ + "*" + ] } }, "user": { @@ -40,17 +50,21 @@ "site": { "page": "test.somepage.com" }, - "imp": [{ - "id": "my-imp-id", - "video": { - "mimes": ["video/mp4"] - }, - "ext": { - "bidder": { - "placementId": 1 + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } } } - }], + ], "regs": { "ext": { "us_privacy": "1-Y-" @@ -58,17 +72,22 @@ }, "ext": { "prebid": { - "nosale": ["*"] + "server": { + "datacenter": "Datacenter", + "externalurl": "http://hosturl.com", + "gvlid": 1 + } } }, "user": { "buyeruid": "some-buyer-id" } - }, - "bidAdjustment": 1.0 + } }, "mockResponse": { - "errors": ["appnexus-error"] + "errors": [ + "appnexus-error" + ] } } } diff --git a/exchange/exchangetest/ccpa-nosale-specific-bidder.json b/exchange/exchangetest/ccpa-nosale-specific-bidder.json index b89e29aea01..7053c9d820a 100644 --- a/exchange/exchangetest/ccpa-nosale-specific-bidder.json +++ b/exchange/exchangetest/ccpa-nosale-specific-bidder.json @@ -6,17 +6,25 @@ "site": { "page": "test.somepage.com" }, - "imp": [{ - "id": "my-imp-id", - "video": { - "mimes": ["video/mp4"] - }, - "ext": { - "appnexus": { - "placementId": 1 + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } + } } } - }], + ], "regs": { "ext": { "us_privacy": "1-Y-" @@ -24,7 +32,9 @@ }, "ext": { "prebid": { - "nosale": ["appnexus"] + "nosale": [ + "appnexus" + ] } }, "user": { @@ -40,17 +50,21 @@ "site": { "page": "test.somepage.com" }, - "imp": [{ - "id": "my-imp-id", - "video": { - "mimes": ["video/mp4"] - }, - "ext": { - "bidder": { - "placementId": 1 + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } } } - }], + ], "regs": { "ext": { "us_privacy": "1-Y-" @@ -58,17 +72,22 @@ }, "ext": { "prebid": { - "nosale": ["appnexus"] + "server": { + "datacenter": "Datacenter", + "externalurl": "http://hosturl.com", + "gvlid": 1 + } } }, "user": { "buyeruid": "some-buyer-id" } - }, - "bidAdjustment": 1.0 + } }, "mockResponse": { - "errors": ["appnexus-error"] + "errors": [ + "appnexus-error" + ] } } } diff --git a/exchange/exchangetest/debuglog_disabled.json b/exchange/exchangetest/debuglog_disabled.json index c4b25a01760..fc3226e34e8 100644 --- a/exchange/exchangetest/debuglog_disabled.json +++ b/exchange/exchangetest/debuglog_disabled.json @@ -24,8 +24,12 @@ ] }, "ext": { - "appnexus": { - "placementId": 1 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } } } }, @@ -37,8 +41,12 @@ ] }, "ext": { - "appnexus": { - "placementId": 1 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } } } } @@ -63,42 +71,45 @@ "outgoingRequests": { "appnexus": { "mockResponse": { - "pbsSeatBid": { - "pbsBids": [ - { - "ortbBid": { - "id": "apn-bid", - "impid": "my-imp-id", - "price": 0.3, - "w": 200, - "h": 250, - "crid": "creative-1", - "cat": [ - "IAB1-1" - ] + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "cat": [ + "IAB1-1" + ] + }, + "bidType": "video", + "bidVideo": { + "duration": 30, + "PrimaryCategory": "" + } }, - "bidType": "video", - "bidVideo": { - "duration": 30, - "PrimaryCategory": "" + { + "ortbBid": { + "id": "apn-other-bid", + "impid": "imp-id-2", + "price": 0.6, + "w": 300, + "h": 500, + "crid": "creative-3", + "cat": [ + "IAB1-2" + ] + }, + "bidType": "video" } - }, - { - "ortbBid": { - "id": "apn-other-bid", - "impid": "imp-id-2", - "price": 0.6, - "w": 300, - "h": 500, - "crid": "creative-3", - "cat": [ - "IAB1-2" - ] - }, - "bidType": "video" - } - ] - } + ], + "seat": "appnexus" + } + ] } } }, @@ -188,8 +199,12 @@ ] }, "ext": { - "appnexus": { - "placementId": 1 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } } } }, @@ -201,8 +216,12 @@ ] }, "ext": { - "appnexus": { - "placementId": 1 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } } } } diff --git a/exchange/exchangetest/debuglog_enabled.json b/exchange/exchangetest/debuglog_enabled.json index 0073c47cefc..af77a640780 100644 --- a/exchange/exchangetest/debuglog_enabled.json +++ b/exchange/exchangetest/debuglog_enabled.json @@ -26,8 +26,12 @@ ] }, "ext": { - "appnexus": { - "placementId": 1 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } } } }, @@ -39,8 +43,12 @@ ] }, "ext": { - "appnexus": { - "placementId": 1 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } } } } @@ -65,42 +73,45 @@ "outgoingRequests": { "appnexus": { "mockResponse": { - "pbsSeatBid": { - "pbsBids": [ - { - "ortbBid": { - "id": "apn-bid", - "impid": "my-imp-id", - "price": 0.3, - "w": 200, - "h": 250, - "crid": "creative-1", - "cat": [ - "IAB1-1" - ] + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "cat": [ + "IAB1-1" + ] + }, + "bidType": "video", + "bidVideo": { + "duration": 30, + "PrimaryCategory": "" + } }, - "bidType": "video", - "bidVideo": { - "duration": 30, - "PrimaryCategory": "" + { + "ortbBid": { + "id": "apn-other-bid", + "impid": "imp-id-2", + "price": 0.6, + "w": 300, + "h": 500, + "crid": "creative-3", + "cat": [ + "IAB1-2" + ] + }, + "bidType": "video" } - }, - { - "ortbBid": { - "id": "apn-other-bid", - "impid": "imp-id-2", - "price": 0.6, - "w": 300, - "h": 500, - "crid": "creative-3", - "cat": [ - "IAB1-2" - ] - }, - "bidType": "video" - } - ] - } + ], + "seat": "appnexus" + } + ] } } }, @@ -190,8 +201,12 @@ ] }, "ext": { - "appnexus": { - "placementId": 1 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } } } }, @@ -203,8 +218,12 @@ ] }, "ext": { - "appnexus": { - "placementId": 1 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } } } } diff --git a/exchange/exchangetest/debuglog_enabled_no_bids.json b/exchange/exchangetest/debuglog_enabled_no_bids.json index b9bb15df7fb..f0ecffae644 100644 --- a/exchange/exchangetest/debuglog_enabled_no_bids.json +++ b/exchange/exchangetest/debuglog_enabled_no_bids.json @@ -26,8 +26,12 @@ ] }, "ext": { - "appnexus": { - "placementId": 1 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } } } }, @@ -39,8 +43,12 @@ ] }, "ext": { - "appnexus": { - "placementId": 1 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } } } } @@ -64,7 +72,9 @@ "outgoingRequests": { "appnexus": { "mockResponse": { - "pbsSeatBid": {} + "pbsSeatBids": [ + {} + ] } } }, diff --git a/exchange/exchangetest/eidpermissions-allowed-alias.json b/exchange/exchangetest/eidpermissions-allowed-alias.json index 65287169866..e1f91a7579d 100644 --- a/exchange/exchangetest/eidpermissions-allowed-alias.json +++ b/exchange/exchangetest/eidpermissions-allowed-alias.json @@ -7,10 +7,12 @@ }, "user": { "ext": { - "eids": [{ - "source": "source1", - "id": "anyId" - }] + "eids": [ + { + "source": "source1", + "id": "anyId" + } + ] } }, "ext": { @@ -19,24 +21,36 @@ "foo": "appnexus" }, "data": { - "eidpermissions": [{ - "source": "source1", - "bidders": ["foo"] - }] + "eidpermissions": [ + { + "source": "source1", + "bidders": [ + "foo" + ] + } + ] } } }, - "imp": [{ - "id": "my-imp-id", - "video": { - "mimes": ["video/mp4"] - }, - "ext": { - "foo": { - "placementId": 1 + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "foo": { + "placementId": 1 + } + } + } } } - }] + ] } }, "outgoingRequests": { @@ -49,76 +63,86 @@ }, "user": { "ext": { - "eids": [{ - "source": "source1", - "id": "anyId" - }] + "eids": [ + { + "source": "source1", + "id": "anyId" + } + ] } }, "ext": { "prebid": { - "aliases": { - "foo": "appnexus" - }, - "data": { - "eidpermissions": [{ - "source": "source1", - "bidders": ["foo"] - }] + "server": { + "datacenter": "Datacenter", + "externalurl": "http://hosturl.com", + "gvlid": 1 } } }, - "imp": [{ - "id": "my-imp-id", - "video": { - "mimes": ["video/mp4"] - }, - "ext": { - "bidder": { - "placementId": 1 + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } } } - }] - }, - "bidAdjustment": 1.0 + ] + } }, "mockResponse": { - "pbsSeatBid": { - "pbsBids": [{ - "ortbBid": { - "id": "apn-bid", - "impid": "my-imp-id", - "price": 0.3, - "w": 200, - "h": 250, - "crid": "creative-1" - }, - "bidType": "video" - }] - } + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1" + }, + "bidType": "video" + } + ], + "seat": "foo" + } + ] } } }, "response": { "bids": { "id": "some-request-id", - "seatbid": [{ - "seat": "foo", - "bid": [{ - "id": "apn-bid", - "impid": "my-imp-id", - "price": 0.3, - "w": 200, - "h": 250, - "crid": "creative-1", - "ext": { - "origbidcpm": 0.3, - "prebid": { - "type": "video" + "seatbid": [ + { + "seat": "foo", + "bid": [ + { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "origbidcpm": 0.3, + "prebid": { + "type": "video" + } + } } - } - }] - }] + ] + } + ] } } } \ No newline at end of file diff --git a/exchange/exchangetest/eidpermissions-allowed.json b/exchange/exchangetest/eidpermissions-allowed.json index 4e427ccae31..85a1cf1859b 100644 --- a/exchange/exchangetest/eidpermissions-allowed.json +++ b/exchange/exchangetest/eidpermissions-allowed.json @@ -7,33 +7,47 @@ }, "user": { "ext": { - "eids": [{ - "source": "source1", - "id": "anyId" - }] + "eids": [ + { + "source": "source1", + "id": "anyId" + } + ] } }, "ext": { "prebid": { "data": { - "eidpermissions": [{ - "source": "source1", - "bidders": ["appnexus"] - }] + "eidpermissions": [ + { + "source": "source1", + "bidders": [ + "appnexus" + ] + } + ] } } }, - "imp": [{ - "id": "my-imp-id", - "video": { - "mimes": ["video/mp4"] - }, - "ext": { - "appnexus": { - "placementId": 1 + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } + } } } - }] + ] } }, "outgoingRequests": { @@ -46,73 +60,86 @@ }, "user": { "ext": { - "eids": [{ - "source": "source1", - "id": "anyId" - }] + "eids": [ + { + "source": "source1", + "id": "anyId" + } + ] } }, "ext": { "prebid": { - "data": { - "eidpermissions": [{ - "source": "source1", - "bidders": ["appnexus"] - }] + "server": { + "datacenter": "Datacenter", + "externalurl": "http://hosturl.com", + "gvlid": 1 } } }, - "imp": [{ - "id": "my-imp-id", - "video": { - "mimes": ["video/mp4"] - }, - "ext": { - "bidder": { - "placementId": 1 + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } } } - }] - }, - "bidAdjustment": 1.0 + ] + } }, "mockResponse": { - "pbsSeatBid": { - "pbsBids": [{ - "ortbBid": { - "id": "apn-bid", - "impid": "my-imp-id", - "price": 0.3, - "w": 200, - "h": 250, - "crid": "creative-1" - }, - "bidType": "video" - }] - } + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1" + }, + "bidType": "video" + } + ], + "seat": "appnexus" + } + ] } } }, "response": { "bids": { "id": "some-request-id", - "seatbid": [{ - "seat": "appnexus", - "bid": [{ - "id": "apn-bid", - "impid": "my-imp-id", - "price": 0.3, - "w": 200, - "h": 250, - "crid": "creative-1", - "ext": { - "origbidcpm": 0.3, - "prebid": { - "type": "video" + "seatbid": [ + { + "seat": "appnexus", + "bid": [ + { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "origbidcpm": 0.3, + "prebid": { + "type": "video" + } + } } - } - }] - }] + ] + } + ] } } } \ No newline at end of file diff --git a/exchange/exchangetest/eidpermissions-denied.json b/exchange/exchangetest/eidpermissions-denied.json index d834ea54ddc..cb8144fe4fe 100644 --- a/exchange/exchangetest/eidpermissions-denied.json +++ b/exchange/exchangetest/eidpermissions-denied.json @@ -7,33 +7,47 @@ }, "user": { "ext": { - "eids": [{ - "source": "source1", - "id": "anyId" - }] + "eids": [ + { + "source": "source1", + "id": "anyId" + } + ] } }, "ext": { "prebid": { "data": { - "eidpermissions": [{ - "source": "source1", - "bidders": ["otherBidder"] - }] + "eidpermissions": [ + { + "source": "source1", + "bidders": [ + "otherBidder" + ] + } + ] } } }, - "imp": [{ - "id": "my-imp-id", - "video": { - "mimes": ["video/mp4"] - }, - "ext": { - "appnexus": { - "placementId": 1 + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } + } } } - }] + ] } }, "outgoingRequests": { @@ -44,69 +58,79 @@ "site": { "page": "test.somepage.com" }, - "user": { - }, + "user": {}, "ext": { "prebid": { - "data": { - "eidpermissions": [{ - "source": "source1", - "bidders": ["otherBidder"] - }] + "server": { + "datacenter": "Datacenter", + "externalurl": "http://hosturl.com", + "gvlid": 1 } } }, - "imp": [{ - "id": "my-imp-id", - "video": { - "mimes": ["video/mp4"] - }, - "ext": { - "bidder": { - "placementId": 1 + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } } } - }] - }, - "bidAdjustment": 1.0 + ] + } }, "mockResponse": { - "pbsSeatBid": { - "pbsBids": [{ - "ortbBid": { - "id": "apn-bid", - "impid": "my-imp-id", - "price": 0.3, - "w": 200, - "h": 250, - "crid": "creative-1" - }, - "bidType": "video" - }] - } + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1" + }, + "bidType": "video" + } + ], + "seat": "appnexus" + } + ] } } }, "response": { "bids": { "id": "some-request-id", - "seatbid": [{ - "seat": "appnexus", - "bid": [{ - "id": "apn-bid", - "impid": "my-imp-id", - "price": 0.3, - "w": 200, - "h": 250, - "crid": "creative-1", - "ext": { - "origbidcpm": 0.3, - "prebid": { - "type": "video" + "seatbid": [ + { + "seat": "appnexus", + "bid": [ + { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "origbidcpm": 0.3, + "prebid": { + "type": "video" + } + } } - } - }] - }] + ] + } + ] } } } \ No newline at end of file diff --git a/exchange/exchangetest/events-bid-account-off-request-off.json b/exchange/exchangetest/events-bid-account-off-request-off.json index 3bf1fc9ed4c..b15b33a8653 100644 --- a/exchange/exchangetest/events-bid-account-off-request-off.json +++ b/exchange/exchangetest/events-bid-account-off-request-off.json @@ -12,47 +12,53 @@ "id": "my-imp-id", "banner": {}, "ext": { - "appnexus": { - "placementId": 1 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } } } } ], "ext": { - "prebid": { - } + "prebid": {} } } }, "outgoingRequests": { "appnexus": { "mockResponse": { - "pbsSeatBid": { - "pbsBids": [ - { - "ortbBid": { - "id": "winning-bid", - "impid": "my-imp-id", - "price": 0.71, - "w": 200, - "h": 250, - "crid": "creative-1" - }, - "bidType": "banner" - }, - { - "ortbBid": { - "id": "losing-bid", - "impid": "my-imp-id", - "price": 0.21, - "w": 200, - "h": 250, - "crid": "creative-2" + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "winning-bid", + "impid": "my-imp-id", + "price": 0.71, + "w": 200, + "h": 250, + "crid": "creative-1" + }, + "bidType": "banner" }, - "bidType": "banner" - } - ] - } + { + "ortbBid": { + "id": "losing-bid", + "impid": "my-imp-id", + "price": 0.21, + "w": 200, + "h": 250, + "crid": "creative-2" + }, + "bidType": "banner" + } + ], + "seat": "appnexus" + } + ] } } }, @@ -96,4 +102,4 @@ ] } } -} +} \ No newline at end of file diff --git a/exchange/exchangetest/events-bid-account-off-request-on.json b/exchange/exchangetest/events-bid-account-off-request-on.json index 6838ec94262..8a6b8aa7e14 100644 --- a/exchange/exchangetest/events-bid-account-off-request-on.json +++ b/exchange/exchangetest/events-bid-account-off-request-on.json @@ -12,15 +12,19 @@ "id": "my-imp-id", "banner": {}, "ext": { - "appnexus": { - "placementId": 1 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } } } } ], "ext": { "prebid": { - "integration" : "testIntegrationType", + "integration": "testIntegrationType", "events": {} } } @@ -29,32 +33,35 @@ "outgoingRequests": { "appnexus": { "mockResponse": { - "pbsSeatBid": { - "pbsBids": [ - { - "ortbBid": { - "id": "winning-bid", - "impid": "my-imp-id", - "price": 0.71, - "w": 200, - "h": 250, - "crid": "creative-1" - }, - "bidType": "banner" - }, - { - "ortbBid": { - "id": "losing-bid", - "impid": "my-imp-id", - "price": 0.21, - "w": 200, - "h": 250, - "crid": "creative-2" + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "winning-bid", + "impid": "my-imp-id", + "price": 0.71, + "w": 200, + "h": 250, + "crid": "creative-1" + }, + "bidType": "banner" }, - "bidType": "banner" - } - ] - } + { + "ortbBid": { + "id": "losing-bid", + "impid": "my-imp-id", + "price": 0.21, + "w": 200, + "h": 250, + "crid": "creative-2" + }, + "bidType": "banner" + } + ], + "seat": "appnexus" + } + ] } } }, @@ -106,4 +113,4 @@ ] } } -} +} \ No newline at end of file diff --git a/exchange/exchangetest/events-bid-account-on-request-off.json b/exchange/exchangetest/events-bid-account-on-request-off.json index 677fa21f1f3..5a2fa6ae4d2 100644 --- a/exchange/exchangetest/events-bid-account-on-request-off.json +++ b/exchange/exchangetest/events-bid-account-on-request-off.json @@ -12,15 +12,19 @@ "id": "my-imp-id", "banner": {}, "ext": { - "appnexus": { - "placementId": 1 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } } } } ], "ext": { "prebid": { - "integration" : "testIntegrationType" + "integration": "testIntegrationType" } } } @@ -28,32 +32,35 @@ "outgoingRequests": { "appnexus": { "mockResponse": { - "pbsSeatBid": { - "pbsBids": [ - { - "ortbBid": { - "id": "winning-bid", - "impid": "my-imp-id", - "price": 0.71, - "w": 200, - "h": 250, - "crid": "creative-1" - }, - "bidType": "banner" - }, - { - "ortbBid": { - "id": "losing-bid", - "impid": "my-imp-id", - "price": 0.21, - "w": 200, - "h": 250, - "crid": "creative-2" + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "winning-bid", + "impid": "my-imp-id", + "price": 0.71, + "w": 200, + "h": 250, + "crid": "creative-1" + }, + "bidType": "banner" }, - "bidType": "banner" - } - ] - } + { + "ortbBid": { + "id": "losing-bid", + "impid": "my-imp-id", + "price": 0.21, + "w": 200, + "h": 250, + "crid": "creative-2" + }, + "bidType": "banner" + } + ], + "seat": "appnexus" + } + ] } } }, @@ -105,4 +112,4 @@ ] } } -} +} \ No newline at end of file diff --git a/exchange/exchangetest/events-vast-account-off-request-off.json b/exchange/exchangetest/events-vast-account-off-request-off.json index 9eff3e81b88..ac9b0c9d95a 100644 --- a/exchange/exchangetest/events-vast-account-off-request-off.json +++ b/exchange/exchangetest/events-vast-account-off-request-off.json @@ -12,11 +12,15 @@ "id": "my-imp-id", "video": {}, "ext": { - "appnexus": { - "placementId": 1 - }, - "audienceNetwork": { - "placementId": 1 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + }, + "audienceNetwork": { + "placementId": 1 + } + } } } } @@ -35,55 +39,61 @@ "appnexus": { "modifyingVastXmlAllowed": true, "mockResponse": { - "pbsSeatBid": { - "pbsBids": [ - { - "ortbBid": { - "id": "winning-bid", - "nurl": "http://domain.com/winning-bid", - "impid": "my-imp-id", - "price": 0.71, - "w": 200, - "h": 250, - "crid": "creative-1" - }, - "bidType": "video" - }, - { - "ortbBid": { - "id": "losing-bid", - "nurl": "http://domain.com/losing-bid", - "impid": "my-imp-id", - "price": 0.21, - "w": 200, - "h": 250, - "crid": "creative-2" + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "winning-bid", + "nurl": "http://domain.com/winning-bid", + "impid": "my-imp-id", + "price": 0.71, + "w": 200, + "h": 250, + "crid": "creative-1" + }, + "bidType": "video" }, - "bidType": "video" - } - ] - } + { + "ortbBid": { + "id": "losing-bid", + "nurl": "http://domain.com/losing-bid", + "impid": "my-imp-id", + "price": 0.21, + "w": 200, + "h": 250, + "crid": "creative-2" + }, + "bidType": "video" + } + ], + "seat": "appnexus" + } + ] } }, "audienceNetwork": { "modifyingVastXmlAllowed": false, "mockResponse": { - "pbsSeatBid": { - "pbsBids": [ - { - "ortbBid": { - "id": "contending-bid", - "nurl": "http://domain.com/contending-bid", - "impid": "my-imp-id", - "price": 0.51, - "w": 200, - "h": 250, - "crid": "creative-4" - }, - "bidType": "video" - } - ] - } + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "contending-bid", + "nurl": "http://domain.com/contending-bid", + "impid": "my-imp-id", + "price": 0.51, + "w": 200, + "h": 250, + "crid": "creative-4" + }, + "bidType": "video" + } + ], + "seat": "audienceNetwork" + } + ] } } }, @@ -127,12 +137,12 @@ "prebid": { "type": "video", "targeting": { - "hb_bidder": "appnexus", - "hb_cache_host": "www.pbcserver.com", - "hb_cache_path": "/pbcache/endpoint", - "hb_env": "mobile-app", - "hb_pb": "0.70", - "hb_size": "200x250" + "hb_bidder": "appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_env": "mobile-app", + "hb_pb": "0.70", + "hb_size": "200x250" } } } diff --git a/exchange/exchangetest/events-vast-account-off-request-on.json b/exchange/exchangetest/events-vast-account-off-request-on.json index 0b30219d37c..23d1924a295 100644 --- a/exchange/exchangetest/events-vast-account-off-request-on.json +++ b/exchange/exchangetest/events-vast-account-off-request-on.json @@ -16,11 +16,15 @@ "id": "my-imp-id", "video": {}, "ext": { - "appnexus": { - "placementId": 1 - }, - "audienceNetwork": { - "placementId": 1 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + }, + "audienceNetwork": { + "placementId": 1 + } + } } } } @@ -31,7 +35,7 @@ "includewinners": true, "includebidderkeys": false }, - "integration" : "testIntegrationType", + "integration": "testIntegrationType", "events": {} } } @@ -41,55 +45,61 @@ "appnexus": { "modifyingVastXmlAllowed": true, "mockResponse": { - "pbsSeatBid": { - "pbsBids": [ - { - "ortbBid": { - "id": "winning-bid", - "nurl": "http://domain.com/winning-bid", - "impid": "my-imp-id", - "price": 0.71, - "w": 200, - "h": 250, - "crid": "creative-1" - }, - "bidType": "video" - }, - { - "ortbBid": { - "id": "losing-bid", - "nurl": "http://domain.com/losing-bid", - "impid": "my-imp-id", - "price": 0.21, - "w": 200, - "h": 250, - "crid": "creative-2" + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "winning-bid", + "nurl": "http://domain.com/winning-bid", + "impid": "my-imp-id", + "price": 0.71, + "w": 200, + "h": 250, + "crid": "creative-1" + }, + "bidType": "video" }, - "bidType": "video" - } - ] - } + { + "ortbBid": { + "id": "losing-bid", + "nurl": "http://domain.com/losing-bid", + "impid": "my-imp-id", + "price": 0.21, + "w": 200, + "h": 250, + "crid": "creative-2" + }, + "bidType": "video" + } + ], + "seat": "appnexus" + } + ] } }, "audienceNetwork": { "modifyingVastXmlAllowed": false, "mockResponse": { - "pbsSeatBid": { - "pbsBids": [ - { - "ortbBid": { - "id": "contending-bid", - "nurl": "http://domain.com/contending-bid", - "impid": "my-imp-id", - "price": 0.51, - "w": 200, - "h": 250, - "crid": "creative-4" - }, - "bidType": "video" - } - ] - } + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "contending-bid", + "nurl": "http://domain.com/contending-bid", + "impid": "my-imp-id", + "price": 0.51, + "w": 200, + "h": 250, + "crid": "creative-4" + }, + "bidType": "video" + } + ], + "seat": "audienceNetwork" + } + ] } } }, @@ -136,12 +146,12 @@ "bidid": "mock_uuid", "type": "video", "targeting": { - "hb_bidder": "appnexus", - "hb_cache_host": "www.pbcserver.com", - "hb_cache_path": "/pbcache/endpoint", - "hb_env": "mobile-app", - "hb_pb": "0.70", - "hb_size": "200x250" + "hb_bidder": "appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_env": "mobile-app", + "hb_pb": "0.70", + "hb_size": "200x250" } } } diff --git a/exchange/exchangetest/events-vast-account-on-request-off.json b/exchange/exchangetest/events-vast-account-on-request-off.json index 5a6d04510a7..e75e1ec6d7d 100644 --- a/exchange/exchangetest/events-vast-account-on-request-off.json +++ b/exchange/exchangetest/events-vast-account-on-request-off.json @@ -12,11 +12,15 @@ "id": "my-imp-id", "video": {}, "ext": { - "appnexus": { - "placementId": 1 - }, - "audienceNetwork": { - "placementId": 1 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + }, + "audienceNetwork": { + "placementId": 1 + } + } } } } @@ -27,7 +31,7 @@ "includewinners": true, "includebidderkeys": false }, - "integration" : "testIntegrationType" + "integration": "testIntegrationType" } } } @@ -36,55 +40,61 @@ "appnexus": { "modifyingVastXmlAllowed": true, "mockResponse": { - "pbsSeatBid": { - "pbsBids": [ - { - "ortbBid": { - "id": "winning-bid", - "nurl": "http://domain.com/winning-bid", - "impid": "my-imp-id", - "price": 0.71, - "w": 200, - "h": 250, - "crid": "creative-1" - }, - "bidType": "video" - }, - { - "ortbBid": { - "id": "losing-bid", - "nurl": "http://domain.com/losing-bid", - "impid": "my-imp-id", - "price": 0.21, - "w": 200, - "h": 250, - "crid": "creative-2" + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "winning-bid", + "nurl": "http://domain.com/winning-bid", + "impid": "my-imp-id", + "price": 0.71, + "w": 200, + "h": 250, + "crid": "creative-1" + }, + "bidType": "video" }, - "bidType": "video" - } - ] - } + { + "ortbBid": { + "id": "losing-bid", + "nurl": "http://domain.com/losing-bid", + "impid": "my-imp-id", + "price": 0.21, + "w": 200, + "h": 250, + "crid": "creative-2" + }, + "bidType": "video" + } + ], + "seat": "appnexus" + } + ] } }, "audienceNetwork": { "modifyingVastXmlAllowed": false, "mockResponse": { - "pbsSeatBid": { - "pbsBids": [ - { - "ortbBid": { - "id": "contending-bid", - "nurl": "http://domain.com/contending-bid", - "impid": "my-imp-id", - "price": 0.51, - "w": 200, - "h": 250, - "crid": "creative-4" - }, - "bidType": "video" - } - ] - } + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "contending-bid", + "nurl": "http://domain.com/contending-bid", + "impid": "my-imp-id", + "price": 0.51, + "w": 200, + "h": 250, + "crid": "creative-4" + }, + "bidType": "video" + } + ], + "seat": "audienceNetwork" + } + ] } } }, @@ -129,12 +139,12 @@ "prebid": { "type": "video", "targeting": { - "hb_bidder": "appnexus", - "hb_cache_host": "www.pbcserver.com", - "hb_cache_path": "/pbcache/endpoint", - "hb_env": "mobile-app", - "hb_pb": "0.70", - "hb_size": "200x250" + "hb_bidder": "appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_env": "mobile-app", + "hb_pb": "0.70", + "hb_size": "200x250" } } } diff --git a/exchange/exchangetest/explicit-buyeruid.json b/exchange/exchangetest/explicit-buyeruid.json index 04f69571bf9..4969ef631d7 100644 --- a/exchange/exchangetest/explicit-buyeruid.json +++ b/exchange/exchangetest/explicit-buyeruid.json @@ -19,11 +19,17 @@ { "id": "my-imp-id", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { - "appnexus": { - "placementId": 1 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } } } } @@ -48,7 +54,9 @@ { "id": "my-imp-id", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { "bidder": { @@ -57,12 +65,13 @@ } } ] - }, - "bidAdjustment": 1.0 + } }, "mockResponse": { - "errors": ["appnexus-error"] + "errors": [ + "appnexus-error" + ] } } } -} +} \ No newline at end of file diff --git a/exchange/exchangetest/extra-bids-with-aliases-adaptercode.json b/exchange/exchangetest/extra-bids-with-aliases-adaptercode.json new file mode 100644 index 00000000000..7b49cb80570 --- /dev/null +++ b/exchange/exchangetest/extra-bids-with-aliases-adaptercode.json @@ -0,0 +1,247 @@ +{ + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "pmbidder": { + "publisherId": 5890 + } + } + } + } + }, + { + "id": "imp-id-2", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "pmbidder": { + "publisherId": 5890 + } + } + } + } + } + ], + "ext": { + "prebid": { + "targeting": { + "includewinners": true, + "includebidderkeys": true + }, + "aliases": { + "pmbidder": "pubmatic" + } + } + } + } + }, + "outgoingRequests": { + "pmbidder": { + "mockResponse": { + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "winning-bid", + "impid": "my-imp-id", + "price": 0.71, + "w": 200, + "h": 250, + "crid": "creative-1" + }, + "bidType": "video", + "bidMeta": { + "adaptercode": "pmbidder" + } + }, + { + "ortbBid": { + "id": "losing-bid", + "impid": "my-imp-id", + "price": 0.21, + "w": 200, + "h": 250, + "crid": "creative-2" + }, + "bidType": "video", + "bidMeta": { + "adaptercode": "pmbidder" + } + }, + { + "ortbBid": { + "id": "other-bid", + "impid": "imp-id-2", + "price": 0.61, + "w": 300, + "h": 500, + "crid": "creative-3" + }, + "bidType": "video", + "bidMeta": { + "adaptercode": "pmbidder" + } + } + ], + "seat": "groupm" + }, + { + "pbsBids": [ + { + "ortbBid": { + "id": "contending-bid", + "impid": "my-imp-id", + "price": 0.51, + "w": 200, + "h": 250, + "crid": "creative-4" + }, + "bidType": "video", + "bidMeta": { + "adaptercode": "pmbidder" + } + } + ], + "seat": "pmbidder" + } + ] + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [ + { + "seat": "pmbidder", + "bid": [ + { + "id": "contending-bid", + "impid": "my-imp-id", + "price": 0.51, + "w": 200, + "h": 250, + "crid": "creative-4", + "ext": { + "origbidcpm": 0.51, + "prebid": { + "meta": { + "adaptercode": "pmbidder" + }, + "type": "video", + "targeting": { + "hb_bidder_pmbidder": "pmbidder", + "hb_cache_host_pmbidd": "www.pbcserver.com", + "hb_cache_path_pmbidd": "/pbcache/endpoint", + "hb_pb_pmbidder": "0.50", + "hb_size_pmbidder": "200x250" + } + } + } + } + ] + }, + { + "seat": "groupm", + "bid": [ + { + "id": "winning-bid", + "impid": "my-imp-id", + "price": 0.71, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "origbidcpm": 0.71, + "prebid": { + "meta": { + "adaptercode": "pmbidder" + }, + "type": "video", + "targeting": { + "hb_bidder": "groupm", + "hb_bidder_groupm": "groupm", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_groupm": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_groupm": "/pbcache/endpoint", + "hb_pb": "0.70", + "hb_pb_groupm": "0.70", + "hb_size": "200x250", + "hb_size_groupm": "200x250" + } + } + } + }, + { + "id": "losing-bid", + "impid": "my-imp-id", + "price": 0.21, + "w": 200, + "h": 250, + "crid": "creative-2", + "ext": { + "origbidcpm": 0.21, + "prebid": { + "meta": { + "adaptercode": "pmbidder" + }, + "type": "video" + } + } + }, + { + "id": "other-bid", + "impid": "imp-id-2", + "price": 0.61, + "w": 300, + "h": 500, + "crid": "creative-3", + "ext": { + "origbidcpm": 0.61, + "prebid": { + "meta": { + "adaptercode": "pmbidder" + }, + "type": "video", + "targeting": { + "hb_bidder": "groupm", + "hb_bidder_groupm": "groupm", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_groupm": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_groupm": "/pbcache/endpoint", + "hb_pb": "0.60", + "hb_pb_groupm": "0.60", + "hb_size": "300x500", + "hb_size_groupm": "300x500" + } + } + } + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/extra-bids.json b/exchange/exchangetest/extra-bids.json new file mode 100644 index 00000000000..67a9b0858bd --- /dev/null +++ b/exchange/exchangetest/extra-bids.json @@ -0,0 +1,461 @@ +{ + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "pubmatic": { + "publisherId": 5890 + }, + "appnexus": { + "placementId": 1 + } + } + } + } + }, + { + "id": "imp-id-2", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "pubmatic": { + "publisherId": 5890 + }, + "appnexus": { + "placementId": 1 + } + } + } + } + } + ], + "ext": { + "prebid": { + "targeting": { + "includewinners": true, + "includebidderkeys": true + }, + "alternatebiddercodes": { + "enabled": true, + "bidders": { + "pubmatic": { + "enabled": true, + "allowedbiddercodes": [ + "groupm" + ] + } + } + }, + "server": { + "datacenter": "Datacenter", + "externalurl": "http://hosturl.com", + "gvlid": 1 + } + } + } + } + }, + "outgoingRequests": { + "pubmatic": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": 5890 + } + } + }, + { + "id": "imp-id-2", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": 5890 + } + } + } + ], + "ext": { + "prebid": { + "alternatebiddercodes": { + "enabled": true, + "bidders": { + "pubmatic": { + "enabled": true, + "allowedbiddercodes": [ + "groupm" + ] + } + } + }, + "server": { + "datacenter": "Datacenter", + "externalurl": "http://hosturl.com", + "gvlid": 1 + } + } + } + } + }, + "mockResponse": { + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "winning-bid", + "impid": "my-imp-id", + "price": 0.71, + "w": 200, + "h": 250, + "crid": "creative-1" + }, + "bidType": "video", + "bidMeta": { + "adaptercode": "pubmatic" + } + }, + { + "ortbBid": { + "id": "losing-bid", + "impid": "my-imp-id", + "price": 0.21, + "w": 200, + "h": 250, + "crid": "creative-2" + }, + "bidType": "video", + "bidMeta": { + "adaptercode": "pubmatic" + } + }, + { + "ortbBid": { + "id": "other-bid", + "impid": "imp-id-2", + "price": 0.61, + "w": 300, + "h": 500, + "crid": "creative-3" + }, + "bidType": "video", + "bidMeta": { + "adaptercode": "pubmatic" + } + } + ], + "seat": "pubmatic" + }, + { + "pbsBids": [ + { + "ortbBid": { + "id": "contending-bid", + "impid": "my-imp-id", + "price": 0.51, + "w": 200, + "h": 250, + "crid": "creative-4" + }, + "bidType": "video", + "bidMeta": { + "adaptercode": "pubmatic" + } + } + ], + "seat": "groupm" + } + ] + } + }, + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + }, + { + "id": "imp-id-2", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + } + ], + "ext": { + "prebid": { + "alternatebiddercodes": { + "enabled": true, + "bidders": null + }, + "server": { + "datacenter": "Datacenter", + "externalurl": "http://hosturl.com", + "gvlid": 1 + } + } + } + } + }, + "mockResponse": { + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-a-1" + }, + "bidType": "banner", + "bidMeta": { + "adaptercode": "appnexus" + } + } + ], + "seat": "appnexus" + }, + { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid-2", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-a-2" + }, + "bidType": "banner", + "bidMeta": { + "adaptercode": "appnexus" + } + } + ], + "seat": "groupm" + } + ] + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [ + { + "seat": "groupm", + "bid": [ + { + "id": "contending-bid", + "impid": "my-imp-id", + "price": 0.51, + "w": 200, + "h": 250, + "crid": "creative-4", + "ext": { + "origbidcpm": 0.51, + "prebid": { + "meta": { + "adaptercode": "pubmatic" + }, + "type": "video", + "targeting": { + "hb_bidder_groupm": "groupm", + "hb_cache_host_groupm": "www.pbcserver.com", + "hb_cache_path_groupm": "/pbcache/endpoint", + "hb_pb_groupm": "0.50", + "hb_size_groupm": "200x250" + } + } + } + }, + { + "id": "apn-bid-2", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-a-2", + "ext": { + "origbidcpm": 0.3, + "prebid": { + "meta": { + "adaptercode": "appnexus" + }, + "type": "banner" + } + } + } + ] + }, + { + "seat": "pubmatic", + "bid": [ + { + "id": "winning-bid", + "impid": "my-imp-id", + "price": 0.71, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "origbidcpm": 0.71, + "prebid": { + "meta": { + "adaptercode": "pubmatic" + }, + "type": "video", + "targeting": { + "hb_bidder": "pubmatic", + "hb_bidder_pubmatic": "pubmatic", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_pubmat": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_pubmat": "/pbcache/endpoint", + "hb_pb": "0.70", + "hb_pb_pubmatic": "0.70", + "hb_size": "200x250", + "hb_size_pubmatic": "200x250" + } + } + } + }, + { + "id": "losing-bid", + "impid": "my-imp-id", + "price": 0.21, + "w": 200, + "h": 250, + "crid": "creative-2", + "ext": { + "origbidcpm": 0.21, + "prebid": { + "meta": { + "adaptercode": "pubmatic" + }, + "type": "video" + } + } + }, + { + "id": "other-bid", + "impid": "imp-id-2", + "price": 0.61, + "w": 300, + "h": 500, + "crid": "creative-3", + "ext": { + "origbidcpm": 0.61, + "prebid": { + "meta": { + "adaptercode": "pubmatic" + }, + "type": "video", + "targeting": { + "hb_bidder": "pubmatic", + "hb_bidder_pubmatic": "pubmatic", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_pubmat": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_pubmat": "/pbcache/endpoint", + "hb_pb": "0.60", + "hb_pb_pubmatic": "0.60", + "hb_size": "300x500", + "hb_size_pubmatic": "300x500" + } + } + } + } + ] + }, + { + "seat": "appnexus", + "bid": [ + { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-a-1", + "ext": { + "origbidcpm": 0.3, + "prebid": { + "meta": { + "adaptercode": "appnexus" + }, + "type": "banner", + "targeting": { + "hb_bidder_appnexus": "appnexus", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_pb_appnexus": "0.20", + "hb_size_appnexus": "200x500" + } + } + } + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/firstpartydata-imp-ext-multiple-bidders.json b/exchange/exchangetest/firstpartydata-imp-ext-multiple-bidders.json deleted file mode 100644 index c16fbc8649c..00000000000 --- a/exchange/exchangetest/firstpartydata-imp-ext-multiple-bidders.json +++ /dev/null @@ -1,185 +0,0 @@ -{ - "requestType": "openrtb2-web", - "incomingRequest": { - "ortbRequest": { - "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": 1 - }, - "rubicon": { - "accountId": 1, - "siteId": 2, - "zoneId": 3 - }, - "data": { - "keywords": "prebid server example" - }, - "context": { - "data": { - "keywords": "another prebid server example" - } - } - } - }] - } - }, - "outgoingRequests": { - "appnexus": { - "expectRequest": { - "ortbRequest": { - "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": { - "bidder": { - "placementId": 1 - }, - "data": { - "keywords": "prebid server example" - }, - "context": { - "data": { - "keywords": "another prebid server example" - } - } - } - }] - }, - "bidAdjustment": 1.0 - }, - "mockResponse": { - "pbsSeatBid": { - "pbsBids": [{ - "ortbBid": { - "id": "apn-bid", - "impid": "some-imp-id", - "price": 0.3, - "w": 200, - "h": 500, - "crid": "creative-1" - }, - "bidType": "banner" - }] - } - } - }, - "rubicon": { - "expectRequest": { - "ortbRequest": { - "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": { - "bidder": { - "accountId": 1, - "siteId": 2, - "zoneId": 3 - }, - "data": { - "keywords": "prebid server example" - }, - "context": { - "data": { - "keywords": "another prebid server example" - } - } - } - }] - }, - "bidAdjustment": 1.0 - }, - "mockResponse": { - "pbsSeatBid": { - "pbsBids": [{ - "ortbBid": { - "id": "rubi-bid", - "impid": "some-imp-id", - "price": 0.4, - "w": 200, - "h": 500, - "crid": "creative-2" - }, - "bidType": "banner" - }] - } - } - } - }, - "response": { - "bids": { - "id": "some-request-id", - "seatbid": [{ - "seat": "appnexus", - "bid": [{ - "id": "apn-bid", - "impid": "some-imp-id", - "price": 0.3, - "w": 200, - "h": 500, - "crid": "creative-1", - "ext": { - "origbidcpm": 0.3, - "prebid": { - "type": "banner" - } - } - }] - }, { - "seat": "rubicon", - "bid": [{ - "id": "rubi-bid", - "impid": "some-imp-id", - "price": 0.4, - "w": 200, - "h": 500, - "crid": "creative-2", - "ext": { - "origbidcpm": 0.4, - "prebid": { - "type": "banner" - } - } - }] - }] - } - } -} \ No newline at end of file diff --git a/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json b/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json index aaaac94c208..f15fea59d87 100644 --- a/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json +++ b/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json @@ -6,40 +6,45 @@ "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": 1 + "imp": [ + { + "id": "some-imp-id", + "banner": { + "format": [ + { + "w": 600, + "h": 500 }, - "rubicon": { - "accountId": 1, - "siteId": 2, - "zoneId": 3 + { + "w": 300, + "h": 600 } - } - }, - "data": { - "keywords": "prebid server example" + ] }, - "context": { + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + }, + "rubicon": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + } + } + }, "data": { - "keywords": "another prebid server example" + "keywords": "prebid server example" + }, + "context": { + "data": { + "keywords": "another prebid server example" + } } } } - }] + ] } }, "outgoingRequests": { @@ -50,48 +55,57 @@ "site": { "page": "test.somepage.com" }, - "imp": [{ - "id": "some-imp-id", - "banner": { - "format": [{ - "w": 600, - "h": 500 - }, { - "w": 300, - "h": 600 - }] - }, - "ext": { - "bidder": { - "placementId": 1 - }, - "data": { - "keywords": "prebid server example" + "imp": [ + { + "id": "some-imp-id", + "banner": { + "format": [ + { + "w": 600, + "h": 500 + }, + { + "w": 300, + "h": 600 + } + ] }, - "context": { + "ext": { + "bidder": { + "placementId": 1 + }, "data": { - "keywords": "another prebid server example" + "keywords": "prebid server example" + }, + "context": { + "data": { + "keywords": "another prebid server example" + } } } } - }] - }, - "bidAdjustment": 1.0 + ] + } }, "mockResponse": { - "pbsSeatBid": { - "pbsBids": [{ - "ortbBid": { - "id": "apn-bid", - "impid": "some-imp-id", - "price": 0.3, - "w": 200, - "h": 500, - "crid": "creative-1" - }, - "bidType": "banner" - }] - } + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid", + "impid": "some-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-1" + }, + "bidType": "banner" + } + ], + "seat": "appnexus" + } + ] } }, "rubicon": { @@ -101,89 +115,105 @@ "site": { "page": "test.somepage.com" }, - "imp": [{ - "id": "some-imp-id", - "banner": { - "format": [{ - "w": 600, - "h": 500 - }, { - "w": 300, - "h": 600 - }] - }, - "ext": { - "bidder": { - "accountId": 1, - "siteId": 2, - "zoneId": 3 - }, - "data": { - "keywords": "prebid server example" + "imp": [ + { + "id": "some-imp-id", + "banner": { + "format": [ + { + "w": 600, + "h": 500 + }, + { + "w": 300, + "h": 600 + } + ] }, - "context": { + "ext": { + "bidder": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + }, "data": { - "keywords": "another prebid server example" + "keywords": "prebid server example" + }, + "context": { + "data": { + "keywords": "another prebid server example" + } } } } - }] - }, - "bidAdjustment": 1.0 + ] + } }, "mockResponse": { - "pbsSeatBid": { - "pbsBids": [{ - "ortbBid": { - "id": "rubi-bid", - "impid": "some-imp-id", - "price": 0.4, - "w": 200, - "h": 500, - "crid": "creative-2" - }, - "bidType": "banner" - }] - } + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "rubi-bid", + "impid": "some-imp-id", + "price": 0.4, + "w": 200, + "h": 500, + "crid": "creative-2" + }, + "bidType": "banner" + } + ], + "seat": "rubicon" + } + ] } } }, "response": { "bids": { "id": "some-request-id", - "seatbid": [{ - "seat": "appnexus", - "bid": [{ - "id": "apn-bid", - "impid": "some-imp-id", - "price": 0.3, - "w": 200, - "h": 500, - "crid": "creative-1", - "ext": { - "origbidcpm": 0.3, - "prebid": { - "type": "banner" + "seatbid": [ + { + "seat": "appnexus", + "bid": [ + { + "id": "apn-bid", + "impid": "some-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-1", + "ext": { + "origbidcpm": 0.3, + "prebid": { + "type": "banner" + } + } } - } - }] - }, { - "seat": "rubicon", - "bid": [{ - "id": "rubi-bid", - "impid": "some-imp-id", - "price": 0.4, - "w": 200, - "h": 500, - "crid": "creative-2", - "ext": { - "origbidcpm": 0.4, - "prebid": { - "type": "banner" + ] + }, + { + "seat": "rubicon", + "bid": [ + { + "id": "rubi-bid", + "impid": "some-imp-id", + "price": 0.4, + "w": 200, + "h": 500, + "crid": "creative-2", + "ext": { + "origbidcpm": 0.4, + "prebid": { + "type": "banner" + } + } } - } - }] - }] + ] + } + ] } } } \ No newline at end of file diff --git a/exchange/exchangetest/firstpartydata-imp-ext-one-bidder.json b/exchange/exchangetest/firstpartydata-imp-ext-one-bidder.json deleted file mode 100644 index 9e42e5aee94..00000000000 --- a/exchange/exchangetest/firstpartydata-imp-ext-one-bidder.json +++ /dev/null @@ -1,111 +0,0 @@ -{ - "requestType": "openrtb2-web", - "incomingRequest": { - "ortbRequest": { - "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": 1 - }, - "data": { - "keywords": "prebid server example" - }, - "context": { - "data": { - "keywords": "another prebid server example" - } - } - } - }] - } - }, - "outgoingRequests": { - "appnexus": { - "expectRequest": { - "ortbRequest": { - "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": { - "bidder": { - "placementId": 1 - }, - "data": { - "keywords": "prebid server example" - }, - "context": { - "data": { - "keywords": "another prebid server example" - } - } - } - }] - }, - "bidAdjustment": 1.0 - }, - "mockResponse": { - "pbsSeatBid": { - "pbsBids": [{ - "ortbBid": { - "id": "apn-bid", - "impid": "some-imp-id", - "price": 0.3, - "w": 200, - "h": 500, - "crid": "creative-1" - }, - "bidType": "banner" - }] - } - } - } - }, - "response": { - "bids": { - "id": "some-request-id", - "seatbid": [{ - "seat": "appnexus", - "bid": [{ - "id": "apn-bid", - "impid": "some-imp-id", - "price": 0.3, - "w": 200, - "h": 500, - "crid": "creative-1", - "ext": { - "origbidcpm": 0.3, - "prebid": { - "type": "banner" - } - } - }] - }] - } - } -} \ No newline at end of file diff --git a/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json b/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json index 87ff8a8a0ed..9eb6a77eed5 100644 --- a/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json +++ b/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json @@ -6,35 +6,40 @@ "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": 1 + "imp": [ + { + "id": "some-imp-id", + "banner": { + "format": [ + { + "w": 600, + "h": 500 + }, + { + "w": 300, + "h": 600 } - } + ] }, - "data": { - "keywords": "prebid server example" - }, - "context": { + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } + }, "data": { - "keywords": "another prebid server example" + "keywords": "prebid server example" + }, + "context": { + "data": { + "keywords": "another prebid server example" + } } } } - }] + ] } }, "outgoingRequests": { @@ -45,71 +50,84 @@ "site": { "page": "test.somepage.com" }, - "imp": [{ - "id": "some-imp-id", - "banner": { - "format": [{ - "w": 600, - "h": 500 - }, { - "w": 300, - "h": 600 - }] - }, - "ext": { - "bidder": { - "placementId": 1 + "imp": [ + { + "id": "some-imp-id", + "banner": { + "format": [ + { + "w": 600, + "h": 500 + }, + { + "w": 300, + "h": 600 + } + ] }, - "data": { - "keywords": "prebid server example" - }, - "context": { + "ext": { + "bidder": { + "placementId": 1 + }, "data": { - "keywords": "another prebid server example" + "keywords": "prebid server example" + }, + "context": { + "data": { + "keywords": "another prebid server example" + } } } } - }] - }, - "bidAdjustment": 1.0 + ] + } }, "mockResponse": { - "pbsSeatBid": { - "pbsBids": [{ - "ortbBid": { - "id": "apn-bid", - "impid": "some-imp-id", - "price": 0.3, - "w": 200, - "h": 500, - "crid": "creative-1" - }, - "bidType": "banner" - }] - } + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid", + "impid": "some-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-1" + }, + "bidType": "banner" + } + ], + "seat": "appnexus" + } + ] } } }, "response": { "bids": { "id": "some-request-id", - "seatbid": [{ - "seat": "appnexus", - "bid": [{ - "id": "apn-bid", - "impid": "some-imp-id", - "price": 0.3, - "w": 200, - "h": 500, - "crid": "creative-1", - "ext": { - "origbidcpm": 0.3, - "prebid": { - "type": "banner" + "seatbid": [ + { + "seat": "appnexus", + "bid": [ + { + "id": "apn-bid", + "impid": "some-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-1", + "ext": { + "origbidcpm": 0.3, + "prebid": { + "type": "banner" + } + } } - } - }] - }] + ] + } + ] } } } \ No newline at end of file diff --git a/exchange/exchangetest/firstpartydata-multibidder-config-invalid.json b/exchange/exchangetest/firstpartydata-multibidder-config-invalid.json index c7d9781e827..778ec791252 100644 --- a/exchange/exchangetest/firstpartydata-multibidder-config-invalid.json +++ b/exchange/exchangetest/firstpartydata-multibidder-config-invalid.json @@ -6,35 +6,44 @@ "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": 1 + "imp": [ + { + "id": "some-imp-id", + "banner": { + "format": [ + { + "w": 600, + "h": 500 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } } } } } - }], - "ext":{ + ], + "ext": { "prebid": { "data": { - "bidders": ["appnexus"] + "bidders": [ + "appnexus" + ] }, "bidderconfig": [ { - "bidders": ["appnexus"], + "bidders": [ + "appnexus" + ], "config": { "ortb2": { "app": { @@ -45,7 +54,9 @@ } }, { - "bidders": ["appnexus"], + "bidders": [ + "appnexus" + ], "config": { "ortb2": { "app": { diff --git a/exchange/exchangetest/firstpartydata-multibidder-config-valid.json b/exchange/exchangetest/firstpartydata-multibidder-config-valid.json index d7c8946feb6..6ea2154543c 100644 --- a/exchange/exchangetest/firstpartydata-multibidder-config-valid.json +++ b/exchange/exchangetest/firstpartydata-multibidder-config-valid.json @@ -22,13 +22,17 @@ ] }, "ext": { - "appnexus": { - "placementId": 1 - }, - "rubicon": { - "accountId": 1, - "siteId": 2, - "zoneId": 3 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + }, + "rubicon": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + } + } } } } @@ -105,14 +109,9 @@ "page": "fpd_appnexus_site_page" }, "ext": { - "prebid": { - "data": { - "eidpermissions": null - } - } + "prebid": {} } - }, - "bidAdjustment": 1.0 + } } }, "rubicon": { @@ -148,16 +147,10 @@ "page": "fpd_rubicon_site_page" }, "ext": { - "prebid": { - "data": { - "eidpermissions": null - } - } + "prebid": {} } - }, - "bidAdjustment": 1.0 + } } } } - } \ No newline at end of file diff --git a/exchange/exchangetest/gdpr-geo-eu-off-device.json b/exchange/exchangetest/gdpr-geo-eu-off-device.json index f704cdd5c8e..7a3dd95b3ae 100644 --- a/exchange/exchangetest/gdpr-geo-eu-off-device.json +++ b/exchange/exchangetest/gdpr-geo-eu-off-device.json @@ -7,17 +7,25 @@ "site": { "page": "test.somepage.com" }, - "imp": [{ - "id": "my-imp-id", - "video": { - "mimes": ["video/mp4"] - }, - "ext": { - "appnexus": { - "placementId": 1 + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } + } } } - }], + ], "user": { "buyeruid": "some-buyer-id" }, @@ -26,7 +34,7 @@ "country": "FRA" } } -} + } }, "outgoingRequests": { "appnexus": { @@ -36,29 +44,33 @@ "site": { "page": "test.somepage.com" }, - "imp": [{ - "id": "my-imp-id", - "video": { - "mimes": ["video/mp4"] - }, - "ext": { - "bidder": { - "placementId": 1 + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } } } - }], - "user": { - }, + ], + "user": {}, "device": { "geo": { "country": "FRA" } } - }, - "bidAdjustment": 1.0 + } }, "mockResponse": { - "errors": ["appnexus-error"] + "errors": [ + "appnexus-error" + ] } } } diff --git a/exchange/exchangetest/gdpr-geo-eu-off.json b/exchange/exchangetest/gdpr-geo-eu-off.json index 24357eb7eec..1fbfae489de 100644 --- a/exchange/exchangetest/gdpr-geo-eu-off.json +++ b/exchange/exchangetest/gdpr-geo-eu-off.json @@ -7,17 +7,25 @@ "site": { "page": "test.somepage.com" }, - "imp": [{ - "id": "my-imp-id", - "video": { - "mimes": ["video/mp4"] - }, - "ext": { - "appnexus": { - "placementId": 1 + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } + } } } - }], + ], "user": { "buyeruid": "some-buyer-id", "geo": { @@ -34,27 +42,32 @@ "site": { "page": "test.somepage.com" }, - "imp": [{ - "id": "my-imp-id", - "video": { - "mimes": ["video/mp4"] - }, - "ext": { - "bidder": { - "placementId": 1 + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } } } - }], + ], "user": { "geo": { "country": "FRA" } } - }, - "bidAdjustment": 1.0 + } }, "mockResponse": { - "errors": ["appnexus-error"] + "errors": [ + "appnexus-error" + ] } } } diff --git a/exchange/exchangetest/gdpr-geo-eu-on-featureflag-off.json b/exchange/exchangetest/gdpr-geo-eu-on-featureflag-off.json index 6c6ca3edc62..6e75e73f643 100644 --- a/exchange/exchangetest/gdpr-geo-eu-on-featureflag-off.json +++ b/exchange/exchangetest/gdpr-geo-eu-on-featureflag-off.json @@ -7,17 +7,25 @@ "site": { "page": "test.somepage.com" }, - "imp": [{ - "id": "my-imp-id", - "video": { - "mimes": ["video/mp4"] - }, - "ext": { - "appnexus": { - "placementId": 1 + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } + } } } - }], + ], "user": { "buyeruid": "some-buyer-id", "geo": { @@ -34,28 +42,33 @@ "site": { "page": "test.somepage.com" }, - "imp": [{ - "id": "my-imp-id", - "video": { - "mimes": ["video/mp4"] - }, - "ext": { - "bidder": { - "placementId": 1 + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } } } - }], + ], "user": { "buyeruid": "some-buyer-id", "geo": { "country": "FRA" } } - }, - "bidAdjustment": 1.0 + } }, "mockResponse": { - "errors": ["appnexus-error"] + "errors": [ + "appnexus-error" + ] } } } diff --git a/exchange/exchangetest/gdpr-geo-eu-on.json b/exchange/exchangetest/gdpr-geo-eu-on.json index eb42a17c936..63d712db314 100644 --- a/exchange/exchangetest/gdpr-geo-eu-on.json +++ b/exchange/exchangetest/gdpr-geo-eu-on.json @@ -7,17 +7,25 @@ "site": { "page": "test.somepage.com" }, - "imp": [{ - "id": "my-imp-id", - "video": { - "mimes": ["video/mp4"] - }, - "ext": { - "appnexus": { - "placementId": 1 + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } + } } } - }], + ], "user": { "buyeruid": "some-buyer-id", "geo": { @@ -34,27 +42,32 @@ "site": { "page": "test.somepage.com" }, - "imp": [{ - "id": "my-imp-id", - "video": { - "mimes": ["video/mp4"] - }, - "ext": { - "bidder": { - "placementId": 1 + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } } } - }], + ], "user": { "geo": { "country": "FRA" } } - }, - "bidAdjustment": 1.0 + } }, "mockResponse": { - "errors": ["appnexus-error"] + "errors": [ + "appnexus-error" + ] } } } diff --git a/exchange/exchangetest/gdpr-geo-usa-off.json b/exchange/exchangetest/gdpr-geo-usa-off.json index d56c9318a56..03ee454907b 100644 --- a/exchange/exchangetest/gdpr-geo-usa-off.json +++ b/exchange/exchangetest/gdpr-geo-usa-off.json @@ -6,17 +6,25 @@ "site": { "page": "test.somepage.com" }, - "imp": [{ - "id": "my-imp-id", - "video": { - "mimes": ["video/mp4"] - }, - "ext": { - "appnexus": { - "placementId": 1 + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } + } } } - }], + ], "user": { "buyeruid": "some-buyer-id", "geo": { @@ -33,28 +41,33 @@ "site": { "page": "test.somepage.com" }, - "imp": [{ - "id": "my-imp-id", - "video": { - "mimes": ["video/mp4"] - }, - "ext": { - "bidder": { - "placementId": 1 + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } } } - }], + ], "user": { "buyeruid": "some-buyer-id", "geo": { "country": "USA" } } - }, - "bidAdjustment": 1.0 + } }, "mockResponse": { - "errors": ["appnexus-error"] + "errors": [ + "appnexus-error" + ] } } } diff --git a/exchange/exchangetest/gdpr-geo-usa-on.json b/exchange/exchangetest/gdpr-geo-usa-on.json index f922be9ea4e..6480dc9ef23 100644 --- a/exchange/exchangetest/gdpr-geo-usa-on.json +++ b/exchange/exchangetest/gdpr-geo-usa-on.json @@ -6,17 +6,25 @@ "site": { "page": "test.somepage.com" }, - "imp": [{ - "id": "my-imp-id", - "video": { - "mimes": ["video/mp4"] - }, - "ext": { - "appnexus": { - "placementId": 1 + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } + } } } - }], + ], "user": { "buyeruid": "some-buyer-id", "geo": { @@ -33,28 +41,33 @@ "site": { "page": "test.somepage.com" }, - "imp": [{ - "id": "my-imp-id", - "video": { - "mimes": ["video/mp4"] - }, - "ext": { - "bidder": { - "placementId": 1 + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } } } - }], + ], "user": { "buyeruid": "some-buyer-id", "geo": { "country": "USA" } } - }, - "bidAdjustment": 1.0 + } }, "mockResponse": { - "errors": ["appnexus-error"] + "errors": [ + "appnexus-error" + ] } } } diff --git a/exchange/exchangetest/include-brand-category.json b/exchange/exchangetest/include-brand-category.json index 38b8a7d389f..81b44b4cdce 100644 --- a/exchange/exchangetest/include-brand-category.json +++ b/exchange/exchangetest/include-brand-category.json @@ -9,22 +9,34 @@ { "id": "my-imp-id", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { - "appnexus": { - "placementId": 1 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } } } }, { "id": "imp-id-2", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { - "appnexus": { - "placementId": 1 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } } } } @@ -34,7 +46,7 @@ "targeting": { "includebrandcategory": { "primaryadserver": 1, - "publisher":"", + "publisher": "", "withcategory": true } } @@ -48,38 +60,45 @@ "outgoingRequests": { "appnexus": { "mockResponse": { - "pbsSeatBid": { - "pbsBids": [ - { - "ortbBid": { - "id": "apn-bid", - "impid": "my-imp-id", - "price": 0.3, - "w": 200, - "h": 250, - "crid": "creative-1", - "cat": ["IAB1-1"] + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "cat": [ + "IAB1-1" + ] + }, + "bidType": "video", + "bidVideo": { + "duration": 30, + "PrimaryCategory": "" + } }, - "bidType": "video", - "bidVideo": { - "duration": 30, - "PrimaryCategory": "" + { + "ortbBid": { + "id": "apn-other-bid", + "impid": "imp-id-2", + "price": 0.6, + "w": 300, + "h": 500, + "crid": "creative-3", + "cat": [ + "IAB1-2" + ] + }, + "bidType": "video" } - }, - { - "ortbBid": { - "id": "apn-other-bid", - "impid": "imp-id-2", - "price": 0.6, - "w": 300, - "h": 500, - "crid": "creative-3", - "cat": ["IAB1-2"] - }, - "bidType": "video" - } - ] - } + ], + "seat": "appnexus" + } + ] } } }, @@ -97,48 +116,52 @@ "w": 200, "h": 250, "crid": "creative-1", - "cat": ["IAB1-1"], + "cat": [ + "IAB1-1" + ], "ext": { "origbidcpm": 0.3, "prebid": { "type": "video", "targeting": { - "hb_bidder": "appnexus", - "hb_bidder_appnexus": "appnexus", - "hb_cache_host": "www.pbcserver.com", - "hb_cache_host_appnex": "www.pbcserver.com", - "hb_cache_path": "/pbcache/endpoint", - "hb_cache_path_appnex": "/pbcache/endpoint", - "hb_pb": "0.20", - "hb_pb_appnexus": "0.20", - "hb_pb_cat_dur": "0.20_VideoGames_0s", - "hb_pb_cat_dur_appnex": "0.20_VideoGames_0s", - "hb_size": "200x250", - "hb_size_appnexus": "200x250" - } + "hb_bidder": "appnexus", + "hb_bidder_appnexus": "appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_pb": "0.20", + "hb_pb_appnexus": "0.20", + "hb_pb_cat_dur": "0.20_VideoGames_0s", + "hb_pb_cat_dur_appnex": "0.20_VideoGames_0s", + "hb_size": "200x250", + "hb_size_appnexus": "200x250" + } } } }, { - "cat": ["IAB1-2"], + "cat": [ + "IAB1-2" + ], "crid": "creative-3", "ext": { "origbidcpm": 0.6, "prebid": { "targeting": { - "hb_bidder": "appnexus", - "hb_bidder_appnexus": "appnexus", - "hb_cache_host": "www.pbcserver.com", - "hb_cache_host_appnex": "www.pbcserver.com", - "hb_cache_path": "/pbcache/endpoint", - "hb_cache_path_appnex": "/pbcache/endpoint", - "hb_pb": "0.50", - "hb_pb_appnexus": "0.50", - "hb_pb_cat_dur": "0.50_HomeDecor_0s", - "hb_pb_cat_dur_appnex": "0.50_HomeDecor_0s", - "hb_size": "300x500", - "hb_size_appnexus": "300x500" - }, + "hb_bidder": "appnexus", + "hb_bidder_appnexus": "appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_pb": "0.50", + "hb_pb_appnexus": "0.50", + "hb_pb_cat_dur": "0.50_HomeDecor_0s", + "hb_pb_cat_dur_appnex": "0.50_HomeDecor_0s", + "hb_size": "300x500", + "hb_size_appnexus": "300x500" + }, "type": "video" } }, @@ -153,4 +176,4 @@ ] } } -} +} \ No newline at end of file diff --git a/exchange/exchangetest/lmt-featureflag-off.json b/exchange/exchangetest/lmt-featureflag-off.json index 9a15c87953e..52a37ece903 100644 --- a/exchange/exchangetest/lmt-featureflag-off.json +++ b/exchange/exchangetest/lmt-featureflag-off.json @@ -6,17 +6,25 @@ "site": { "page": "test.somepage.com" }, - "imp": [{ - "id": "my-imp-id", - "video": { - "mimes": ["video/mp4"] - }, - "ext": { - "appnexus": { - "placementId": 1 + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } + } } } - }], + ], "device": { "lmt": 1 }, @@ -34,17 +42,21 @@ "site": { "page": "test.somepage.com" }, - "imp": [{ - "id": "my-imp-id", - "video": { - "mimes": ["video/mp4"] - }, - "ext": { - "bidder": { - "placementId": 1 + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } } } - }], + ], "device": { "lmt": 1 }, @@ -52,11 +64,12 @@ "id": "some-id", "buyeruid": "some-buyer-id" } - }, - "bidAdjustment": 1.0 + } }, "mockResponse": { - "errors": ["appnexus-error"] + "errors": [ + "appnexus-error" + ] } } } diff --git a/exchange/exchangetest/lmt-featureflag-on.json b/exchange/exchangetest/lmt-featureflag-on.json index 440f8c76472..5d82a9c1c1b 100644 --- a/exchange/exchangetest/lmt-featureflag-on.json +++ b/exchange/exchangetest/lmt-featureflag-on.json @@ -6,17 +6,25 @@ "site": { "page": "test.somepage.com" }, - "imp": [{ - "id": "my-imp-id", - "video": { - "mimes": ["video/mp4"] - }, - "ext": { - "appnexus": { - "placementId": 1 + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } + } } } - }], + ], "device": { "lmt": 1 }, @@ -34,27 +42,31 @@ "site": { "page": "test.somepage.com" }, - "imp": [{ - "id": "my-imp-id", - "video": { - "mimes": ["video/mp4"] - }, - "ext": { - "bidder": { - "placementId": 1 + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } } } - }], + ], "device": { "lmt": 1 }, - "user": { - } - }, - "bidAdjustment": 1.0 + "user": {} + } }, "mockResponse": { - "errors": ["appnexus-error"] + "errors": [ + "appnexus-error" + ] } } } diff --git a/exchange/exchangetest/passthrough_imp_only.json b/exchange/exchangetest/passthrough_imp_only.json new file mode 100644 index 00000000000..b7b2270270b --- /dev/null +++ b/exchange/exchangetest/passthrough_imp_only.json @@ -0,0 +1,174 @@ +{ + "passthrough_flag": true, + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id-2", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + }, + "passthrough": { + "imp_passthrough_val": 20 + } + } + } + }, + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } + } + } + } + ] + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id-2", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + }, + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + } + ] + } + }, + "mockResponse": { + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id-2", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "someField": "someValue", + "origbidcpm": 0.3 + } + }, + "bidType": "video" + }, + { + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "someField": "someValue", + "origbidcpm": 0.3 + } + }, + "bidType": "video" + } + ], + "seat": "appnexus" + } + ] + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [ + { + "seat": "appnexus", + "bid": [ + { + "id": "apn-bid", + "impid": "my-imp-id-2", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "someField": "someValue", + "origbidcpm": 0.3, + "prebid": { + "type": "video", + "passthrough": { + "imp_passthrough_val": 20 + } + } + } + }, + { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "someField": "someValue", + "origbidcpm": 0.3, + "prebid": { + "type": "video" + } + } + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/passthrough_root_and_imp.json b/exchange/exchangetest/passthrough_root_and_imp.json new file mode 100644 index 00000000000..c0fa8dcfb5d --- /dev/null +++ b/exchange/exchangetest/passthrough_root_and_imp.json @@ -0,0 +1,137 @@ +{ + "passthrough_flag": true, + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + }, + "passthrough": { + "imp_passthrough_val": 20 + } + } + } + } + ], + "ext": { + "prebid": { + "passthrough": { + "bid_response_passthrough": 20 + } + } + } + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + } + ], + "ext": { + "prebid": { + "server": { + "datacenter": "Datacenter", + "externalurl": "http://hosturl.com", + "gvlid": 1 + } + } + } + } + }, + "mockResponse": { + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "someField": "someValue", + "origbidcpm": 0.3 + } + }, + "bidType": "video" + } + ], + "seat": "appnexus" + } + ] + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [ + { + "seat": "appnexus", + "bid": [ + { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "someField": "someValue", + "origbidcpm": 0.3, + "prebid": { + "type": "video", + "passthrough": { + "imp_passthrough_val": 20 + } + } + } + } + ] + } + ] + }, + "ext": { + "prebid": { + "passthrough": { + "bid_response_passthrough": 20 + } + } + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/passthrough_root_only.json b/exchange/exchangetest/passthrough_root_only.json new file mode 100644 index 00000000000..a56a617f103 --- /dev/null +++ b/exchange/exchangetest/passthrough_root_only.json @@ -0,0 +1,131 @@ +{ + "passthrough_flag": true, + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } + } + } + } + ], + "ext": { + "prebid": { + "passthrough": { + "bid_response_passthrough": 20 + } + } + } + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + } + ], + "ext": { + "prebid": { + "server": { + "datacenter": "Datacenter", + "externalurl": "http://hosturl.com", + "gvlid": 1 + } + } + } + } + }, + "mockResponse": { + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "someField": "someValue", + "origbidcpm": 0.3 + } + }, + "bidType": "video" + } + ], + "seat": "appnexus" + } + ] + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [ + { + "seat": "appnexus", + "bid": [ + { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "prebid": { + "type": "video" + }, + "someField": "someValue", + "origbidcpm": 0.3 + } + } + ] + } + ] + }, + "ext": { + "prebid": { + "passthrough": { + "bid_response_passthrough": 20 + } + } + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/request-ext-prebid-filtering.json b/exchange/exchangetest/request-ext-prebid-filtering.json new file mode 100644 index 00000000000..1cb2ecea0fa --- /dev/null +++ b/exchange/exchangetest/request-ext-prebid-filtering.json @@ -0,0 +1,169 @@ +{ + "description": "Verifies only allowed bid.request.ext.prebid values are passed to the bidder, leaving other ext fields untouched.", + "incomingRequest": { + "ortbRequest": { + "id": "test-request-id", + "site": { + "page": "test.testpage.com" + }, + "imp": [ + { + "id": "imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } + } + } + } + ], + "ext": { + "other": "test-other", + "prebid": { + "data": { + "eidpermissions": [ + { + "source": "test-source", + "bidders": [ + "appnexus" + ] + } + ] + }, + "integration": "test-integration", + "channel": { + "name": "test-name", + "version": "test-version" + }, + "debug": true, + "currency": { + "rates": { + "FOO": { + "BAR": 42 + } + }, + "usepbsrates": true + }, + "bidderparams": { + "appnexus": { + "param1": 1, + "paramA": "A" + }, + "rubicon": { + "not": "permitted-for-appnexus" + } + } + } + } + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "test-request-id", + "site": { + "page": "test.testpage.com" + }, + "imp": [ + { + "id": "imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + } + ], + "ext": { + "other": "test-other", + "prebid": { + "integration": "test-integration", + "server": { + "datacenter": "Datacenter", + "externalurl": "http://hosturl.com", + "gvlid": 1 + }, + "channel": { + "name": "test-name", + "version": "test-version" + }, + "debug": true, + "currency": { + "rates": { + "FOO": { + "BAR": 42 + } + }, + "usepbsrates": true + }, + "bidderparams": { + "param1": 1, + "paramA": "A" + } + } + } + } + }, + "mockResponse": { + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid", + "impid": "imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1" + }, + "bidType": "video" + } + ], + "seat": "appnexus" + } + ] + } + } + }, + "response": { + "bids": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "appnexus", + "bid": [ + { + "id": "apn-bid", + "impid": "imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "origbidcpm": 0.3, + "prebid": { + "type": "video" + } + } + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/request-imp-ext-prebid-filtering.json b/exchange/exchangetest/request-imp-ext-prebid-filtering.json new file mode 100644 index 00000000000..fb8e0892aa5 --- /dev/null +++ b/exchange/exchangetest/request-imp-ext-prebid-filtering.json @@ -0,0 +1,136 @@ +{ + "description": "Verifies only allowed bid.request.imp[].ext.prebid values are passed to the bidder, dropping other fields.", + "incomingRequest": { + "ortbRequest": { + "id": "test-request-id", + "site": { + "page": "test.testpage.com" + }, + "imp": [ + { + "id": "imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + }, + "storedrequest": { + "id": "any-id" + }, + "storedauctionresponse": { + "id": "any-id" + }, + "storedbidresponse": [ + { + "id": "any-id", + "bidder": "foo" + } + ], + "is_rewarded_inventory": true, + "options": { + "echovideoattrs": true + }, + "unknown": "any-unknown" + }, + "data": "any-data", + "context": "any-context", + "skadn": "any-skadn", + "gpid": "any-gpid", + "tid": "any-tid" + } + } + ] + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "test-request-id", + "site": { + "page": "test.testpage.com" + }, + "imp": [ + { + "id": "imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + }, + "prebid": { + "is_rewarded_inventory": true, + "options": { + "echovideoattrs": true + } + }, + "data": "any-data", + "context": "any-context", + "skadn": "any-skadn", + "gpid": "any-gpid", + "tid": "any-tid" + } + } + ] + } + }, + "mockResponse": { + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid", + "impid": "imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1" + }, + "bidType": "video" + } + ], + "seat": "appnexus" + } + ] + } + } + }, + "response": { + "bids": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "appnexus", + "bid": [ + { + "id": "apn-bid", + "impid": "imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "origbidcpm": 0.3, + "prebid": { + "type": "video" + } + } + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/request-multi-bidders-debug-info.json b/exchange/exchangetest/request-multi-bidders-debug-info.json index 18d9b1aa93b..cb3c3456bbc 100644 --- a/exchange/exchangetest/request-multi-bidders-debug-info.json +++ b/exchange/exchangetest/request-multi-bidders-debug-info.json @@ -15,11 +15,15 @@ ] }, "ext": { - "appnexus": { - "placementId": 1 - }, - "audienceNetwork": { - "placementId": "some-placement" + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + }, + "audienceNetwork": { + "placementId": "some-placement" + } + } } } }, @@ -31,11 +35,15 @@ ] }, "ext": { - "appnexus": { - "placementId": 2 - }, - "audienceNetwork": { - "placementId": "some-other-placement" + "prebid": { + "bidder": { + "appnexus": { + "placementId": 2 + }, + "audienceNetwork": { + "placementId": "some-other-placement" + } + } } } } @@ -65,28 +73,39 @@ { "uri": "appnexusTest.com", "requestbody": "appnexusTestRequestBody", - "requestheaders": { "header_1" : ["value_11", "value_12"], "header_2" : ["value_21"] }, + "requestheaders": { + "header_1": [ + "value_11", + "value_12" + ], + "header_2": [ + "value_21" + ] + }, "responsebody": "appnexusTestResponseBody", "status": 200 } ], - "pbsSeatBid": { - "pbsBids": [ - { - "ortbBid": { - "id": "winning-bid", - "impid": "my-imp-id", - "price": 12.00, - "w": 200, - "h": 250, - "crid": "creative-1", - "cat": [ - "IAB1-1" - ] + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "winning-bid", + "impid": "my-imp-id", + "price": 12.00, + "w": 200, + "h": 250, + "crid": "creative-1", + "cat": [ + "IAB1-1" + ] + } } - } - ] - } + ], + "seat": "appnexus" + } + ] } }, "audienceNetwork": { @@ -152,7 +171,15 @@ { "uri": "appnexusTest.com", "requestbody": "appnexusTestRequestBody", - "requestheaders": { "header_1" : ["value_11", "value_12"], "header_2" : ["value_21"] }, + "requestheaders": { + "header_1": [ + "value_11", + "value_12" + ], + "header_2": [ + "value_21" + ] + }, "responsebody": "appnexusTestResponseBody", "status": 200 } @@ -178,11 +205,15 @@ ] }, "ext": { - "appnexus": { - "placementId": 1 - }, - "audienceNetwork": { - "placementId": "some-placement" + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + }, + "audienceNetwork": { + "placementId": "some-placement" + } + } } } }, @@ -194,11 +225,15 @@ ] }, "ext": { - "appnexus": { - "placementId": 2 - }, - "audienceNetwork": { - "placementId": "some-other-placement" + "prebid": { + "bidder": { + "appnexus": { + "placementId": 2 + }, + "audienceNetwork": { + "placementId": "some-other-placement" + } + } } } } @@ -227,6 +262,4 @@ } } } -} - - +} \ No newline at end of file diff --git a/exchange/exchangetest/request-multi-bidders-one-no-resp.json b/exchange/exchangetest/request-multi-bidders-one-no-resp.json index d46de2dcf44..b5a1da828b3 100644 --- a/exchange/exchangetest/request-multi-bidders-one-no-resp.json +++ b/exchange/exchangetest/request-multi-bidders-one-no-resp.json @@ -9,28 +9,40 @@ { "id": "my-imp-id", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { - "appnexus": { - "placementId": 1 - }, - "audienceNetwork": { - "placementId": "some-placement" + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + }, + "audienceNetwork": { + "placementId": "some-placement" + } + } } } }, { "id": "imp-id-2", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { - "appnexus": { - "placementId": 2 - }, - "audienceNetwork": { - "placementId": "some-other-placement" + "prebid": { + "bidder": { + "appnexus": { + "placementId": 2 + }, + "audienceNetwork": { + "placementId": "some-other-placement" + } + } } } } @@ -38,7 +50,10 @@ "ext": { "prebid": { "targeting": { - "durationRangeSec": [15,30], + "durationRangeSec": [ + 15, + 30 + ], "includebrandcategory": { "primaryadserver": 1, "publisher": "", @@ -53,29 +68,30 @@ "outgoingRequests": { "appnexus": { "mockResponse": { - "pbsSeatBid": { - "pbsBids": [ - { - "ortbBid": { - "id": "winning-bid", - "impid": "my-imp-id", - "price": 12.00, - "w": 200, - "h": 250, - "crid": "creative-1", - "cat": [ - "IAB1-1" - ] + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "winning-bid", + "impid": "my-imp-id", + "price": 12.00, + "w": 200, + "h": 250, + "crid": "creative-1", + "cat": [ + "IAB1-1" + ] + } } - } - ] - } + ], + "seat": "appnexus" + } + ] } }, "audienceNetwork": { - "mockResponse": { - - } + "mockResponse": {} } }, "response": { @@ -86,38 +102,39 @@ "seat": "appnexus", "bid": [ { - "id": "winning-bid", - "impid": "my-imp-id", - "price": 12.00, - "w": 200, - "h": 250, - "crid": "creative-1", - "cat": ["IAB1-1"], - "ext": { - "origbidcpm": 12.00, - "prebid": { - "type": "", - "targeting": { - "hb_bidder": "appnexus", - "hb_bidder_appnexus": "appnexus", - "hb_cache_host": "www.pbcserver.com", - "hb_cache_host_appnex": "www.pbcserver.com", - "hb_cache_path": "/pbcache/endpoint", - "hb_cache_path_appnex": "/pbcache/endpoint", - "hb_size": "200x250", - "hb_size_appnexus": "200x250", - "hb_pb": "12.00", - "hb_pb_appnexus": "12.00", - "hb_pb_cat_dur": "12.00_VideoGames_15s", - "hb_pb_cat_dur_appnex": "12.00_VideoGames_15s" + "id": "winning-bid", + "impid": "my-imp-id", + "price": 12.00, + "w": 200, + "h": 250, + "crid": "creative-1", + "cat": [ + "IAB1-1" + ], + "ext": { + "origbidcpm": 12.00, + "prebid": { + "type": "", + "targeting": { + "hb_bidder": "appnexus", + "hb_bidder_appnexus": "appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_size": "200x250", + "hb_size_appnexus": "200x250", + "hb_pb": "12.00", + "hb_pb_appnexus": "12.00", + "hb_pb_cat_dur": "12.00_VideoGames_15s", + "hb_pb_cat_dur_appnex": "12.00_VideoGames_15s" + } } } } - }] + ] } ] } } -} - - +} \ No newline at end of file diff --git a/exchange/exchangetest/request-no-user.json b/exchange/exchangetest/request-no-user.json index 9e3bdb6e9ca..a3b49805add 100644 --- a/exchange/exchangetest/request-no-user.json +++ b/exchange/exchangetest/request-no-user.json @@ -9,11 +9,17 @@ { "id": "my-imp-id", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { - "appnexus": { - "placementId": 1 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } } } } @@ -38,7 +44,9 @@ { "id": "my-imp-id", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { "bidder": { @@ -47,12 +55,13 @@ } } ] - }, - "bidAdjustment": 1.0 + } }, "mockResponse": { - "errors": ["appnexus-error"] + "errors": [ + "appnexus-error" + ] } } } -} +} \ No newline at end of file diff --git a/exchange/exchangetest/request-other-user-ext.json b/exchange/exchangetest/request-other-user-ext.json index f9fb3264c3c..5f807e87d07 100644 --- a/exchange/exchangetest/request-other-user-ext.json +++ b/exchange/exchangetest/request-other-user-ext.json @@ -19,11 +19,17 @@ { "id": "my-imp-id", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { - "appnexus": { - "placementId": 1 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } } } } @@ -49,7 +55,9 @@ { "id": "my-imp-id", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { "bidder": { @@ -58,12 +66,13 @@ } } ] - }, - "bidAdjustment": 1.0 + } }, "mockResponse": { - "errors": ["appnexus-error"] + "errors": [ + "appnexus-error" + ] } } } -} +} \ No newline at end of file diff --git a/exchange/exchangetest/request-other-user.json b/exchange/exchangetest/request-other-user.json index 7413d37ff79..99ebb253fe4 100644 --- a/exchange/exchangetest/request-other-user.json +++ b/exchange/exchangetest/request-other-user.json @@ -12,11 +12,17 @@ { "id": "my-imp-id", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { - "appnexus": { - "placementId": 1 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } } } } @@ -42,7 +48,9 @@ { "id": "my-imp-id", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { "bidder": { @@ -51,12 +59,13 @@ } } ] - }, - "bidAdjustment": 1.0 + } }, "mockResponse": { - "errors": ["appnexus-error"] + "errors": [ + "appnexus-error" + ] } } } -} +} \ No newline at end of file diff --git a/exchange/exchangetest/request-user-no-prebid.json b/exchange/exchangetest/request-user-no-prebid.json index aae11606baa..8dbbe66df13 100644 --- a/exchange/exchangetest/request-user-no-prebid.json +++ b/exchange/exchangetest/request-user-no-prebid.json @@ -7,18 +7,23 @@ }, "user": { "id": "foo", - "ext": { - } + "ext": {} }, "imp": [ { "id": "my-imp-id", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { - "appnexus": { - "placementId": 1 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } } } } @@ -39,14 +44,15 @@ "user": { "id": "foo", "buyeruid": "implicit-appnexus", - "ext": { - } + "ext": {} }, "imp": [ { "id": "my-imp-id", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { "bidder": { @@ -55,12 +61,13 @@ } } ] - }, - "bidAdjustment": 1.0 + } }, "mockResponse": { - "errors": ["appnexus-error"] + "errors": [ + "appnexus-error" + ] } } } -} +} \ No newline at end of file diff --git a/exchange/exchangetest/schain-host-and-request.json b/exchange/exchangetest/schain-host-and-request.json new file mode 100644 index 00000000000..4c5f2a8c6a6 --- /dev/null +++ b/exchange/exchangetest/schain-host-and-request.json @@ -0,0 +1,118 @@ +{ + "host_schain_flag": true, + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } + } + } + } + ], + "ext": { + "prebid": { + "bidadjustmentfactors": null, + "schains": [ + { + "bidders": [ + "appnexus" + ], + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "whatever.com", + "sid": "1234", + "rid": "123-456-7890", + "hp": 1 + } + ], + "ver": "2.0" + } + } + ], + "debug": true, + "nosale": [ + "appnexus" + ] + } + } + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + } + ], + "ext": { + "prebid": { + "debug": true, + "server": { + "datacenter": "Datacenter", + "externalurl": "http://hosturl.com", + "gvlid": 1 + } + } + }, + "source": { + "ext": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "whatever.com", + "sid": "1234", + "rid": "123-456-7890", + "hp": 1 + }, + { + "asi": "pbshostcompany.com", + "sid": "00001", + "rid": "BidRequest", + "hp": 1 + } + ], + "ver": "2.0" + } + } + } + }, + "bidAdjustment": 1.0 + } + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/schain-host-only.json b/exchange/exchangetest/schain-host-only.json new file mode 100644 index 00000000000..0f1b8656a47 --- /dev/null +++ b/exchange/exchangetest/schain-host-only.json @@ -0,0 +1,92 @@ +{ + "host_schain_flag": true, + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } + } + } + } + ], + "ext": { + "prebid": { + "debug": true, + "nosale": [ + "appnexus" + ] + } + } + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + } + ], + "ext": { + "prebid": { + "debug": true, + "server": { + "datacenter": "Datacenter", + "externalurl": "http://hosturl.com", + "gvlid": 1 + } + } + }, + "source": { + "ext": { + "schain": { + "complete": 0, + "nodes": [ + { + "asi": "pbshostcompany.com", + "sid": "00001", + "rid": "BidRequest", + "hp": 1 + } + ], + "ver": "1.0" + } + } + } + }, + "bidAdjustment": 1.0 + } + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/targeting-cache-vast-banner.json b/exchange/exchangetest/targeting-cache-vast-banner.json index d396abbbcc2..39f9a005718 100644 --- a/exchange/exchangetest/targeting-cache-vast-banner.json +++ b/exchange/exchangetest/targeting-cache-vast-banner.json @@ -13,8 +13,12 @@ "h": 250 }, "ext": { - "appnexus": { - "placementId": 1 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } } } } @@ -32,21 +36,24 @@ "outgoingRequests": { "appnexus": { "mockResponse": { - "pbsSeatBid": { - "pbsBids": [ - { - "ortbBid": { - "id": "winning-bid", - "impid": "my-imp-id", - "price": 0.01, - "w": 200, - "h": 250, - "crid": "creative-1" - }, - "bidType": "banner" - } - ] - } + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "winning-bid", + "impid": "my-imp-id", + "price": 0.01, + "w": 200, + "h": 250, + "crid": "creative-1" + }, + "bidType": "banner" + } + ], + "seat": "appnexus" + } + ] } } }, @@ -56,34 +63,36 @@ "seatbid": [ { "seat": "appnexus", - "bid": [{ - "id": "winning-bid", - "impid": "my-imp-id", - "price": 0.01, - "w": 200, - "h": 250, - "crid": "creative-1", - "ext": { - "origbidcpm": 0.01, - "prebid": { - "type": "banner", - "targeting": { - "hb_bidder": "appnexus", - "hb_bidder_appnexus": "appnexus", - "hb_cache_host": "www.pbcserver.com", - "hb_cache_host_appnex": "www.pbcserver.com", - "hb_cache_path": "/pbcache/endpoint", - "hb_cache_path_appnex": "/pbcache/endpoint", - "hb_pb": "0.00", - "hb_pb_appnexus": "0.00", - "hb_size": "200x250", - "hb_size_appnexus": "200x250" + "bid": [ + { + "id": "winning-bid", + "impid": "my-imp-id", + "price": 0.01, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "origbidcpm": 0.01, + "prebid": { + "type": "banner", + "targeting": { + "hb_bidder": "appnexus", + "hb_bidder_appnexus": "appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_pb": "0.00", + "hb_pb_appnexus": "0.00", + "hb_size": "200x250", + "hb_size_appnexus": "200x250" + } } } } - }] + ] } ] } } -} +} \ No newline at end of file diff --git a/exchange/exchangetest/targeting-cache-vast.json b/exchange/exchangetest/targeting-cache-vast.json index 1cda3a5e2bb..5374208676d 100644 --- a/exchange/exchangetest/targeting-cache-vast.json +++ b/exchange/exchangetest/targeting-cache-vast.json @@ -9,11 +9,17 @@ { "id": "my-imp-id", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { - "appnexus": { - "placementId": 1 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } } } } @@ -31,21 +37,24 @@ "outgoingRequests": { "appnexus": { "mockResponse": { - "pbsSeatBid": { - "pbsBids": [ - { - "ortbBid": { - "id": "winning-bid", - "impid": "my-imp-id", - "price": 0.01, - "w": 200, - "h": 250, - "crid": "creative-1" - }, - "bidType": "video" - } - ] - } + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "winning-bid", + "impid": "my-imp-id", + "price": 0.01, + "w": 200, + "h": 250, + "crid": "creative-1" + }, + "bidType": "video" + } + ], + "seat": "appnexus" + } + ] } } }, @@ -55,44 +64,46 @@ "seatbid": [ { "seat": "appnexus", - "bid": [{ - "id": "winning-bid", - "impid": "my-imp-id", - "price": 0.01, - "w": 200, - "h": 250, - "crid": "creative-1", - "ext": { - "origbidcpm": 0.01, - "prebid": { - "cache": { - "bids": { - "cacheId": "0", - "url": "https://www.pbcserver.com/pbcache/endpoint?uuid=0" + "bid": [ + { + "id": "winning-bid", + "impid": "my-imp-id", + "price": 0.01, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "origbidcpm": 0.01, + "prebid": { + "cache": { + "bids": { + "cacheId": "0", + "url": "https://www.pbcserver.com/pbcache/endpoint?uuid=0" + }, + "key": "", + "url": "" }, - "key": "", - "url": "" - }, - "type": "video", - "targeting": { - "hb_bidder": "appnexus", - "hb_bidder_appnexus": "appnexus", - "hb_cache_host": "www.pbcserver.com", - "hb_cache_host_appnex": "www.pbcserver.com", - "hb_cache_path": "/pbcache/endpoint", - "hb_cache_path_appnex": "/pbcache/endpoint", - "hb_uuid": "0", - "hb_uuid_appnexus": "0", - "hb_pb": "0.00", - "hb_pb_appnexus": "0.00", - "hb_size": "200x250", - "hb_size_appnexus": "200x250" + "type": "video", + "targeting": { + "hb_bidder": "appnexus", + "hb_bidder_appnexus": "appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_uuid": "0", + "hb_uuid_appnexus": "0", + "hb_pb": "0.00", + "hb_pb_appnexus": "0.00", + "hb_size": "200x250", + "hb_size_appnexus": "200x250" + } } } } - }] + ] } ] } } -} +} \ No newline at end of file diff --git a/exchange/exchangetest/targeting-cache-zero.json b/exchange/exchangetest/targeting-cache-zero.json index 3be5e630b92..2d422d6d208 100644 --- a/exchange/exchangetest/targeting-cache-zero.json +++ b/exchange/exchangetest/targeting-cache-zero.json @@ -1,6 +1,5 @@ { "//": "Prevents #199", - "incomingRequest": { "ortbRequest": { "id": "some-request-id", @@ -11,11 +10,17 @@ { "id": "my-imp-id", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { - "appnexus": { - "placementId": 1 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } } } } @@ -34,21 +39,24 @@ "outgoingRequests": { "appnexus": { "mockResponse": { - "pbsSeatBid": { - "pbsBids": [ - { - "ortbBid": { - "id": "winning-bid", - "impid": "my-imp-id", - "price": 0.01, - "w": 200, - "h": 250, - "crid": "creative-1" - }, - "bidType": "video" - } - ] - } + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "winning-bid", + "impid": "my-imp-id", + "price": 0.01, + "w": 200, + "h": 250, + "crid": "creative-1" + }, + "bidType": "video" + } + ], + "seat": "appnexus" + } + ] } } }, @@ -58,46 +66,48 @@ "seatbid": [ { "seat": "appnexus", - "bid": [{ - "id": "winning-bid", - "impid": "my-imp-id", - "price": 0.01, - "w": 200, - "h": 250, - "crid": "creative-1", - "ext": { - "origbidcpm": 0.01, - "prebid": { - "cache": { - "bids": { - "cacheId": "0", - "url": "https://www.pbcserver.com/pbcache/endpoint?uuid=0" + "bid": [ + { + "id": "winning-bid", + "impid": "my-imp-id", + "price": 0.01, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "origbidcpm": 0.01, + "prebid": { + "cache": { + "bids": { + "cacheId": "0", + "url": "https://www.pbcserver.com/pbcache/endpoint?uuid=0" + }, + "key": "", + "url": "" }, - "key": "", - "url": "" - }, - "type": "video", - "targeting": { - "hb_bidder": "appnexus", - "hb_bidder_appnexus": "appnexus", - "hb_cache_host": "www.pbcserver.com", - "hb_cache_host_appnex": "www.pbcserver.com", - "hb_cache_id": "0", - "hb_cache_id_appnexus": "0", - "hb_cache_path": "/pbcache/endpoint", - "hb_cache_path_appnex": "/pbcache/endpoint", - "hb_uuid": "1", - "hb_uuid_appnexus": "1", - "hb_pb": "0.00", - "hb_pb_appnexus": "0.00", - "hb_size": "200x250", - "hb_size_appnexus": "200x250" + "type": "video", + "targeting": { + "hb_bidder": "appnexus", + "hb_bidder_appnexus": "appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_id": "0", + "hb_cache_id_appnexus": "0", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_uuid": "1", + "hb_uuid_appnexus": "1", + "hb_pb": "0.00", + "hb_pb_appnexus": "0.00", + "hb_size": "200x250", + "hb_size_appnexus": "200x250" + } } } } - }] + ] } ] } } -} +} \ No newline at end of file diff --git a/exchange/exchangetest/targeting-mobile.json b/exchange/exchangetest/targeting-mobile.json index 4b41bc15347..deea901ae52 100644 --- a/exchange/exchangetest/targeting-mobile.json +++ b/exchange/exchangetest/targeting-mobile.json @@ -9,28 +9,40 @@ { "id": "my-imp-id", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { - "appnexus": { - "placementId": 1 - }, - "audienceNetwork": { - "placementId": "some-placement" + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + }, + "audienceNetwork": { + "placementId": "some-placement" + } + } } } }, { "id": "imp-id-2", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { - "appnexus": { - "placementId": 2 - }, - "audienceNetwork": { - "placementId": "some-other-placement" + "prebid": { + "bidder": { + "appnexus": { + "placementId": 2 + }, + "audienceNetwork": { + "placementId": "some-other-placement" + } + } } } } @@ -45,62 +57,68 @@ "outgoingRequests": { "appnexus": { "mockResponse": { - "pbsSeatBid": { - "pbsBids": [ - { - "ortbBid": { - "id": "winning-bid", - "impid": "my-imp-id", - "price": 0.71, - "w": 200, - "h": 250, - "crid": "creative-1" + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "winning-bid", + "impid": "my-imp-id", + "price": 0.71, + "w": 200, + "h": 250, + "crid": "creative-1" + }, + "bidType": "video" }, - "bidType": "video" - }, - { - "ortbBid": { - "id": "losing-bid", - "impid": "my-imp-id", - "price": 0.21, - "w": 200, - "h": 250, - "crid": "creative-2" + { + "ortbBid": { + "id": "losing-bid", + "impid": "my-imp-id", + "price": 0.21, + "w": 200, + "h": 250, + "crid": "creative-2" + }, + "bidType": "video" }, - "bidType": "video" - }, - { - "ortbBid": { - "id": "other-bid", - "impid": "imp-id-2", - "price": 0.61, - "w": 300, - "h": 500, - "crid": "creative-3" - }, - "bidType": "video" - } - ] - } + { + "ortbBid": { + "id": "other-bid", + "impid": "imp-id-2", + "price": 0.61, + "w": 300, + "h": 500, + "crid": "creative-3" + }, + "bidType": "video" + } + ], + "seat": "appnexus" + } + ] } }, "audienceNetwork": { "mockResponse": { - "pbsSeatBid": { - "pbsBids": [ - { - "ortbBid": { - "id": "contending-bid", - "impid": "my-imp-id", - "price": 0.51, - "w": 200, - "h": 250, - "crid": "creative-4" - }, - "bidType": "video" - } - ] - } + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "contending-bid", + "impid": "my-imp-id", + "price": 0.51, + "w": 200, + "h": 250, + "crid": "creative-4" + }, + "bidType": "video" + } + ], + "seat": "audienceNetwork" + } + ] } } }, @@ -110,103 +128,107 @@ "seatbid": [ { "seat": "audienceNetwork", - "bid": [{ - "id": "contending-bid", - "impid": "my-imp-id", - "price": 0.51, - "w": 200, - "h": 250, - "crid": "creative-4", - "ext": { - "origbidcpm": 0.51, - "prebid": { - "type": "video", - "targeting": { - "hb_bidder_audienceNe": "audienceNetwork", - "hb_cache_host_audien": "www.pbcserver.com", - "hb_cache_path_audien": "/pbcache/endpoint", - "hb_pb_audienceNetwor": "0.50", - "hb_size_audienceNetw": "200x250", - "hb_env_audienceNetwo": "mobile-app" + "bid": [ + { + "id": "contending-bid", + "impid": "my-imp-id", + "price": 0.51, + "w": 200, + "h": 250, + "crid": "creative-4", + "ext": { + "origbidcpm": 0.51, + "prebid": { + "type": "video", + "targeting": { + "hb_bidder_audienceNe": "audienceNetwork", + "hb_cache_host_audien": "www.pbcserver.com", + "hb_cache_path_audien": "/pbcache/endpoint", + "hb_pb_audienceNetwor": "0.50", + "hb_size_audienceNetw": "200x250", + "hb_env_audienceNetwo": "mobile-app" + } } } } - }] + ] }, { "seat": "appnexus", - "bid": [{ - "id": "winning-bid", - "impid": "my-imp-id", - "price": 0.71, - "w": 200, - "h": 250, - "crid": "creative-1", - "ext": { - "origbidcpm": 0.71, - "prebid": { - "type": "video", - "targeting": { - "hb_bidder": "appnexus", - "hb_cache_host": "www.pbcserver.com", - "hb_cache_host_appnex": "www.pbcserver.com", - "hb_cache_path": "/pbcache/endpoint", - "hb_cache_path_appnex": "/pbcache/endpoint", - "hb_bidder_appnexus": "appnexus", - "hb_pb": "0.70", - "hb_pb_appnexus": "0.70", - "hb_size": "200x250", - "hb_size_appnexus": "200x250", - "hb_env": "mobile-app", - "hb_env_appnexus": "mobile-app" + "bid": [ + { + "id": "winning-bid", + "impid": "my-imp-id", + "price": 0.71, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "origbidcpm": 0.71, + "prebid": { + "type": "video", + "targeting": { + "hb_bidder": "appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_bidder_appnexus": "appnexus", + "hb_pb": "0.70", + "hb_pb_appnexus": "0.70", + "hb_size": "200x250", + "hb_size_appnexus": "200x250", + "hb_env": "mobile-app", + "hb_env_appnexus": "mobile-app" + } } } - } - }, - { - "id": "losing-bid", - "impid": "my-imp-id", - "price": 0.21, - "w": 200, - "h": 250, - "crid": "creative-2", - "ext": { - "origbidcpm": 0.21, - "prebid": { - "type": "video" + }, + { + "id": "losing-bid", + "impid": "my-imp-id", + "price": 0.21, + "w": 200, + "h": 250, + "crid": "creative-2", + "ext": { + "origbidcpm": 0.21, + "prebid": { + "type": "video" + } } - } - }, - { - "id": "other-bid", - "impid": "imp-id-2", - "price": 0.61, - "w": 300, - "h": 500, - "crid": "creative-3", - "ext": { - "origbidcpm": 0.61, - "prebid": { - "type": "video", - "targeting": { - "hb_bidder": "appnexus", - "hb_cache_host": "www.pbcserver.com", - "hb_cache_host_appnex": "www.pbcserver.com", - "hb_cache_path": "/pbcache/endpoint", - "hb_cache_path_appnex": "/pbcache/endpoint", - "hb_bidder_appnexus": "appnexus", - "hb_pb": "0.60", - "hb_pb_appnexus": "0.60", - "hb_size": "300x500", - "hb_size_appnexus": "300x500", - "hb_env": "mobile-app", - "hb_env_appnexus": "mobile-app" + }, + { + "id": "other-bid", + "impid": "imp-id-2", + "price": 0.61, + "w": 300, + "h": 500, + "crid": "creative-3", + "ext": { + "origbidcpm": 0.61, + "prebid": { + "type": "video", + "targeting": { + "hb_bidder": "appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_bidder_appnexus": "appnexus", + "hb_pb": "0.60", + "hb_pb_appnexus": "0.60", + "hb_size": "300x500", + "hb_size_appnexus": "300x500", + "hb_env": "mobile-app", + "hb_env_appnexus": "mobile-app" + } } } } - }] + ] } ] } } -} +} \ No newline at end of file diff --git a/exchange/exchangetest/targeting-no-winners.json b/exchange/exchangetest/targeting-no-winners.json index 74a67263cce..729575ee971 100644 --- a/exchange/exchangetest/targeting-no-winners.json +++ b/exchange/exchangetest/targeting-no-winners.json @@ -9,28 +9,40 @@ { "id": "my-imp-id", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { - "appnexus": { - "placementId": 1 - }, - "audienceNetwork": { - "placementId": "some-placement" + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + }, + "audienceNetwork": { + "placementId": "some-placement" + } + } } } }, { "id": "imp-id-2", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { - "appnexus": { - "placementId": 2 - }, - "audienceNetwork": { - "placementId": "some-other-placement" + "prebid": { + "bidder": { + "appnexus": { + "placementId": 2 + }, + "audienceNetwork": { + "placementId": "some-other-placement" + } + } } } } @@ -47,62 +59,68 @@ "outgoingRequests": { "appnexus": { "mockResponse": { - "pbsSeatBid": { - "pbsBids": [ - { - "ortbBid": { - "id": "winning-bid", - "impid": "my-imp-id", - "price": 0.71, - "w": 200, - "h": 250, - "crid": "creative-1" + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "winning-bid", + "impid": "my-imp-id", + "price": 0.71, + "w": 200, + "h": 250, + "crid": "creative-1" + }, + "bidType": "video" }, - "bidType": "video" - }, - { - "ortbBid": { - "id": "losing-bid", - "impid": "my-imp-id", - "price": 0.21, - "w": 200, - "h": 250, - "crid": "creative-2" + { + "ortbBid": { + "id": "losing-bid", + "impid": "my-imp-id", + "price": 0.21, + "w": 200, + "h": 250, + "crid": "creative-2" + }, + "bidType": "video" }, - "bidType": "video" - }, - { - "ortbBid": { - "id": "other-bid", - "impid": "imp-id-2", - "price": 0.61, - "w": 300, - "h": 500, - "crid": "creative-3" - }, - "bidType": "video" - } - ] - } + { + "ortbBid": { + "id": "other-bid", + "impid": "imp-id-2", + "price": 0.61, + "w": 300, + "h": 500, + "crid": "creative-3" + }, + "bidType": "video" + } + ], + "seat": "appnexus" + } + ] } }, "audienceNetwork": { "mockResponse": { - "pbsSeatBid": { - "pbsBids": [ - { - "ortbBid": { - "id": "contending-bid", - "impid": "my-imp-id", - "price": 0.51, - "w": 200, - "h": 250, - "crid": "creative-4" - }, - "bidType": "video" - } - ] - } + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "contending-bid", + "impid": "my-imp-id", + "price": 0.51, + "w": 200, + "h": 250, + "crid": "creative-4" + }, + "bidType": "video" + } + ], + "seat": "audienceNetwork" + } + ] } } }, @@ -112,88 +130,92 @@ "seatbid": [ { "seat": "audienceNetwork", - "bid": [{ - "id": "contending-bid", - "impid": "my-imp-id", - "price": 0.51, - "w": 200, - "h": 250, - "crid": "creative-4", - "ext": { - "origbidcpm": 0.51, - "prebid": { - "type": "video", - "targeting": { - "hb_bidder_audienceNe": "audienceNetwork", - "hb_cache_host_audien": "www.pbcserver.com", - "hb_cache_path_audien": "/pbcache/endpoint", - "hb_pb_audienceNetwor": "0.50", - "hb_size_audienceNetw": "200x250" + "bid": [ + { + "id": "contending-bid", + "impid": "my-imp-id", + "price": 0.51, + "w": 200, + "h": 250, + "crid": "creative-4", + "ext": { + "origbidcpm": 0.51, + "prebid": { + "type": "video", + "targeting": { + "hb_bidder_audienceNe": "audienceNetwork", + "hb_cache_host_audien": "www.pbcserver.com", + "hb_cache_path_audien": "/pbcache/endpoint", + "hb_pb_audienceNetwor": "0.50", + "hb_size_audienceNetw": "200x250" + } } } } - }] + ] }, { "seat": "appnexus", - "bid": [{ - "id": "winning-bid", - "impid": "my-imp-id", - "price": 0.71, - "w": 200, - "h": 250, - "crid": "creative-1", - "ext": { - "origbidcpm": 0.71, - "prebid": { - "type": "video", - "targeting": { - "hb_bidder_appnexus": "appnexus", - "hb_cache_host_appnex": "www.pbcserver.com", - "hb_cache_path_appnex": "/pbcache/endpoint", - "hb_pb_appnexus": "0.70", - "hb_size_appnexus": "200x250" + "bid": [ + { + "id": "winning-bid", + "impid": "my-imp-id", + "price": 0.71, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "origbidcpm": 0.71, + "prebid": { + "type": "video", + "targeting": { + "hb_bidder_appnexus": "appnexus", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_pb_appnexus": "0.70", + "hb_size_appnexus": "200x250" + } } } - } - }, - { - "id": "losing-bid", - "impid": "my-imp-id", - "price": 0.21, - "w": 200, - "h": 250, - "crid": "creative-2", - "ext": { - "origbidcpm": 0.21, - "prebid": { - "type": "video" + }, + { + "id": "losing-bid", + "impid": "my-imp-id", + "price": 0.21, + "w": 200, + "h": 250, + "crid": "creative-2", + "ext": { + "origbidcpm": 0.21, + "prebid": { + "type": "video" + } } - } - }, - { - "id": "other-bid", - "impid": "imp-id-2", - "price": 0.61, - "w": 300, - "h": 500, - "crid": "creative-3", - "ext": { - "origbidcpm": 0.61, - "prebid": { - "type": "video", - "targeting": { - "hb_bidder_appnexus": "appnexus", - "hb_cache_host_appnex": "www.pbcserver.com", - "hb_cache_path_appnex": "/pbcache/endpoint", - "hb_pb_appnexus": "0.60", - "hb_size_appnexus": "300x500" + }, + { + "id": "other-bid", + "impid": "imp-id-2", + "price": 0.61, + "w": 300, + "h": 500, + "crid": "creative-3", + "ext": { + "origbidcpm": 0.61, + "prebid": { + "type": "video", + "targeting": { + "hb_bidder_appnexus": "appnexus", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_pb_appnexus": "0.60", + "hb_size_appnexus": "300x500" + } } } } - }] + ] } ] } } -} +} \ No newline at end of file diff --git a/exchange/exchangetest/targeting-only-winners.json b/exchange/exchangetest/targeting-only-winners.json index 491ab98f2a5..900ea1fda77 100644 --- a/exchange/exchangetest/targeting-only-winners.json +++ b/exchange/exchangetest/targeting-only-winners.json @@ -9,28 +9,40 @@ { "id": "my-imp-id", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { - "appnexus": { - "placementId": 1 - }, - "audienceNetwork": { - "placementId": "some-placement" + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + }, + "audienceNetwork": { + "placementId": "some-placement" + } + } } } }, { "id": "imp-id-2", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { - "appnexus": { - "placementId": 2 - }, - "audienceNetwork": { - "placementId": "some-other-placement" + "prebid": { + "bidder": { + "appnexus": { + "placementId": 2 + }, + "audienceNetwork": { + "placementId": "some-other-placement" + } + } } } } @@ -47,62 +59,68 @@ "outgoingRequests": { "appnexus": { "mockResponse": { - "pbsSeatBid": { - "pbsBids": [ - { - "ortbBid": { - "id": "winning-bid", - "impid": "my-imp-id", - "price": 0.71, - "w": 200, - "h": 250, - "crid": "creative-1" + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "winning-bid", + "impid": "my-imp-id", + "price": 0.71, + "w": 200, + "h": 250, + "crid": "creative-1" + }, + "bidType": "video" }, - "bidType": "video" - }, - { - "ortbBid": { - "id": "losing-bid", - "impid": "my-imp-id", - "price": 0.21, - "w": 200, - "h": 250, - "crid": "creative-2" + { + "ortbBid": { + "id": "losing-bid", + "impid": "my-imp-id", + "price": 0.21, + "w": 200, + "h": 250, + "crid": "creative-2" + }, + "bidType": "video" }, - "bidType": "video" - }, - { - "ortbBid": { - "id": "other-bid", - "impid": "imp-id-2", - "price": 0.61, - "w": 300, - "h": 500, - "crid": "creative-3" - }, - "bidType": "video" - } - ] - } + { + "ortbBid": { + "id": "other-bid", + "impid": "imp-id-2", + "price": 0.61, + "w": 300, + "h": 500, + "crid": "creative-3" + }, + "bidType": "video" + } + ], + "seat": "appnexus" + } + ] } }, "audienceNetwork": { "mockResponse": { - "pbsSeatBid": { - "pbsBids": [ - { - "ortbBid": { - "id": "contending-bid", - "impid": "my-imp-id", - "price": 0.51, - "w": 200, - "h": 250, - "crid": "creative-4" - }, - "bidType": "video" - } - ] - } + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "contending-bid", + "impid": "my-imp-id", + "price": 0.51, + "w": 200, + "h": 250, + "crid": "creative-4" + }, + "bidType": "video" + } + ], + "seat": "audienceNetwork" + } + ] } } }, @@ -112,81 +130,85 @@ "seatbid": [ { "seat": "audienceNetwork", - "bid": [{ - "id": "contending-bid", - "impid": "my-imp-id", - "price": 0.51, - "w": 200, - "h": 250, - "crid": "creative-4", - "ext": { - "origbidcpm": 0.51, - "prebid": { - "type": "video" + "bid": [ + { + "id": "contending-bid", + "impid": "my-imp-id", + "price": 0.51, + "w": 200, + "h": 250, + "crid": "creative-4", + "ext": { + "origbidcpm": 0.51, + "prebid": { + "type": "video" + } } } - }] + ] }, { "seat": "appnexus", - "bid": [{ - "id": "winning-bid", - "impid": "my-imp-id", - "price": 0.71, - "w": 200, - "h": 250, - "crid": "creative-1", - "ext": { - "origbidcpm": 0.71, - "prebid": { - "type": "video", - "targeting": { - "hb_bidder": "appnexus", - "hb_cache_host": "www.pbcserver.com", - "hb_cache_path": "/pbcache/endpoint", - "hb_pb": "0.70", - "hb_size": "200x250" + "bid": [ + { + "id": "winning-bid", + "impid": "my-imp-id", + "price": 0.71, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "origbidcpm": 0.71, + "prebid": { + "type": "video", + "targeting": { + "hb_bidder": "appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_pb": "0.70", + "hb_size": "200x250" + } } } - } - }, - { - "id": "losing-bid", - "impid": "my-imp-id", - "price": 0.21, - "w": 200, - "h": 250, - "crid": "creative-2", - "ext": { - "origbidcpm": 0.21, - "prebid": { - "type": "video" + }, + { + "id": "losing-bid", + "impid": "my-imp-id", + "price": 0.21, + "w": 200, + "h": 250, + "crid": "creative-2", + "ext": { + "origbidcpm": 0.21, + "prebid": { + "type": "video" + } } - } - }, - { - "id": "other-bid", - "impid": "imp-id-2", - "price": 0.61, - "w": 300, - "h": 500, - "crid": "creative-3", - "ext": { - "origbidcpm": 0.61, - "prebid": { - "type": "video", - "targeting": { - "hb_bidder": "appnexus", - "hb_cache_host": "www.pbcserver.com", - "hb_cache_path": "/pbcache/endpoint", - "hb_pb": "0.60", - "hb_size": "300x500" + }, + { + "id": "other-bid", + "impid": "imp-id-2", + "price": 0.61, + "w": 300, + "h": 500, + "crid": "creative-3", + "ext": { + "origbidcpm": 0.61, + "prebid": { + "type": "video", + "targeting": { + "hb_bidder": "appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_pb": "0.60", + "hb_size": "300x500" + } } } } - }] + ] } ] } } -} +} \ No newline at end of file diff --git a/exchange/exchangetest/targeting-with-winners.json b/exchange/exchangetest/targeting-with-winners.json index faa5fbb9cd3..ca1b1afe719 100644 --- a/exchange/exchangetest/targeting-with-winners.json +++ b/exchange/exchangetest/targeting-with-winners.json @@ -9,28 +9,40 @@ { "id": "my-imp-id", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { - "appnexus": { - "placementId": 1 - }, - "audienceNetwork": { - "placementId": "some-placement" + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + }, + "audienceNetwork": { + "placementId": "some-placement" + } + } } } }, { "id": "imp-id-2", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { - "appnexus": { - "placementId": 2 - }, - "audienceNetwork": { - "placementId": "some-other-placement" + "prebid": { + "bidder": { + "appnexus": { + "placementId": 2 + }, + "audienceNetwork": { + "placementId": "some-other-placement" + } + } } } } @@ -45,62 +57,68 @@ "outgoingRequests": { "appnexus": { "mockResponse": { - "pbsSeatBid": { - "pbsBids": [ - { - "ortbBid": { - "id": "winning-bid", - "impid": "my-imp-id", - "price": 0.71, - "w": 200, - "h": 250, - "crid": "creative-1" + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "winning-bid", + "impid": "my-imp-id", + "price": 0.71, + "w": 200, + "h": 250, + "crid": "creative-1" + }, + "bidType": "video" }, - "bidType": "video" - }, - { - "ortbBid": { - "id": "losing-bid", - "impid": "my-imp-id", - "price": 0.21, - "w": 200, - "h": 250, - "crid": "creative-2" + { + "ortbBid": { + "id": "losing-bid", + "impid": "my-imp-id", + "price": 0.21, + "w": 200, + "h": 250, + "crid": "creative-2" + }, + "bidType": "video" }, - "bidType": "video" - }, - { - "ortbBid": { - "id": "other-bid", - "impid": "imp-id-2", - "price": 0.61, - "w": 300, - "h": 500, - "crid": "creative-3" - }, - "bidType": "video" - } - ] - } + { + "ortbBid": { + "id": "other-bid", + "impid": "imp-id-2", + "price": 0.61, + "w": 300, + "h": 500, + "crid": "creative-3" + }, + "bidType": "video" + } + ], + "seat": "appnexus" + } + ] } }, "audienceNetwork": { "mockResponse": { - "pbsSeatBid": { - "pbsBids": [ - { - "ortbBid": { - "id": "contending-bid", - "impid": "my-imp-id", - "price": 0.51, - "w": 200, - "h": 250, - "crid": "creative-4" - }, - "bidType": "video" - } - ] - } + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "contending-bid", + "impid": "my-imp-id", + "price": 0.51, + "w": 200, + "h": 250, + "crid": "creative-4" + }, + "bidType": "video" + } + ], + "seat": "audienceNetwork" + } + ] } } }, @@ -110,98 +128,102 @@ "seatbid": [ { "seat": "audienceNetwork", - "bid": [{ - "id": "contending-bid", - "impid": "my-imp-id", - "price": 0.51, - "w": 200, - "h": 250, - "crid": "creative-4", - "ext": { - "origbidcpm": 0.51, - "prebid": { - "type": "video", - "targeting": { - "hb_bidder_audienceNe": "audienceNetwork", - "hb_cache_host_audien": "www.pbcserver.com", - "hb_cache_path_audien": "/pbcache/endpoint", - "hb_pb_audienceNetwor": "0.50", - "hb_size_audienceNetw": "200x250" + "bid": [ + { + "id": "contending-bid", + "impid": "my-imp-id", + "price": 0.51, + "w": 200, + "h": 250, + "crid": "creative-4", + "ext": { + "origbidcpm": 0.51, + "prebid": { + "type": "video", + "targeting": { + "hb_bidder_audienceNe": "audienceNetwork", + "hb_cache_host_audien": "www.pbcserver.com", + "hb_cache_path_audien": "/pbcache/endpoint", + "hb_pb_audienceNetwor": "0.50", + "hb_size_audienceNetw": "200x250" + } } } } - }] + ] }, { "seat": "appnexus", - "bid": [{ - "id": "winning-bid", - "impid": "my-imp-id", - "price": 0.71, - "w": 200, - "h": 250, - "crid": "creative-1", - "ext": { - "origbidcpm": 0.71, - "prebid": { - "type": "video", - "targeting": { - "hb_bidder": "appnexus", - "hb_bidder_appnexus": "appnexus", - "hb_cache_host": "www.pbcserver.com", - "hb_cache_host_appnex": "www.pbcserver.com", - "hb_cache_path": "/pbcache/endpoint", - "hb_cache_path_appnex": "/pbcache/endpoint", - "hb_pb": "0.70", - "hb_pb_appnexus": "0.70", - "hb_size": "200x250", - "hb_size_appnexus": "200x250" + "bid": [ + { + "id": "winning-bid", + "impid": "my-imp-id", + "price": 0.71, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "origbidcpm": 0.71, + "prebid": { + "type": "video", + "targeting": { + "hb_bidder": "appnexus", + "hb_bidder_appnexus": "appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_pb": "0.70", + "hb_pb_appnexus": "0.70", + "hb_size": "200x250", + "hb_size_appnexus": "200x250" + } } } - } - }, - { - "id": "losing-bid", - "impid": "my-imp-id", - "price": 0.21, - "w": 200, - "h": 250, - "crid": "creative-2", - "ext": { - "origbidcpm": 0.21, - "prebid": { - "type": "video" + }, + { + "id": "losing-bid", + "impid": "my-imp-id", + "price": 0.21, + "w": 200, + "h": 250, + "crid": "creative-2", + "ext": { + "origbidcpm": 0.21, + "prebid": { + "type": "video" + } } - } - }, - { - "id": "other-bid", - "impid": "imp-id-2", - "price": 0.61, - "w": 300, - "h": 500, - "crid": "creative-3", - "ext": { - "origbidcpm": 0.61, - "prebid": { - "type": "video", - "targeting": { - "hb_bidder": "appnexus", - "hb_bidder_appnexus": "appnexus", - "hb_cache_host": "www.pbcserver.com", - "hb_cache_host_appnex": "www.pbcserver.com", - "hb_cache_path": "/pbcache/endpoint", - "hb_cache_path_appnex": "/pbcache/endpoint", - "hb_pb": "0.60", - "hb_pb_appnexus": "0.60", - "hb_size": "300x500", - "hb_size_appnexus": "300x500" + }, + { + "id": "other-bid", + "impid": "imp-id-2", + "price": 0.61, + "w": 300, + "h": 500, + "crid": "creative-3", + "ext": { + "origbidcpm": 0.61, + "prebid": { + "type": "video", + "targeting": { + "hb_bidder": "appnexus", + "hb_bidder_appnexus": "appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_pb": "0.60", + "hb_pb_appnexus": "0.60", + "hb_size": "300x500", + "hb_size_appnexus": "300x500" + } } } } - }] + ] } ] } } -} +} \ No newline at end of file diff --git a/exchange/exchangetest/tmax.json b/exchange/exchangetest/tmax.json index 6cd11fa05bf..bb3c462d47e 100644 --- a/exchange/exchangetest/tmax.json +++ b/exchange/exchangetest/tmax.json @@ -10,11 +10,17 @@ { "id": "my-imp-id", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { - "appnexus": { - "placementId": 1 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } } } } @@ -40,7 +46,9 @@ { "id": "my-imp-id", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { "bidder": { @@ -49,11 +57,12 @@ } } ] - }, - "bidAdjustment": 1.0 + } }, "mockResponse": { - "errors": ["appnexus-error"] + "errors": [ + "appnexus-error" + ] } } }, @@ -63,9 +72,11 @@ "ext": { "tmaxrequest": 500, "errors": { - "appnexus": ["appnexus-error"] + "appnexus": [ + "appnexus-error" + ] } } } } -} +} \ No newline at end of file diff --git a/exchange/exchangetest/tricky-userids.json b/exchange/exchangetest/tricky-userids.json index 12188da2081..1991ee5f9c3 100644 --- a/exchange/exchangetest/tricky-userids.json +++ b/exchange/exchangetest/tricky-userids.json @@ -9,19 +9,25 @@ { "id": "my-imp-id", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { - "appnexus": { - "placementId": 1 - }, - "districtm": { - "placementId": 2 - }, - "rubicon": { - "accountId": 1, - "siteId": 2, - "zoneId": 3 + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + }, + "districtm": { + "placementId": 2 + }, + "rubicon": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + } + } } } } @@ -63,7 +69,9 @@ { "id": "my-imp-id", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { "bidder": { @@ -74,16 +82,19 @@ ], "ext": { "prebid": { - "aliases": { - "districtm": "appnexus" + "server": { + "datacenter": "Datacenter", + "externalurl": "http://hosturl.com", + "gvlid": 1 } } } - }, - "bidAdjustment": 1.0 + } }, "mockResponse": { - "errors": ["appnexus-error"] + "errors": [ + "appnexus-error" + ] } }, "rubicon": { @@ -100,7 +111,9 @@ { "id": "my-imp-id", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { "bidder": { @@ -113,16 +126,19 @@ ], "ext": { "prebid": { - "aliases": { - "districtm": "appnexus" + "server": { + "datacenter": "Datacenter", + "externalurl": "http://hosturl.com", + "gvlid": 1 } } } - }, - "bidAdjustment": 1.0 + } }, "mockResponse": { - "errors": ["appnexus-error"] + "errors": [ + "appnexus-error" + ] } }, "districtm": { @@ -139,7 +155,9 @@ { "id": "my-imp-id", "video": { - "mimes": ["video/mp4"] + "mimes": [ + "video/mp4" + ] }, "ext": { "bidder": { @@ -150,16 +168,19 @@ ], "ext": { "prebid": { - "aliases": { - "districtm": "appnexus" + "server": { + "datacenter": "Datacenter", + "externalurl": "http://hosturl.com", + "gvlid": 1 } } } - }, - "bidAdjustment": 1.0 + } }, "mockResponse": { - "errors": ["districtm-error"] + "errors": [ + "districtm-error" + ] } } }, @@ -168,10 +189,14 @@ "id": "some-request-id", "ext": { "errors": { - "appnexus": ["appnexus-error"], - "districtm": ["districtm-error"] + "appnexus": [ + "appnexus-error" + ], + "districtm": [ + "districtm-error" + ] } } } } -} +} \ No newline at end of file diff --git a/exchange/gdpr.go b/exchange/gdpr.go index 208ce0fdb0b..43eba4869ee 100644 --- a/exchange/gdpr.go +++ b/exchange/gdpr.go @@ -3,7 +3,9 @@ package exchange import ( "encoding/json" - "github.com/mxmCherry/openrtb/v15/openrtb2" + gpplib "github.com/prebid/go-gpp" + gppConstants "github.com/prebid/go-gpp/constants" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/gdpr" ) @@ -11,6 +13,15 @@ import ( func extractGDPR(bidRequest *openrtb2.BidRequest) (gdpr.Signal, error) { var re regsExt var err error + + if bidRequest.Regs != nil && len(bidRequest.Regs.GPPSID) > 0 { + for _, id := range bidRequest.Regs.GPPSID { + if id == int8(gppConstants.SectionTCFEU2) { + return gdpr.SignalYes, nil + } + } + return gdpr.SignalNo, nil + } if bidRequest.Regs != nil && bidRequest.Regs.Ext != nil { err = json.Unmarshal(bidRequest.Regs.Ext, &re) } @@ -21,7 +32,13 @@ func extractGDPR(bidRequest *openrtb2.BidRequest) (gdpr.Signal, error) { } // ExtractConsent will pull the consent string from an openrtb request -func extractConsent(bidRequest *openrtb2.BidRequest) (consent string, err error) { +func extractConsent(bidRequest *openrtb2.BidRequest, gpp gpplib.GppContainer) (consent string, err error) { + for i, id := range gpp.SectionTypes { + if id == gppConstants.SectionTCFEU2 { + consent = gpp.Sections[i].GetValue() + return + } + } var ue userExt if bidRequest.User != nil && bidRequest.User.Ext != nil { err = json.Unmarshal(bidRequest.User.Ext, &ue) diff --git a/exchange/gdpr_test.go b/exchange/gdpr_test.go index e44dc9702fb..382ea04a79a 100644 --- a/exchange/gdpr_test.go +++ b/exchange/gdpr_test.go @@ -4,7 +4,9 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" + gpplib "github.com/prebid/go-gpp" + gppConstants "github.com/prebid/go-gpp/constants" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/gdpr" "github.com/stretchr/testify/assert" ) @@ -47,21 +49,48 @@ func TestExtractGDPR(t *testing.T) { wantGDPR: gdpr.SignalAmbiguous, wantError: true, }, + { + description: "Regs Ext GDPR = null, GPPSID has tcf2", + giveRegs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr": null}`), GPPSID: []int8{2}}, + wantGDPR: gdpr.SignalYes, + }, + { + description: "Regs Ext GDPR = 1, GPPSID has uspv1", + giveRegs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr": 1}`), GPPSID: []int8{6}}, + wantGDPR: gdpr.SignalNo, + }, + { + description: "Regs Ext GDPR = 0, GPPSID has tcf2", + giveRegs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr": 0}`), GPPSID: []int8{2}}, + wantGDPR: gdpr.SignalYes, + }, + { + description: "Regs Ext is nil, GPPSID has tcf2", + giveRegs: &openrtb2.Regs{GPPSID: []int8{2}}, + wantGDPR: gdpr.SignalYes, + }, + { + description: "Regs Ext is nil, GPPSID has uspv1", + giveRegs: &openrtb2.Regs{GPPSID: []int8{6}}, + wantGDPR: gdpr.SignalNo, + }, } for _, tt := range tests { - bidReq := openrtb2.BidRequest{ - Regs: tt.giveRegs, - } + t.Run(tt.description, func(t *testing.T) { + bidReq := openrtb2.BidRequest{ + Regs: tt.giveRegs, + } - result, err := extractGDPR(&bidReq) - assert.Equal(t, tt.wantGDPR, result, tt.description) + result, err := extractGDPR(&bidReq) + assert.Equal(t, tt.wantGDPR, result) - if tt.wantError { - assert.NotNil(t, err, tt.description) - } else { - assert.Nil(t, err, tt.description) - } + if tt.wantError { + assert.NotNil(t, err) + } else { + assert.Nil(t, err) + } + }) } } @@ -69,6 +98,7 @@ func TestExtractConsent(t *testing.T) { tests := []struct { description string giveUser *openrtb2.User + giveGPP gpplib.GppContainer wantConsent string wantError bool }{ @@ -98,20 +128,56 @@ func TestExtractConsent(t *testing.T) { wantConsent: "", wantError: true, }, + { + description: "User Ext is nil, GPP has no GDPR", + giveUser: &openrtb2.User{Ext: nil}, + giveGPP: gpplib.GppContainer{Version: 1, SectionTypes: []gppConstants.SectionID{6}, Sections: []gpplib.Section{&upsv1Section}}, + wantConsent: "", + }, + { + description: "User Ext is nil, GPP has GDPR", + giveUser: &openrtb2.User{Ext: nil}, + giveGPP: gpplib.GppContainer{Version: 1, SectionTypes: []gppConstants.SectionID{2}, Sections: []gpplib.Section{&tcf1Section}}, + wantConsent: "BOS2bx5OS2bx5ABABBAAABoAAAAAFA", + }, + { + description: "User Ext has GDPR, GPP has GDPR", + giveUser: &openrtb2.User{Ext: json.RawMessage(`{"consent": "BSOMECONSENT"}`)}, + giveGPP: gpplib.GppContainer{Version: 1, SectionTypes: []gppConstants.SectionID{2}, Sections: []gpplib.Section{&tcf1Section}}, + wantConsent: "BOS2bx5OS2bx5ABABBAAABoAAAAAFA", + }, } for _, tt := range tests { - bidReq := openrtb2.BidRequest{ - User: tt.giveUser, - } + t.Run(tt.description, func(t *testing.T) { + bidReq := openrtb2.BidRequest{ + User: tt.giveUser, + } - result, err := extractConsent(&bidReq) - assert.Equal(t, tt.wantConsent, result, tt.description) + result, err := extractConsent(&bidReq, tt.giveGPP) + assert.Equal(t, tt.wantConsent, result, tt.description) - if tt.wantError { - assert.NotNil(t, err, tt.description) - } else { - assert.Nil(t, err, tt.description) - } + if tt.wantError { + assert.NotNil(t, err, tt.description) + } else { + assert.Nil(t, err, tt.description) + } + }) } } + +var upsv1Section mockGPPSection = mockGPPSection{sectionID: 6, value: "1YNY"} +var tcf1Section mockGPPSection = mockGPPSection{sectionID: 2, value: "BOS2bx5OS2bx5ABABBAAABoAAAAAFA"} + +type mockGPPSection struct { + sectionID gppConstants.SectionID + value string +} + +func (ms mockGPPSection) GetID() gppConstants.SectionID { + return ms.sectionID +} + +func (ms mockGPPSection) GetValue() string { + return ms.value +} diff --git a/exchange/targeting.go b/exchange/targeting.go index 0113d932a99..4b45fa3b45e 100644 --- a/exchange/targeting.go +++ b/exchange/targeting.go @@ -3,7 +3,7 @@ package exchange import ( "strconv" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" ) diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go index 18cc6fc434c..ff30b32527b 100644 --- a/exchange/targeting_test.go +++ b/exchange/targeting_test.go @@ -12,10 +12,11 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/gdpr" + "github.com/prebid/prebid-server/hooks/hookexecution" metricsConfig "github.com/prebid/prebid-server/metrics/config" "github.com/prebid/prebid-server/openrtb_ext" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/stretchr/testify/assert" ) @@ -83,12 +84,22 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op t.Errorf("Failed to create a category Fetcher: %v", error) } + gdprPermsBuilder := fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + }, + }.Builder + tcf2ConfigBuilder := fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + }.Builder + ex := &exchange{ adapterMap: buildAdapterMap(mockBids, server.URL, server.Client()), me: &metricsConfig.NilMetricsEngine{}, cache: &wellBehavedCache{}, cacheTime: time.Duration(0), - vendorListFetcher: gdpr.NewVendorListFetcher(context.Background(), config.GDPR{}, &http.Client{}, gdpr.VendorListURLMaker), + gdprPermsBuilder: gdprPermsBuilder, + tcf2ConfigBuilder: tcf2ConfigBuilder, currencyConverter: currency.NewRateConverter(&http.Client{}, "", time.Duration(0)), gdprDefaultValue: gdpr.SignalYes, categoriesFetcher: categoriesFetcher, @@ -108,11 +119,10 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op } auctionRequest := AuctionRequest{ - BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: req}, - Account: config.Account{}, - UserSyncs: &emptyUsersync{}, - GDPRPermissionsBuilder: mockGDPRPermissionsBuilder, - TCF2ConfigBuilder: mockTCF2ConfigBuilder, + BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: req}, + Account: config.Account{}, + UserSyncs: &emptyUsersync{}, + HookExecutor: &hookexecution.EmptyHookExecutor{}, } debugLog := DebugLog{} @@ -134,7 +144,7 @@ func buildAdapterMap(bids map[openrtb_ext.BidderName][]*openrtb2.Bid, mockServer adapterMap[bidder] = AdaptBidder(&mockTargetingBidder{ mockServerURL: mockServerURL, bids: bids, - }, client, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil) + }, client, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, "") } return adapterMap } @@ -159,10 +169,16 @@ func buildTargetingExt(includeCache bool, includeWinners bool, includeBidderKeys } func buildParams(t *testing.T, mockBids map[openrtb_ext.BidderName][]*openrtb2.Bid) json.RawMessage { - params := make(map[string]json.RawMessage) + params := make(map[string]interface{}) + paramsPrebid := make(map[string]interface{}) + paramsPrebidBidders := make(map[string]json.RawMessage) + for bidder := range mockBids { - params[string(bidder)] = json.RawMessage(`{"whatever":true}`) + paramsPrebidBidders[string(bidder)] = json.RawMessage(`{"whatever":true}`) } + + paramsPrebid["bidder"] = paramsPrebidBidders + params["prebid"] = paramsPrebid ext, err := json.Marshal(params) if err != nil { t.Fatalf("Failed to make imp exts: %v", err) diff --git a/exchange/utils.go b/exchange/utils.go index 9f68a6b6704..962afe407f6 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -3,13 +3,15 @@ package exchange import ( "context" "encoding/json" + "errors" "fmt" - "github.com/prebid/prebid-server/stored_responses" "math/rand" "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/go-gdpr/vendorconsent" + gpplib "github.com/prebid/go-gpp" + "github.com/prebid/openrtb/v17/openrtb2" + "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/firstpartydata" @@ -20,31 +22,33 @@ import ( "github.com/prebid/prebid-server/privacy/ccpa" "github.com/prebid/prebid-server/privacy/lmt" "github.com/prebid/prebid-server/schain" + "github.com/prebid/prebid-server/stored_responses" ) -var integrationTypeMap = map[metrics.RequestType]config.IntegrationType{ - metrics.ReqTypeAMP: config.IntegrationTypeAMP, - metrics.ReqTypeORTB2App: config.IntegrationTypeApp, - metrics.ReqTypeVideo: config.IntegrationTypeVideo, - metrics.ReqTypeORTB2Web: config.IntegrationTypeWeb, +var channelTypeMap = map[metrics.RequestType]config.ChannelType{ + metrics.ReqTypeAMP: config.ChannelAMP, + metrics.ReqTypeORTB2App: config.ChannelApp, + metrics.ReqTypeVideo: config.ChannelVideo, + metrics.ReqTypeORTB2Web: config.ChannelWeb, } const unknownBidder string = "" // cleanOpenRTBRequests splits the input request into requests which are sanitized for each bidder. Intended behavior is: // -// 1. BidRequest.Imp[].Ext will only contain the "prebid" field and a "bidder" field which has the params for the intended Bidder. -// 2. Every BidRequest.Imp[] requested Bids from the Bidder who keys it. -// 3. BidRequest.User.BuyerUID will be set to that Bidder's ID. +// 1. BidRequest.Imp[].Ext will only contain the "prebid" field and a "bidder" field which has the params for the intended Bidder. +// 2. Every BidRequest.Imp[] requested Bids from the Bidder who keys it. +// 3. BidRequest.User.BuyerUID will be set to that Bidder's ID. func cleanOpenRTBRequests(ctx context.Context, auctionReq AuctionRequest, requestExt *openrtb_ext.ExtRequest, bidderToSyncerKey map[string]string, metricsEngine metrics.MetricsEngine, gdprDefaultValue gdpr.Signal, - gdprPerms gdpr.Permissions, privacyConfig config.Privacy, - tcf2Cfg gdpr.TCF2ConfigReader, + gdprPermsBuilder gdpr.PermissionsBuilder, + tcf2ConfigBuilder gdpr.TCF2ConfigBuilder, + hostSChainNode *openrtb2.SupplyChainNode, ) (allowedBidderRequests []BidderRequest, privacyLabels metrics.PrivacyLabels, errs []error) { req := auctionReq.BidRequestWrapper @@ -53,7 +57,7 @@ func cleanOpenRTBRequests(ctx context.Context, return } - allowedBidderRequests = make([]BidderRequest, 0, 0) + allowedBidderRequests = make([]BidderRequest, 0) bidderImpWithBidResp := stored_responses.InitStoredBidResponses(req.BidRequest, auctionReq.StoredBidResponses) @@ -69,23 +73,32 @@ func cleanOpenRTBRequests(ctx context.Context, } var allBidderRequests []BidderRequest - allBidderRequests, errs = getAuctionBidderRequests(auctionReq, requestExt, bidderToSyncerKey, impsByBidder, aliases) + allBidderRequests, errs = getAuctionBidderRequests(auctionReq, requestExt, bidderToSyncerKey, impsByBidder, aliases, hostSChainNode) - bidderNameToBidderReq := buildBidResponseRequest(req.BidRequest, bidderImpWithBidResp, aliases) + bidderNameToBidderReq := buildBidResponseRequest(req.BidRequest, bidderImpWithBidResp, aliases, auctionReq.BidderImpReplaceImpID) //this function should be executed after getAuctionBidderRequests allBidderRequests = mergeBidderRequests(allBidderRequests, bidderNameToBidderReq) + var gpp gpplib.GppContainer + if req.BidRequest.Regs != nil && len(req.BidRequest.Regs.GPP) > 0 { + gpp, err = gpplib.Parse(req.BidRequest.Regs.GPP) + if err != nil { + errs = append(errs, err) + } + } + gdprSignal, err := extractGDPR(req.BidRequest) if err != nil { errs = append(errs, err) } - consent, err := extractConsent(req.BidRequest) + + consent, err := extractConsent(req.BidRequest, gpp) if err != nil { errs = append(errs, err) } gdprApplies := gdprSignal == gdpr.SignalYes || (gdprSignal == gdpr.SignalAmbiguous && gdprDefaultValue == gdpr.SignalYes) - ccpaEnforcer, err := extractCCPA(req.BidRequest, privacyConfig, &auctionReq.Account, aliases, integrationTypeMap[auctionReq.LegacyLabels.RType]) + ccpaEnforcer, err := extractCCPA(req.BidRequest, privacyConfig, &auctionReq.Account, aliases, channelTypeMap[auctionReq.LegacyLabels.RType], gpp) if err != nil { errs = append(errs, err) } @@ -103,9 +116,13 @@ func cleanOpenRTBRequests(ctx context.Context, privacyLabels.COPPAEnforced = privacyEnforcement.COPPA privacyLabels.LMTEnforced = lmtEnforcer.ShouldEnforce(unknownBidder) + tcf2Cfg := tcf2ConfigBuilder(privacyConfig.GDPR.TCF2, auctionReq.Account.GDPR) + var gdprEnforced bool + var gdprPerms gdpr.Permissions = &gdpr.AlwaysAllow{} + if gdprApplies { - gdprEnforced = tcf2Cfg.IntegrationEnabled(integrationTypeMap[auctionReq.LegacyLabels.RType]) + gdprEnforced = tcf2Cfg.ChannelEnabled(channelTypeMap[auctionReq.LegacyLabels.RType]) } if gdprEnforced { @@ -115,6 +132,14 @@ func cleanOpenRTBRequests(ctx context.Context, version := int(parsedConsent.Version()) privacyLabels.GDPRTCFVersion = metrics.TCFVersionToValue(version) } + + gdprRequestInfo := gdpr.RequestInfo{ + AliasGVLIDs: aliasesGVLIDs, + Consent: consent, + GDPRSignal: gdprSignal, + PublisherID: auctionReq.LegacyLabels.PubID, + } + gdprPerms = gdprPermsBuilder(tcf2Cfg, gdprRequestInfo) } // bidder level privacy policies @@ -126,8 +151,7 @@ func cleanOpenRTBRequests(ctx context.Context, // GDPR if gdprEnforced { - var publisherID = auctionReq.LegacyLabels.PubID - auctionPermissions, err := gdprPerms.AuctionActivitiesAllowed(ctx, bidderRequest.BidderCoreName, bidderRequest.BidderName, publisherID, gdprSignal, consent, aliasesGVLIDs) + auctionPermissions, err := gdprPerms.AuctionActivitiesAllowed(ctx, bidderRequest.BidderCoreName, bidderRequest.BidderName) bidRequestAllowed = auctionPermissions.AllowBidRequest if err == nil { @@ -149,7 +173,6 @@ func cleanOpenRTBRequests(ctx context.Context, if bidRequestAllowed { privacyEnforcement.Apply(bidderRequest.BidRequest) - allowedBidderRequests = append(allowedBidderRequests, bidderRequest) } } @@ -157,16 +180,16 @@ func cleanOpenRTBRequests(ctx context.Context, return } -func ccpaEnabled(account *config.Account, privacyConfig config.Privacy, requestType config.IntegrationType) bool { - if accountEnabled := account.CCPA.EnabledForIntegrationType(requestType); accountEnabled != nil { +func ccpaEnabled(account *config.Account, privacyConfig config.Privacy, requestType config.ChannelType) bool { + if accountEnabled := account.CCPA.EnabledForChannelType(requestType); accountEnabled != nil { return *accountEnabled } return privacyConfig.CCPA.Enforce } -func extractCCPA(orig *openrtb2.BidRequest, privacyConfig config.Privacy, account *config.Account, aliases map[string]string, requestType config.IntegrationType) (privacy.PolicyEnforcer, error) { +func extractCCPA(orig *openrtb2.BidRequest, privacyConfig config.Privacy, account *config.Account, aliases map[string]string, requestType config.ChannelType, gpp gpplib.GppContainer) (privacy.PolicyEnforcer, error) { // Quick extra wrapper until RequestWrapper makes its way into CleanRequests - ccpaPolicy, err := ccpa.ReadFromRequestWrapper(&openrtb_ext.RequestWrapper{BidRequest: orig}) + ccpaPolicy, err := ccpa.ReadFromRequestWrapper(&openrtb_ext.RequestWrapper{BidRequest: orig}, gpp) if err != nil { return privacy.NilPolicyEnforcer{}, err } @@ -195,7 +218,8 @@ func getAuctionBidderRequests(auctionRequest AuctionRequest, requestExt *openrtb_ext.ExtRequest, bidderToSyncerKey map[string]string, impsByBidder map[string][]openrtb2.Imp, - aliases map[string]string) ([]BidderRequest, []error) { + aliases map[string]string, + hostSChainNode *openrtb2.SupplyChainNode) ([]BidderRequest, []error) { bidderRequests := make([]BidderRequest, 0, len(impsByBidder)) req := auctionRequest.BidRequestWrapper @@ -204,12 +228,12 @@ func getAuctionBidderRequests(auctionRequest AuctionRequest, return nil, []error{err} } - bidderParamsInReqExt, err := adapters.ExtractReqExtBidderParamsEmbeddedMap(req.BidRequest) + bidderParamsInReqExt, err := adapters.ExtractReqExtBidderParamsMap(req.BidRequest) if err != nil { return nil, []error{err} } - sChainWriter, err := schain.NewSChainWriter(requestExt) + sChainWriter, err := schain.NewSChainWriter(requestExt, hostSChainNode) if err != nil { return nil, []error{err} } @@ -223,23 +247,10 @@ func getAuctionBidderRequests(auctionRequest AuctionRequest, sChainWriter.Write(&reqCopy, bidder) - 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) + reqCopy.Ext, err = buildRequestExtForBidder(bidder, req.BidRequest.Ext, requestExt, bidderParamsInReqExt, auctionRequest.Account.AlternateBidderCodes) 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)) @@ -272,26 +283,83 @@ func getAuctionBidderRequests(auctionRequest 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 { +func buildRequestExtForBidder(bidder string, requestExt json.RawMessage, requestExtParsed *openrtb_ext.ExtRequest, bidderParamsInReqExt map[string]json.RawMessage, cfgABC *openrtb_ext.ExtAlternateBidderCodes) (json.RawMessage, error) { + // Resolve alternatebiddercode for current bidder + var reqABC *openrtb_ext.ExtAlternateBidderCodes + if len(requestExt) != 0 && requestExtParsed != nil && requestExtParsed.Prebid.AlternateBidderCodes != nil { + reqABC = requestExtParsed.Prebid.AlternateBidderCodes + } + alternateBidderCodes := buildRequestExtAlternateBidderCodes(bidder, cfgABC, reqABC) + + if (len(requestExt) == 0 || requestExtParsed == nil) && alternateBidderCodes == nil { + return json.RawMessage(``), nil + } + + // Resolve Bidder Params + var bidderParams json.RawMessage + if bidderParamsInReqExt != nil { + bidderParams = bidderParamsInReqExt[bidder] + } + + // Copy Allowed Fields + // Per: https://docs.prebid.org/prebid-server/endpoints/openrtb2/pbs-endpoint-auction.html#prebid-server-ortb2-extension-summary + prebid := openrtb_ext.ExtRequestPrebid{ + BidderParams: bidderParams, + AlternateBidderCodes: alternateBidderCodes, + } + + if requestExtParsed != nil { + prebid.CurrencyConversions = requestExtParsed.Prebid.CurrencyConversions + prebid.Integration = requestExtParsed.Prebid.Integration + prebid.Channel = requestExtParsed.Prebid.Channel + prebid.Debug = requestExtParsed.Prebid.Debug + prebid.Server = requestExtParsed.Prebid.Server + } + + // Marshal New Prebid Object + prebidJson, err := json.Marshal(prebid) + if err != nil { + return nil, err + } + + // Update Ext With Prebid Json + extMap := make(map[string]json.RawMessage) + if len(requestExt) != 0 { + if err := json.Unmarshal(requestExt, &extMap); err != nil { return nil, err } } - return params, nil + extMap["prebid"] = prebidJson + + return json.Marshal(extMap) } -func getExtJson(req *openrtb2.BidRequest, unpackedExt *openrtb_ext.ExtRequest) (json.RawMessage, error) { - if len(req.Ext) == 0 || unpackedExt == nil { - return json.RawMessage(``), nil +func buildRequestExtAlternateBidderCodes(bidder string, accABC *openrtb_ext.ExtAlternateBidderCodes, reqABC *openrtb_ext.ExtAlternateBidderCodes) *openrtb_ext.ExtAlternateBidderCodes { + if reqABC != nil { + alternateBidderCodes := &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: reqABC.Enabled, + } + if bidderCodes, ok := reqABC.Bidders[bidder]; ok { + alternateBidderCodes.Bidders = map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + bidder: bidderCodes, + } + } + return alternateBidderCodes } - extCopy := *unpackedExt - extCopy.Prebid.SChains = nil - return json.Marshal(extCopy) + if accABC != nil { + alternateBidderCodes := &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: accABC.Enabled, + } + if bidderCodes, ok := accABC.Bidders[bidder]; ok { + alternateBidderCodes.Bidders = map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + bidder: bidderCodes, + } + } + return alternateBidderCodes + } + + return nil } // extractBuyerUIDs parses the values from user.ext.prebid.buyeruids, and then deletes those values from the ext. @@ -364,7 +432,7 @@ func splitImps(imps []openrtb2.Imp) (map[string][]openrtb2.Imp, error) { return nil, fmt.Errorf("unable to remove other bidder fields for imp[%d]: %v", i, err) } - for bidder, bidderExt := range extractBidderExts(impExt, impExtPrebidBidder) { + for bidder, bidderExt := range impExtPrebidBidder { impCopy := imp sanitizedImpExt[openrtb_ext.PrebidExtBidderKey] = bidderExt @@ -383,17 +451,28 @@ func splitImps(imps []openrtb2.Imp) (map[string][]openrtb2.Imp, error) { } func createSanitizedImpExt(impExt, impExtPrebid map[string]json.RawMessage) (map[string]json.RawMessage, error) { - sanitizedImpExt := make(map[string]json.RawMessage, 3) + sanitizedImpExt := make(map[string]json.RawMessage, 6) + sanitizedImpPrebidExt := make(map[string]json.RawMessage, 2) + + // copy allowed imp[].ext.prebid fields + if v, exists := impExtPrebid["is_rewarded_inventory"]; exists { + sanitizedImpPrebidExt["is_rewarded_inventory"] = v + } + + if v, exists := impExtPrebid["options"]; exists { + sanitizedImpPrebidExt["options"] = v + } - delete(impExtPrebid, openrtb_ext.PrebidExtBidderKey) - if len(impExtPrebid) > 0 { - if impExtPrebidJSON, err := json.Marshal(impExtPrebid); err == nil { + // marshal sanitized imp[].ext.prebid + if len(sanitizedImpPrebidExt) > 0 { + if impExtPrebidJSON, err := json.Marshal(sanitizedImpPrebidExt); err == nil { sanitizedImpExt[openrtb_ext.PrebidExtKey] = impExtPrebidJSON } else { return nil, fmt.Errorf("cannot marshal ext.prebid: %v", err) } } + // copy reserved imp[].ext fields known to not be bidder names if v, exists := impExt[openrtb_ext.FirstPartyDataExtKey]; exists { sanitizedImpExt[openrtb_ext.FirstPartyDataExtKey] = v } @@ -410,29 +489,11 @@ func createSanitizedImpExt(impExt, impExtPrebid map[string]json.RawMessage) (map sanitizedImpExt[openrtb_ext.GPIDKey] = v } - return sanitizedImpExt, nil -} - -func extractBidderExts(impExt, impExtPrebidBidders map[string]json.RawMessage) map[string]json.RawMessage { - bidderExts := make(map[string]json.RawMessage) - - // prefer imp.ext.prebid.bidder.BIDDER - for bidder, bidderExt := range impExtPrebidBidders { - bidderExts[bidder] = bidderExt - } - - // fallback to imp.BIDDER - for bidder, bidderExt := range impExt { - if isSpecialField(bidder) { - continue - } - - if _, exists := bidderExts[bidder]; !exists { - bidderExts[bidder] = bidderExt - } + if v, exists := impExt[string(openrtb_ext.TIDKey)]; exists { + sanitizedImpExt[openrtb_ext.TIDKey] = v } - return bidderExts + return sanitizedImpExt, nil } func isSpecialField(bidder string) bool { @@ -440,7 +501,8 @@ func isSpecialField(bidder string) bool { bidder == openrtb_ext.FirstPartyDataExtKey || bidder == openrtb_ext.SKAdNExtKey || bidder == openrtb_ext.GPIDKey || - bidder == openrtb_ext.PrebidExtKey + bidder == openrtb_ext.PrebidExtKey || + bidder == openrtb_ext.TIDKey } // prepareUser changes req.User so that it's ready for the given bidder. @@ -499,7 +561,7 @@ func removeUnpermissionedEids(request *openrtb2.BidRequest, bidder string, reque return nil } - var eids []openrtb_ext.ExtUserEid + var eids []openrtb2.EID if err := json.Unmarshal(eidsJSON, &eids); err != nil { return err } @@ -515,7 +577,7 @@ func removeUnpermissionedEids(request *openrtb2.BidRequest, bidder string, reque eidRules[p.Source] = p.Bidders } - eidsAllowed := make([]openrtb_ext.ExtUserEid, 0, len(eids)) + eidsAllowed := make([]openrtb2.EID, 0, len(eids)) for _, eid := range eids { allowed := false if rule, hasRule := eidRules[eid.Source]; hasRule { @@ -629,7 +691,7 @@ func extractBidRequestExt(bidRequest *openrtb2.BidRequest) (*openrtb_ext.ExtRequ requestExt := &openrtb_ext.ExtRequest{} if bidRequest == nil { - return requestExt, fmt.Errorf("Error bidRequest should not be nil") + return requestExt, errors.New("Error bidRequest should not be nil") } if len(bidRequest.Ext) > 0 { @@ -731,14 +793,22 @@ func applyFPD(fpd *firstpartydata.ResolvedFirstPartyData, bidReq *openrtb2.BidRe bidReq.App = fpd.App } if fpd.User != nil { + //BuyerUID is a value obtained between fpd extraction and fpd application. + //BuyerUID needs to be set back to fpd before applying this fpd to final bidder request + if bidReq.User != nil && len(bidReq.User.BuyerUID) > 0 { + fpd.User.BuyerUID = bidReq.User.BuyerUID + } bidReq.User = fpd.User } } func buildBidResponseRequest(req *openrtb2.BidRequest, bidderImpResponses stored_responses.BidderImpsWithBidResponses, - aliases map[string]string) map[openrtb_ext.BidderName]BidderRequest { + aliases map[string]string, + bidderImpReplaceImpID stored_responses.BidderImpReplaceImpID) map[openrtb_ext.BidderName]BidderRequest { + bidderToBidderResponse := make(map[openrtb_ext.BidderName]BidderRequest) + for bidderName, impResps := range bidderImpResponses { resolvedBidder := resolveBidder(string(bidderName), aliases) bidderToBidderResponse[bidderName] = BidderRequest{ @@ -746,6 +816,7 @@ func buildBidResponseRequest(req *openrtb2.BidRequest, BidderCoreName: resolvedBidder, BidderName: bidderName, BidderStoredResponses: impResps, + ImpReplaceImpId: bidderImpReplaceImpID[string(resolvedBidder)], BidderLabels: metrics.AdapterLabels{Adapter: resolvedBidder}, } } @@ -782,3 +853,11 @@ func mergeBidderRequests(allBidderRequests []BidderRequest, bidderNameToBidderRe } return allBidderRequests } + +func WrapJSONInData(data []byte) []byte { + res := make([]byte, 0, len(data)) + res = append(res, []byte(`{"data":`)...) + res = append(res, data...) + res = append(res, []byte(`}`)...) + return res +} diff --git a/exchange/utils_test.go b/exchange/utils_test.go index f68223b2966..d499ea4311c 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -5,9 +5,10 @@ import ( "encoding/json" "errors" "fmt" + "sort" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/firstpartydata" @@ -27,15 +28,15 @@ type permissionsMock struct { activitiesError error } -func (p *permissionsMock) HostCookiesAllowed(ctx context.Context, gdpr gdpr.Signal, consent string) (bool, error) { +func (p *permissionsMock) HostCookiesAllowed(ctx context.Context) (bool, error) { return true, nil } -func (p *permissionsMock) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, gdpr gdpr.Signal, consent string) (bool, error) { +func (p *permissionsMock) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName) (bool, error) { return true, nil } -func (p *permissionsMock) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string, aliasGVLIDs map[string]uint16) (permissions gdpr.AuctionPermissions, err error) { +func (p *permissionsMock) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions gdpr.AuctionPermissions, err error) { permissions = gdpr.AuctionPermissions{ PassGeo: p.passGeo, PassID: p.passID, @@ -55,6 +56,22 @@ func (p *permissionsMock) AuctionActivitiesAllowed(ctx context.Context, bidderCo return permissions, p.activitiesError } +type fakePermissionsBuilder struct { + permissions gdpr.Permissions +} + +func (fpb fakePermissionsBuilder) Builder(gdpr.TCF2ConfigReader, gdpr.RequestInfo) gdpr.Permissions { + return fpb.permissions +} + +type fakeTCF2ConfigBuilder struct { + cfg gdpr.TCF2ConfigReader +} + +func (fcr fakeTCF2ConfigBuilder) Builder(hostConfig config.TCF2, accountConfig config.AccountGDPR) gdpr.TCF2ConfigReader { + return fcr.cfg +} + func assertReq(t *testing.T, bidderRequests []BidderRequest, applyCOPPA bool, consentedVendors map[string]bool) { // assert individual bidder requests @@ -171,25 +188,6 @@ func TestSplitImps(t *testing.T) { }, expectedError: "", }, - { - // This is a "happy path" integration test. Functionality is covered in detail by TestExtractBidderExts. - description: "Legacy imp.ext.BIDDER - 2 Imps, 2 Bidders Each", - givenImps: []openrtb2.Imp{ - {ID: "imp1", Ext: json.RawMessage(`{"bidderA":{"imp1paramA":"imp1valueA"},"bidderB":{"imp1paramB":"imp1valueB"}}`)}, - {ID: "imp2", Ext: json.RawMessage(`{"bidderA":{"imp2paramA":"imp2valueA"},"bidderB":{"imp2paramB":"imp2valueB"}}`)}, - }, - expectedImps: map[string][]openrtb2.Imp{ - "bidderA": { - {ID: "imp1", Ext: json.RawMessage(`{"bidder":{"imp1paramA":"imp1valueA"}}`)}, - {ID: "imp2", Ext: json.RawMessage(`{"bidder":{"imp2paramA":"imp2valueA"}}`)}, - }, - "bidderB": { - {ID: "imp1", Ext: json.RawMessage(`{"bidder":{"imp1paramB":"imp1valueB"}}`)}, - {ID: "imp2", Ext: json.RawMessage(`{"bidder":{"imp2paramB":"imp2valueB"}}`)}, - }, - }, - expectedError: "", - }, { description: "Malformed imp.ext", givenImps: []openrtb2.Imp{ @@ -249,13 +247,14 @@ func TestCreateSanitizedImpExt(t *testing.T) { expectedError: "", }, { - description: "imp.ext.prebid - Bidders Only", + description: "imp.ext.prebid - Bidder Only", givenImpExt: map[string]json.RawMessage{ "prebid": json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`), "data": json.RawMessage(`"anyData"`), "context": json.RawMessage(`"anyContext"`), "skadn": json.RawMessage(`"anySKAdNetwork"`), "gpid": json.RawMessage(`"anyGPID"`), + "tid": json.RawMessage(`"anyTID"`), }, givenImpExtPrebid: map[string]json.RawMessage{ "bidder": json.RawMessage(`"anyBidder"`), @@ -265,28 +264,55 @@ func TestCreateSanitizedImpExt(t *testing.T) { "context": json.RawMessage(`"anyContext"`), "skadn": json.RawMessage(`"anySKAdNetwork"`), "gpid": json.RawMessage(`"anyGPID"`), + "tid": json.RawMessage(`"anyTID"`), }, expectedError: "", }, { - description: "imp.ext.prebid - Bidders + Other Values", + description: "imp.ext.prebid - Bidder + Other Forbidden Value", givenImpExt: map[string]json.RawMessage{ "prebid": json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`), "data": json.RawMessage(`"anyData"`), "context": json.RawMessage(`"anyContext"`), "skadn": json.RawMessage(`"anySKAdNetwork"`), "gpid": json.RawMessage(`"anyGPID"`), + "tid": json.RawMessage(`"anyTID"`), }, givenImpExtPrebid: map[string]json.RawMessage{ "bidder": json.RawMessage(`"anyBidder"`), - "someOther": json.RawMessage(`"value"`), + "forbidden": json.RawMessage(`"anyValue"`), }, expected: map[string]json.RawMessage{ - "prebid": json.RawMessage(`{"someOther":"value"}`), "data": json.RawMessage(`"anyData"`), "context": json.RawMessage(`"anyContext"`), "skadn": json.RawMessage(`"anySKAdNetwork"`), "gpid": json.RawMessage(`"anyGPID"`), + "tid": json.RawMessage(`"anyTID"`), + }, + expectedError: "", + }, + { + description: "imp.ext.prebid - Bidder + Other Allowed Values", + givenImpExt: map[string]json.RawMessage{ + "prebid": json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`), + "data": json.RawMessage(`"anyData"`), + "context": json.RawMessage(`"anyContext"`), + "skadn": json.RawMessage(`"anySKAdNetwork"`), + "gpid": json.RawMessage(`"anyGPID"`), + "tid": json.RawMessage(`"anyTID"`), + }, + givenImpExtPrebid: map[string]json.RawMessage{ + "bidder": json.RawMessage(`"anyBidder"`), + "is_rewarded_inventory": json.RawMessage(`"anyIsRewardedInventory"`), + "options": json.RawMessage(`"anyOptions"`), + }, + expected: map[string]json.RawMessage{ + "prebid": json.RawMessage(`{"is_rewarded_inventory":"anyIsRewardedInventory","options":"anyOptions"}`), + "data": json.RawMessage(`"anyData"`), + "context": json.RawMessage(`"anyContext"`), + "skadn": json.RawMessage(`"anySKAdNetwork"`), + "gpid": json.RawMessage(`"anyGPID"`), + "tid": json.RawMessage(`"anyTID"`), }, expectedError: "", }, @@ -298,6 +324,7 @@ func TestCreateSanitizedImpExt(t *testing.T) { "context": json.RawMessage(`"anyContext"`), "skadn": json.RawMessage(`"anySKAdNetwork"`), "gpid": json.RawMessage(`"anyGPID"`), + "tid": json.RawMessage(`"anyTID"`), }, givenImpExtPrebid: map[string]json.RawMessage{}, expected: map[string]json.RawMessage{ @@ -305,11 +332,12 @@ func TestCreateSanitizedImpExt(t *testing.T) { "context": json.RawMessage(`"anyContext"`), "skadn": json.RawMessage(`"anySKAdNetwork"`), "gpid": json.RawMessage(`"anyGPID"`), + "tid": json.RawMessage(`"anyTID"`), }, expectedError: "", }, { - description: "imp.ext + imp.ext.prebid - Prebid Bidders Only", + description: "imp.ext + imp.ext.prebid - Prebid Bidder Only", givenImpExt: map[string]json.RawMessage{ "anyBidder": json.RawMessage(`"anyBidderValues"`), "prebid": json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`), @@ -317,6 +345,7 @@ func TestCreateSanitizedImpExt(t *testing.T) { "context": json.RawMessage(`"anyContext"`), "skadn": json.RawMessage(`"anySKAdNetwork"`), "gpid": json.RawMessage(`"anyGPID"`), + "tid": json.RawMessage(`"anyTID"`), }, givenImpExtPrebid: map[string]json.RawMessage{ "bidder": json.RawMessage(`"anyBidder"`), @@ -326,11 +355,12 @@ func TestCreateSanitizedImpExt(t *testing.T) { "context": json.RawMessage(`"anyContext"`), "skadn": json.RawMessage(`"anySKAdNetwork"`), "gpid": json.RawMessage(`"anyGPID"`), + "tid": json.RawMessage(`"anyTID"`), }, expectedError: "", }, { - description: "imp.ext + imp.ext.prebid - Prebid Bidders + Other Values", + description: "imp.ext + imp.ext.prebid - Prebid Bidder + Other Forbidden Value", givenImpExt: map[string]json.RawMessage{ "anyBidder": json.RawMessage(`"anyBidderValues"`), "prebid": json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`), @@ -338,17 +368,44 @@ func TestCreateSanitizedImpExt(t *testing.T) { "context": json.RawMessage(`"anyContext"`), "skadn": json.RawMessage(`"anySKAdNetwork"`), "gpid": json.RawMessage(`"anyGPID"`), + "tid": json.RawMessage(`"anyTID"`), }, givenImpExtPrebid: map[string]json.RawMessage{ "bidder": json.RawMessage(`"anyBidder"`), - "someOther": json.RawMessage(`"value"`), + "forbidden": json.RawMessage(`"anyValue"`), }, expected: map[string]json.RawMessage{ - "prebid": json.RawMessage(`{"someOther":"value"}`), "data": json.RawMessage(`"anyData"`), "context": json.RawMessage(`"anyContext"`), "skadn": json.RawMessage(`"anySKAdNetwork"`), "gpid": json.RawMessage(`"anyGPID"`), + "tid": json.RawMessage(`"anyTID"`), + }, + expectedError: "", + }, + { + description: "imp.ext + imp.ext.prebid - Prebid Bidder + Other Allowed Values", + givenImpExt: map[string]json.RawMessage{ + "anyBidder": json.RawMessage(`"anyBidderValues"`), + "prebid": json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`), + "data": json.RawMessage(`"anyData"`), + "context": json.RawMessage(`"anyContext"`), + "skadn": json.RawMessage(`"anySKAdNetwork"`), + "gpid": json.RawMessage(`"anyGPID"`), + "tid": json.RawMessage(`"anyTID"`), + }, + givenImpExtPrebid: map[string]json.RawMessage{ + "bidder": json.RawMessage(`"anyBidder"`), + "is_rewarded_inventory": json.RawMessage(`"anyIsRewardedInventory"`), + "options": json.RawMessage(`"anyOptions"`), + }, + expected: map[string]json.RawMessage{ + "prebid": json.RawMessage(`{"is_rewarded_inventory":"anyIsRewardedInventory","options":"anyOptions"}`), + "data": json.RawMessage(`"anyData"`), + "context": json.RawMessage(`"anyContext"`), + "skadn": json.RawMessage(`"anySKAdNetwork"`), + "gpid": json.RawMessage(`"anyGPID"`), + "tid": json.RawMessage(`"anyTID"`), }, expectedError: "", }, @@ -360,12 +417,13 @@ func TestCreateSanitizedImpExt(t *testing.T) { "context": json.RawMessage(`"anyContext"`), "skadn": json.RawMessage(`"anySKAdNetwork"`), "gpid": json.RawMessage(`"anyGPID"`), + "tid": json.RawMessage(`"anyTID"`), }, givenImpExtPrebid: map[string]json.RawMessage{ - "malformed": json.RawMessage(`json`), // String value without quotes. + "options": json.RawMessage(`malformed`), // String value without quotes. }, expected: nil, - expectedError: "cannot marshal ext.prebid: json: error calling MarshalJSON for type json.RawMessage: invalid character 'j' looking for beginning of value", + expectedError: "cannot marshal ext.prebid: json: error calling MarshalJSON for type json.RawMessage: invalid character 'm' looking for beginning of value", }, } @@ -382,84 +440,6 @@ func TestCreateSanitizedImpExt(t *testing.T) { } } -func TestExtractBidderExts(t *testing.T) { - bidderAJSON := json.RawMessage(`{"paramA":"valueA"}}`) - bidderBJSON := json.RawMessage(`{"paramB":"valueB"}}`) - - testCases := []struct { - description string - givenImpExt map[string]json.RawMessage - givenImpExtPrebidBidders map[string]json.RawMessage - expected map[string]json.RawMessage - }{ - { - description: "Nil", - givenImpExt: nil, - givenImpExtPrebidBidders: nil, - expected: map[string]json.RawMessage{}, - }, - { - description: "Empty", - givenImpExt: map[string]json.RawMessage{}, - givenImpExtPrebidBidders: map[string]json.RawMessage{}, - expected: map[string]json.RawMessage{}, - }, - { - description: "One - imp.ext.BIDDER", - givenImpExt: map[string]json.RawMessage{"bidderA": bidderAJSON}, - givenImpExtPrebidBidders: map[string]json.RawMessage{}, - expected: map[string]json.RawMessage{"bidderA": bidderAJSON}, - }, - { - description: "Many - imp.ext.BIDDER", - givenImpExt: map[string]json.RawMessage{"bidderA": bidderAJSON, "bidderB": bidderBJSON}, - givenImpExtPrebidBidders: map[string]json.RawMessage{}, - expected: map[string]json.RawMessage{"bidderA": bidderAJSON, "bidderB": bidderBJSON}, - }, - { - description: "Special Names Ignored - imp.ext.BIDDER", - givenImpExt: map[string]json.RawMessage{"prebid": json.RawMessage(`{"prebid":"value1"}}`), "context": json.RawMessage(`{"firstPartyData":"value2"}}`), "skadn": json.RawMessage(`{"skAdNetwork":"value3"}}`), "gpid": json.RawMessage(`{"gpid":"value4"}}`)}, - givenImpExtPrebidBidders: map[string]json.RawMessage{}, - expected: map[string]json.RawMessage{}, - }, - { - description: "One - imp.ext.prebid.bidder.BIDDER", - givenImpExt: map[string]json.RawMessage{}, - givenImpExtPrebidBidders: map[string]json.RawMessage{"bidderA": bidderAJSON}, - expected: map[string]json.RawMessage{"bidderA": bidderAJSON}, - }, - { - description: "Many - imp.ext.prebid.bidder.BIDDER", - givenImpExt: map[string]json.RawMessage{}, - givenImpExtPrebidBidders: map[string]json.RawMessage{"bidderA": bidderAJSON, "bidderB": bidderBJSON}, - expected: map[string]json.RawMessage{"bidderA": bidderAJSON, "bidderB": bidderBJSON}, - }, - { - description: "Special Names Not Treated Differently - imp.ext.prebid.bidder.BIDDER", - givenImpExt: map[string]json.RawMessage{}, - givenImpExtPrebidBidders: map[string]json.RawMessage{"prebid": json.RawMessage(`{"prebid":"value1"}}`), "context": json.RawMessage(`{"firstPartyData":"value2"}}`), "skadn": json.RawMessage(`{"skAdNetwork":"value3"}}`), "gpid": json.RawMessage(`{"gpid":"value4"}}`)}, - expected: map[string]json.RawMessage{"prebid": json.RawMessage(`{"prebid":"value1"}}`), "context": json.RawMessage(`{"firstPartyData":"value2"}}`), "skadn": json.RawMessage(`{"skAdNetwork":"value3"}}`), "gpid": json.RawMessage(`{"gpid":"value4"}}`)}, - }, - { - description: "Mixed - Both - imp.ext.BIDDER + imp.ext.prebid.bidder.BIDDER", - givenImpExt: map[string]json.RawMessage{"bidderA": bidderAJSON}, - givenImpExtPrebidBidders: map[string]json.RawMessage{"bidderB": bidderBJSON}, - expected: map[string]json.RawMessage{"bidderA": bidderAJSON, "bidderB": bidderBJSON}, - }, - { - description: "Mixed - Overwrites - imp.ext.BIDDER + imp.ext.prebid.bidder.BIDDER", - givenImpExt: map[string]json.RawMessage{"bidderA": json.RawMessage(`{"shouldBe":"Ignored"}}`)}, - givenImpExtPrebidBidders: map[string]json.RawMessage{"bidderA": bidderAJSON}, - expected: map[string]json.RawMessage{"bidderA": bidderAJSON}, - }, - } - - for _, test := range testCases { - result := extractBidderExts(test.givenImpExt, test.givenImpExtPrebidBidders) - assert.Equal(t, test.expected, result, test.description) - } -} - func TestCleanOpenRTBRequests(t *testing.T) { testCases := []struct { req AuctionRequest @@ -495,9 +475,20 @@ func TestCleanOpenRTBRequests(t *testing.T) { } for _, test := range testCases { + metricsMock := metrics.MetricsEngineMock{} bidderToSyncerKey := map[string]string{} - bidderRequests, _, err := cleanOpenRTBRequests(context.Background(), test.req, nil, bidderToSyncerKey, &metricsMock, gdpr.SignalNo, gdpr.AlwaysAllow{}, privacyConfig, &config.TCF2{}) + + gdprPermsBuilder := fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + }, + }.Builder + tcf2ConfigBuilder := fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + }.Builder + + bidderRequests, _, err := cleanOpenRTBRequests(context.Background(), test.req, nil, bidderToSyncerKey, &metricsMock, gdpr.SignalNo, privacyConfig, gdprPermsBuilder, tcf2ConfigBuilder, nil) if test.hasError { assert.NotNil(t, err, "Error shouldn't be nil") } else { @@ -554,7 +545,17 @@ func TestCleanOpenRTBRequestsWithFPD(t *testing.T) { for _, test := range testCases { metricsMock := metrics.MetricsEngineMock{} bidderToSyncerKey := map[string]string{} - bidderRequests, _, err := cleanOpenRTBRequests(context.Background(), test.req, nil, bidderToSyncerKey, &metricsMock, gdpr.SignalNo, gdpr.AlwaysAllow{}, config.Privacy{}, &config.TCF2{}) + + gdprPermissionsBuilder := fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + }, + }.Builder + tcf2ConfigBuilder := fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + }.Builder + + bidderRequests, _, err := cleanOpenRTBRequests(context.Background(), test.req, nil, bidderToSyncerKey, &metricsMock, gdpr.SignalNo, config.Privacy{}, gdprPermissionsBuilder, tcf2ConfigBuilder, nil) assert.Empty(t, err, "No errors should be returned") for _, bidderRequest := range bidderRequests { bidderName := bidderRequest.BidderName @@ -562,6 +563,7 @@ func TestCleanOpenRTBRequestsWithFPD(t *testing.T) { 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") + assert.Equal(t, test.req.BidRequestWrapper.User.BuyerUID, bidderRequest.BidRequest.User.BuyerUID, "Incorrect FPD user buyerUID") } else { assert.Equal(t, "", bidderRequest.BidRequest.Site.Name, "Incorrect FPD site name") assert.Equal(t, "", bidderRequest.BidRequest.User.Keywords, "Incorrect FPD user keywords") @@ -592,7 +594,7 @@ func TestCleanOpenRTBRequestsWithBidResponses(t *testing.T) { W: 300, H: 250, }, - Ext: json.RawMessage(`{"bidderA": {"placementId":"123"}}`), + Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`), }, }, expectedBidderRequests: map[string]BidderRequest{ @@ -616,11 +618,11 @@ func TestCleanOpenRTBRequestsWithBidResponses(t *testing.T) { W: 300, H: 250, }, - Ext: json.RawMessage(`{"bidderA": {"placementId":"123"}}`), + Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`), }, { ID: "imp-id2", - Ext: json.RawMessage(`{"bidderA": {"placementId":"123"}}`), + Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`), }, }, expectedBidderRequests: map[string]BidderRequest{ @@ -646,7 +648,7 @@ func TestCleanOpenRTBRequestsWithBidResponses(t *testing.T) { W: 300, H: 250, }, - Ext: json.RawMessage(`{"bidderA": {"placementId":"123"}}`), + Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`), }, }, expectedBidderRequests: map[string]BidderRequest{ @@ -677,11 +679,11 @@ func TestCleanOpenRTBRequestsWithBidResponses(t *testing.T) { W: 300, H: 250, }, - Ext: json.RawMessage(`{"bidderA": {"placementId":"123"}}`), + Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`), }, { ID: "imp-id2", - Ext: json.RawMessage(`{"bidderA": {"placementId":"123"}}`), + Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`), }, }, expectedBidderRequests: map[string]BidderRequest{ @@ -709,7 +711,7 @@ func TestCleanOpenRTBRequestsWithBidResponses(t *testing.T) { imps: []openrtb2.Imp{ { ID: "imp-id3", - Ext: json.RawMessage(`{"bidderC": {"placementId":"1234"}}`), + Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderC":{"placementId":"1234"}}}}`), }, { ID: "imp-id1", @@ -717,11 +719,11 @@ func TestCleanOpenRTBRequestsWithBidResponses(t *testing.T) { W: 300, H: 250, }, - Ext: json.RawMessage(`{"bidderA": {"placementId":"123"}}`), + Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`), }, { ID: "imp-id2", - Ext: json.RawMessage(`{"bidderA": {"placementId":"123"}}`), + Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`), }, }, expectedBidderRequests: map[string]BidderRequest{ @@ -756,11 +758,11 @@ func TestCleanOpenRTBRequestsWithBidResponses(t *testing.T) { imps: []openrtb2.Imp{ { ID: "imp-id1", - Ext: json.RawMessage(`{"bidderA": {"placementId":"123"}}`), + Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`), }, { ID: "imp-id2", - Ext: json.RawMessage(`{"bidderA": {"placementId":"123"}}`), + Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`), }, }, expectedBidderRequests: map[string]BidderRequest{ @@ -806,11 +808,22 @@ func TestCleanOpenRTBRequestsWithBidResponses(t *testing.T) { for _, test := range testCases { metricsMock := metrics.MetricsEngineMock{} bidderToSyncerKey := map[string]string{} + + gdprPermissionsBuilder := fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + }, + }.Builder + tcf2ConfigBuilder := fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + }.Builder + auctionReq := AuctionRequest{ BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Imp: test.imps}}, UserSyncs: &emptyUsersync{}, StoredBidResponses: test.storedBidResponses, } + actualBidderRequests, _, err := cleanOpenRTBRequests( context.Background(), auctionReq, @@ -818,9 +831,10 @@ func TestCleanOpenRTBRequestsWithBidResponses(t *testing.T) { bidderToSyncerKey, &metricsMock, gdpr.SignalNo, - gdpr.AlwaysAllow{}, config.Privacy{}, - &config.TCF2{}) + gdprPermissionsBuilder, + tcf2ConfigBuilder, + nil) assert.Empty(t, err, "No errors should be returned") assert.Len(t, actualBidderRequests, len(test.expectedBidderRequests), "result len doesn't match for testCase %s", test.description) for _, actualBidderRequest := range actualBidderRequests { @@ -972,6 +986,15 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) { Account: accountConfig, } + gdprPermissionsBuilder := fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + }, + }.Builder + tcf2ConfigBuilder := fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(config.TCF2{}, accountConfig.GDPR), + }.Builder + bidderToSyncerKey := map[string]string{} bidderRequests, privacyLabels, errs := cleanOpenRTBRequests( context.Background(), @@ -980,9 +1003,10 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) { bidderToSyncerKey, &metrics.MetricsEngineMock{}, gdpr.SignalNo, - gdpr.AlwaysAllow{}, privacyConfig, - &config.TCF2{}) + gdprPermissionsBuilder, + tcf2ConfigBuilder, + nil) result := bidderRequests[0] assert.Nil(t, errs) @@ -1035,6 +1059,15 @@ func TestCleanOpenRTBRequestsCCPAErrors(t *testing.T) { UserSyncs: &emptyUsersync{}, } + gdprPermissionsBuilder := fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + }, + }.Builder + tcf2ConfigBuilder := fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + }.Builder + privacyConfig := config.Privacy{ CCPA: config.CCPA{ Enforce: true, @@ -1042,7 +1075,8 @@ func TestCleanOpenRTBRequestsCCPAErrors(t *testing.T) { } bidderToSyncerKey := map[string]string{} metrics := metrics.MetricsEngineMock{} - _, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, &reqExtStruct, bidderToSyncerKey, &metrics, gdpr.SignalNo, gdpr.AlwaysAllow{}, privacyConfig, &config.TCF2{}) + + _, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, &reqExtStruct, bidderToSyncerKey, &metrics, gdpr.SignalNo, privacyConfig, gdprPermissionsBuilder, tcf2ConfigBuilder, nil) assert.ElementsMatch(t, []error{test.expectError}, errs, test.description) } @@ -1082,9 +1116,19 @@ func TestCleanOpenRTBRequestsCOPPA(t *testing.T) { UserSyncs: &emptyUsersync{}, } + gdprPermissionsBuilder := fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + }, + }.Builder + tcf2ConfigBuilder := fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + }.Builder + bidderToSyncerKey := map[string]string{} metrics := metrics.MetricsEngineMock{} - bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, bidderToSyncerKey, &metrics, gdpr.SignalNo, &gdpr.AlwaysAllow{}, config.Privacy{}, &config.TCF2{}) + + bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, bidderToSyncerKey, &metrics, gdpr.SignalNo, config.Privacy{}, gdprPermissionsBuilder, tcf2ConfigBuilder, nil) result := bidderRequests[0] assert.Nil(t, errs) @@ -1168,9 +1212,18 @@ func TestCleanOpenRTBRequestsSChain(t *testing.T) { UserSyncs: &emptyUsersync{}, } + gdprPermissionsBuilder := fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + }, + }.Builder + tcf2ConfigBuilder := fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + }.Builder + bidderToSyncerKey := map[string]string{} metrics := metrics.MetricsEngineMock{} - bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, bidderToSyncerKey, &metrics, gdpr.SignalNo, gdpr.AlwaysAllow{}, config.Privacy{}, &config.TCF2{}) + bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, bidderToSyncerKey, &metrics, gdpr.SignalNo, config.Privacy{}, gdprPermissionsBuilder, tcf2ConfigBuilder, nil) if test.hasError == true { assert.NotNil(t, errs) assert.Len(t, bidderRequests, 0) @@ -1225,9 +1278,19 @@ func TestCleanOpenRTBRequestsBidderParams(t *testing.T) { UserSyncs: &emptyUsersync{}, } + gdprPermissionsBuilder := fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + }, + }.Builder + tcf2ConfigBuilder := fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + }.Builder + bidderToSyncerKey := map[string]string{} metrics := metrics.MetricsEngineMock{} - bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, bidderToSyncerKey, &metrics, gdpr.SignalNo, gdpr.AlwaysAllow{}, config.Privacy{}, &config.TCF2{}) + + bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, bidderToSyncerKey, &metrics, gdpr.SignalNo, config.Privacy{}, gdprPermissionsBuilder, tcf2ConfigBuilder, nil) if test.hasError == true { assert.NotNil(t, errs) assert.Len(t, bidderRequests, 0) @@ -1900,6 +1963,15 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) { UserSyncs: &emptyUsersync{}, } + gdprPermissionsBuilder := fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + }, + }.Builder + tcf2ConfigBuilder := fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + }.Builder + privacyConfig := config.Privacy{ LMT: config.LMT{ Enforce: test.enforceLMT, @@ -1908,7 +1980,7 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) { bidderToSyncerKey := map[string]string{} metrics := metrics.MetricsEngineMock{} - results, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, bidderToSyncerKey, &metrics, gdpr.SignalNo, &gdpr.AlwaysAllow{}, privacyConfig, &config.TCF2{}) + results, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, bidderToSyncerKey, &metrics, gdpr.SignalNo, privacyConfig, gdprPermissionsBuilder, tcf2ConfigBuilder, nil) result := results[0] assert.Nil(t, errs) @@ -2114,6 +2186,21 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { Account: accountConfig, } + gdprPermissionsBuilder := fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + passGeo: !test.gdprScrub, + passID: !test.gdprScrub, + activitiesError: test.permissionsError, + }, + }.Builder + tcf2ConfigBuilder := fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config( + privacyConfig.GDPR.TCF2, + accountConfig.GDPR, + ), + }.Builder + bidderToSyncerKey := map[string]string{} gdprDefaultValue := gdpr.SignalYes @@ -2128,9 +2215,10 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { bidderToSyncerKey, &metrics.MetricsEngineMock{}, gdprDefaultValue, - &permissionsMock{allowAllBidders: true, passGeo: !test.gdprScrub, passID: !test.gdprScrub, activitiesError: test.permissionsError}, privacyConfig, - gdpr.NewTCF2Config(privacyConfig.GDPR.TCF2, accountConfig.GDPR)) + gdprPermissionsBuilder, + tcf2ConfigBuilder, + nil) result := results[0] if test.expectError { @@ -2186,7 +2274,7 @@ func TestCleanOpenRTBRequestsGDPRBlockBidRequest(t *testing.T) { req.Regs = &openrtb2.Regs{ Ext: json.RawMessage(`{"gdpr":1}`), } - req.Imp[0].Ext = json.RawMessage(`{"appnexus": {"placementId": 1}, "rubicon": {}}`) + req.Imp[0].Ext = json.RawMessage(`{"prebid":{"bidder":{"appnexus": {"placementId": 1}, "rubicon": {}}}}`) privacyConfig := config.Privacy{ GDPR: config.GDPR{ @@ -2209,6 +2297,18 @@ func TestCleanOpenRTBRequestsGDPRBlockBidRequest(t *testing.T) { Account: accountConfig, } + gdprPermissionsBuilder := fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowedBidders: test.gdprAllowedBidders, + passGeo: true, + passID: true, + activitiesError: nil, + }, + }.Builder + tcf2ConfigBuilder := fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(privacyConfig.GDPR.TCF2, accountConfig.GDPR), + }.Builder + metricsMock := metrics.MetricsEngineMock{} metricsMock.Mock.On("RecordAdapterGDPRRequestBlocked", mock.Anything).Return() @@ -2220,9 +2320,10 @@ func TestCleanOpenRTBRequestsGDPRBlockBidRequest(t *testing.T) { bidderToSyncerKey, &metricsMock, gdpr.SignalNo, - &permissionsMock{allowedBidders: test.gdprAllowedBidders, passGeo: true, passID: true, activitiesError: nil}, privacyConfig, - &privacyConfig.GDPR.TCF2) + gdprPermissionsBuilder, + tcf2ConfigBuilder, + nil) // extract bidder name from each request in the results bidders := []openrtb_ext.BidderName{} @@ -2242,6 +2343,146 @@ func TestCleanOpenRTBRequestsGDPRBlockBidRequest(t *testing.T) { } } +func TestBuildRequestExtForBidder(t *testing.T) { + bidder := "foo" + bidderParams := json.RawMessage(`"bar"`) + + testCases := []struct { + description string + requestExt json.RawMessage + bidderParams map[string]json.RawMessage + alternateBidderCodes *openrtb_ext.ExtAlternateBidderCodes + expectedJson json.RawMessage + }{ + { + description: "Nil", + bidderParams: nil, + requestExt: nil, + alternateBidderCodes: nil, + expectedJson: json.RawMessage(``), + }, + { + description: "Empty", + bidderParams: nil, + alternateBidderCodes: nil, + requestExt: json.RawMessage(`{}`), + expectedJson: json.RawMessage(`{"prebid":{}}`), + }, + { + description: "Prebid - Allowed Fields Only", + bidderParams: nil, + requestExt: json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}}}`), + expectedJson: json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}}}`), + }, + { + description: "Prebid - Allowed Fields + Bidder Params", + bidderParams: map[string]json.RawMessage{bidder: bidderParams}, + requestExt: json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}}}`), + expectedJson: json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}, "bidderparams":"bar"}}`), + }, + { + description: "Other", + bidderParams: nil, + requestExt: json.RawMessage(`{"other":"foo"}`), + expectedJson: json.RawMessage(`{"other":"foo","prebid":{}}`), + }, + { + description: "Prebid + Other + Bider Params", + bidderParams: map[string]json.RawMessage{bidder: bidderParams}, + requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}}}`), + expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}, "bidderparams":"bar"}}`), + }, + { + description: "Prebid + AlternateBidderCodes in pbs config (default explicitly defined)", + bidderParams: map[string]json.RawMessage{bidder: bidderParams}, + alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{}, + requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}}}`), + expectedJson: json.RawMessage(`{"other":"foo","prebid":{"alternatebiddercodes":{"enabled":false,"bidders":null},"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"bidderparams":"bar"}}`), + }, + { + description: "Prebid + AlternateBidderCodes in pbs config", + bidderParams: map[string]json.RawMessage{bidder: bidderParams}, + alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{Enabled: true, Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{"foo": {Enabled: true, AllowedBidderCodes: []string{"*"}}}}, + requestExt: json.RawMessage(`{"other":"foo"}`), + expectedJson: json.RawMessage(`{"other":"foo","prebid":{"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["*"]}}},"bidderparams":"bar"}}`), + }, + { + description: "Prebid + AlternateBidderCodes in pbs config but current bidder not in AlternateBidderCodes config", + bidderParams: map[string]json.RawMessage{bidder: bidderParams}, + alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{Enabled: true, Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{"bar": {Enabled: true, AllowedBidderCodes: []string{"*"}}}}, + requestExt: json.RawMessage(`{"other":"foo"}`), + expectedJson: json.RawMessage(`{"other":"foo","prebid":{"alternatebiddercodes":{"enabled":true,"bidders":null},"bidderparams":"bar"}}`), + }, + { + description: "Prebid + AlternateBidderCodes in request", + bidderParams: map[string]json.RawMessage{bidder: bidderParams}, + alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{}, + requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["foo2"]},"bar":{"enabled":true,"allowedbiddercodes":["ix"]}}}}}`), + expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["foo2"]}}},"bidderparams":"bar"}}`), + }, + { + description: "Prebid + AlternateBidderCodes in request but current bidder not in AlternateBidderCodes config", + bidderParams: map[string]json.RawMessage{bidder: bidderParams}, + alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{}, + requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"bar":{"enabled":true,"allowedbiddercodes":["ix"]}}}}}`), + expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":null},"bidderparams":"bar"}}`), + }, + { + description: "Prebid + AlternateBidderCodes in both pbs config and in the request", + bidderParams: map[string]json.RawMessage{bidder: bidderParams}, + alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{Enabled: true, Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{"foo": {Enabled: true, AllowedBidderCodes: []string{"*"}}}}, + requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["foo2"]},"bar":{"enabled":true,"allowedbiddercodes":["ix"]}}}}}`), + expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["foo2"]}}},"bidderparams":"bar"}}`), + }, + } + + for _, test := range testCases { + requestExtParsed := &openrtb_ext.ExtRequest{} + if test.requestExt != nil { + err := json.Unmarshal(test.requestExt, requestExtParsed) + if !assert.NoError(t, err, test.description+":parse_ext") { + continue + } + } + + actualJson, actualErr := buildRequestExtForBidder(bidder, test.requestExt, requestExtParsed, test.bidderParams, test.alternateBidderCodes) + if len(test.expectedJson) > 0 { + assert.JSONEq(t, string(test.expectedJson), string(actualJson), test.description+":json") + } else { + assert.Equal(t, test.expectedJson, actualJson, test.description+":json") + } + assert.NoError(t, actualErr, test.description+":err") + } +} + +func TestBuildRequestExtForBidder_RequestExtParsedNil(t *testing.T) { + var ( + bidder = "foo" + requestExt = json.RawMessage(`{}`) + requestExtParsed *openrtb_ext.ExtRequest + bidderParams map[string]json.RawMessage + alternateBidderCodes *openrtb_ext.ExtAlternateBidderCodes + ) + + actualJson, actualErr := buildRequestExtForBidder(bidder, requestExt, requestExtParsed, bidderParams, alternateBidderCodes) + assert.Equal(t, json.RawMessage(``), actualJson) + assert.NoError(t, actualErr) +} + +func TestBuildRequestExtForBidder_RequestExtMalformed(t *testing.T) { + var ( + bidder = "foo" + requestExt = json.RawMessage(`malformed`) + requestExtParsed = &openrtb_ext.ExtRequest{} + bidderParams map[string]json.RawMessage + alternateBidderCodes *openrtb_ext.ExtAlternateBidderCodes + ) + + actualJson, actualErr := buildRequestExtForBidder(bidder, requestExt, requestExtParsed, bidderParams, alternateBidderCodes) + assert.Equal(t, json.RawMessage(nil), actualJson) + assert.EqualError(t, actualErr, "invalid character 'm' looking for beginning of value") +} + // newAdapterAliasBidRequest builds a BidRequest with aliases func newAdapterAliasBidRequest(t *testing.T) *openrtb2.BidRequest { dnt := int8(1) @@ -2325,7 +2566,7 @@ func newBidRequest(t *testing.T) *openrtb2.BidRequest { H: 600, }}, }, - Ext: json.RawMessage(`{"appnexus": {"placementId": 1}}`), + Ext: json.RawMessage(`{"prebid":{"bidder":{"appnexus": {"placementId": 1}}}}`), }}, } } @@ -2366,7 +2607,7 @@ func newBidRequestWithBidderParams(t *testing.T) *openrtb2.BidRequest { H: 600, }}, }, - Ext: json.RawMessage(`{"appnexus": {"placementId": 1}, "pubmatic":{"publisherId": "1234"}}`), + Ext: json.RawMessage(`{"prebid":{"bidder":{"appnexus": {"placementId": 1}, "pubmatic":{"publisherId": "1234"}}}}`), }}, } } @@ -2459,51 +2700,51 @@ func TestRemoveUnpermissionedEids(t *testing.T) { }, { description: "Allowed By Nil Permissions", - userExt: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`), + userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), eidPermissions: nil, - expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`), + expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), }, { description: "Allowed By Empty Permissions", - userExt: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`), + userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{}, - expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`), + expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), }, { description: "Allowed By Specific Bidder", - userExt: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`), + userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ {Source: "source1", Bidders: []string{"bidderA"}}, }, - expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`), + expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), }, { description: "Allowed By All Bidders", - userExt: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`), + userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ {Source: "source1", Bidders: []string{"*"}}, }, - expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`), + expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), }, { description: "Allowed By Lack Of Matching Source", - userExt: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`), + userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ {Source: "source2", Bidders: []string{"otherBidder"}}, }, - expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`), + expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), }, { description: "Allowed - Keep Other Data", - userExt: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}],"other":42}`), + userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}],"other":42}`), eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ {Source: "source1", Bidders: []string{"bidderA"}}, }, - expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}],"other":42}`), + expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}],"other":42}`), }, { description: "Denied", - userExt: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`), + userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ {Source: "source1", Bidders: []string{"otherBidder"}}, }, @@ -2511,7 +2752,7 @@ func TestRemoveUnpermissionedEids(t *testing.T) { }, { description: "Denied - Keep Other Data", - userExt: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}],"otherdata":42}`), + userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}],"otherdata":42}`), eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ {Source: "source1", Bidders: []string{"otherBidder"}}, }, @@ -2519,12 +2760,12 @@ func TestRemoveUnpermissionedEids(t *testing.T) { }, { description: "Mix Of Allowed By Specific Bidder, Allowed By Lack Of Matching Source, Denied, Keep Other Data", - userExt: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"},{"source":"source2","id":"anyID"},{"source":"source3","id":"anyID"}],"other":42}`), + userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID1"}]},{"source":"source2","uids":[{"id":"anyID2"}]},{"source":"source3","uids":[{"id":"anyID3"}]}],"other":42}`), eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ {Source: "source1", Bidders: []string{"bidderA"}}, {Source: "source3", Bidders: []string{"otherBidder"}}, }, - expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"},{"source":"source2","id":"anyID"}],"other":42}`), + expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID1"}]},{"source":"source2","uids":[{"id":"anyID2"}]}],"other":42}`), }, } @@ -2565,12 +2806,12 @@ func TestRemoveUnpermissionedEidsUnmarshalErrors(t *testing.T) { { description: "Malformed Eid Array Type", userExt: json.RawMessage(`{"eids":[42]}`), - expectedErr: "json: cannot unmarshal number into Go value of type openrtb_ext.ExtUserEid", + expectedErr: "json: cannot unmarshal number into Go value of type openrtb2.EID", }, { description: "Malformed Eid Item Type", userExt: json.RawMessage(`{"eids":[{"source":42,"id":"anyID"}]}`), - expectedErr: "json: cannot unmarshal number into Go struct field ExtUserEid.source of type string", + expectedErr: "json: cannot unmarshal number into Go struct field EID.source of type string", }, } @@ -2778,7 +3019,7 @@ func TestCleanOpenRTBRequestsSChainMultipleBidders(t *testing.T) { TID: "61018dc9-fa61-4c41-b7dc-f90b9ae80e87", }, Imp: []openrtb2.Imp{{ - Ext: json.RawMessage(`{"appnexus": {"placementId": 1}, "axonix": { "supplyId": "123"}}`), + Ext: json.RawMessage(`{"prebid":{"bidder":{"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"}}]}}`), } @@ -2791,9 +3032,22 @@ func TestCleanOpenRTBRequestsSChainMultipleBidders(t *testing.T) { BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: req}, UserSyncs: &emptyUsersync{}, } + + gdprPermissionsBuilder := fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + passGeo: true, + passID: true, + activitiesError: nil, + }, + }.Builder + tcf2ConfigBuilder := fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + }.Builder + bidderToSyncerKey := map[string]string{} metrics := metrics.MetricsEngineMock{} - bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, bidderToSyncerKey, &metrics, gdpr.SignalNo, gdpr.AlwaysAllow{}, config.Privacy{}, &config.TCF2{}) + bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, bidderToSyncerKey, &metrics, gdpr.SignalNo, config.Privacy{}, gdprPermissionsBuilder, tcf2ConfigBuilder, nil) assert.Nil(t, errs) assert.Len(t, bidderRequests, 2, "Bid request count is not 2") @@ -2810,58 +3064,60 @@ func TestCleanOpenRTBRequestsSChainMultipleBidders(t *testing.T) { } 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 + inputFpd firstpartydata.ResolvedFirstPartyData inputRequest openrtb2.BidRequest expectedRequest openrtb2.BidRequest }{ { description: "req.Site defined; bidderFPD.Site not defined; expect request.Site remains the same", - inputFpd: fpdBidderTest, - bidderName: bidderTest, + inputFpd: firstpartydata.ResolvedFirstPartyData{Site: nil, App: nil, User: nil}, 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, + inputFpd: firstpartydata.ResolvedFirstPartyData{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId"}}, 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, + inputFpd: firstpartydata.ResolvedFirstPartyData{App: &openrtb2.App{ID: "AppId"}}, 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, + inputFpd: firstpartydata.ResolvedFirstPartyData{App: &openrtb2.App{ID: "AppId"}}, 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"}}, }, + { + description: "req.User is defined; bidderFPD.User defined; req.User has BuyerUID. Expect to see user.BuyerUID in result request", + inputFpd: firstpartydata.ResolvedFirstPartyData{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId"}}, + inputRequest: openrtb2.BidRequest{User: &openrtb2.User{ID: "UserIdIn", BuyerUID: "12345"}}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{ID: "UserId", BuyerUID: "12345"}, Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}}, + }, + { + description: "req.User is defined; bidderFPD.User defined; req.User has BuyerUID with zero length. Expect to see empty user.BuyerUID in result request", + inputFpd: firstpartydata.ResolvedFirstPartyData{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId"}}, + inputRequest: openrtb2.BidRequest{User: &openrtb2.User{ID: "UserIdIn", BuyerUID: ""}}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{ID: "UserId"}, Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}}, + }, + { + description: "req.User is not defined; bidderFPD.User defined and has BuyerUID. Expect to see user.BuyerUID in result request", + inputFpd: firstpartydata.ResolvedFirstPartyData{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId", BuyerUID: "FPDBuyerUID"}}, + inputRequest: openrtb2.BidRequest{}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId", BuyerUID: "FPDBuyerUID"}}, + }, } for _, testCase := range testCases { - applyFPD(testCase.inputFpd[testCase.bidderName], &testCase.inputRequest) + applyFPD(&testCase.inputFpd, &testCase.inputRequest) assert.Equal(t, testCase.expectedRequest, testCase.inputRequest, fmt.Sprintf("incorrect request after applying fpd, testcase %s", testCase.description)) } } @@ -2927,3 +3183,228 @@ func Test_parseAliasesGVLIDs(t *testing.T) { }) } } + +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 := WrapJSONInData(test.input) + assert.JSONEq(t, test.expectedRes, string(actualRes), "Incorrect result data") + } +} + +func TestCleanOpenRTBRequestsFilterBidderRequestExt(t *testing.T) { + testCases := []struct { + desc string + inExt json.RawMessage + inCfgABC *openrtb_ext.ExtAlternateBidderCodes + wantExt []json.RawMessage + wantError bool + }{ + { + desc: "Nil request ext, default account alternatebiddercodes config (nil)", + inExt: nil, + inCfgABC: nil, + wantExt: []json.RawMessage{ + json.RawMessage(""), + json.RawMessage(""), + }, + wantError: false, + }, + { + desc: "Nil request ext, default account alternatebiddercodes config (explicity defined)", + inExt: nil, + inCfgABC: &openrtb_ext.ExtAlternateBidderCodes{Enabled: false}, + wantExt: []json.RawMessage{ + json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":false,"bidders":null}}}`), + json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":false,"bidders":null}}}`), + }, + wantError: false, + }, + { + desc: "request ext, default account alternatebiddercodes config (explicity defined)", + inExt: json.RawMessage(`{"prebid":{}}`), + inCfgABC: &openrtb_ext.ExtAlternateBidderCodes{Enabled: false}, + wantExt: []json.RawMessage{ + json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":false,"bidders":null}}}`), + json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":false,"bidders":null}}}`), + }, + wantError: false, + }, + { + desc: "Nil request ext, account alternatebiddercodes config disabled with biddercodes defined", + inExt: nil, + inCfgABC: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: false, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "pubmatic": {Enabled: true}, + }, + }, + wantExt: []json.RawMessage{ + json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":false,"bidders":null}}}`), + json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":false,"bidders":{"pubmatic":{"enabled":true,"allowedbiddercodes":null}}}}}`), + }, + wantError: false, + }, + { + desc: "Nil request ext, account alternatebiddercodes config disabled with biddercodes defined (not participant bidder)", + inExt: nil, + inCfgABC: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: false, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "ix": {Enabled: true}, + }, + }, + wantExt: []json.RawMessage{ + json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":false,"bidders":null}}}`), + json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":false,"bidders":null}}}`), + }, + wantError: false, + }, + { + desc: "Nil request ext, alternatebiddercodes config enabled but bidder not present", + inExt: nil, + inCfgABC: &openrtb_ext.ExtAlternateBidderCodes{Enabled: true}, + wantExt: []json.RawMessage{ + json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":null}}}`), + json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":null}}}`), + }, + wantError: false, + }, + { + desc: "request ext with default alternatebiddercodes values (nil)", + inExt: json.RawMessage(`{"prebid":{}}`), + inCfgABC: nil, + wantExt: []json.RawMessage{ + json.RawMessage(`{"prebid":{}}`), + json.RawMessage(`{"prebid":{}}`), + }, + wantError: false, + }, + { + desc: "request ext w/o alternatebiddercodes", + inExt: json.RawMessage(`{"prebid":{}}`), + inCfgABC: &openrtb_ext.ExtAlternateBidderCodes{}, + wantExt: []json.RawMessage{ + json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":false,"bidders":null}}}`), + json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":false,"bidders":null}}}`), + }, + wantError: false, + }, + { + desc: "request ext having alternatebiddercodes for only one bidder", + inExt: json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":{"pubmatic":{"enabled":true,"allowedbiddercodes":["groupm"]}}}}}`), + inCfgABC: &openrtb_ext.ExtAlternateBidderCodes{Enabled: false}, + wantExt: []json.RawMessage{ + json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":null}}}`), + json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":{"pubmatic":{"enabled":true,"allowedbiddercodes":["groupm"]}}}}}`), + }, + wantError: false, + }, + { + desc: "request ext having alternatebiddercodes for multiple bidder", + inExt: json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":{"pubmatic":{"enabled":true,"allowedbiddercodes":["groupm"]},"appnexus":{"enabled":true,"allowedbiddercodes":["ix"]}}}}}`), + inCfgABC: &openrtb_ext.ExtAlternateBidderCodes{Enabled: false}, + wantExt: []json.RawMessage{ + json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":{"appnexus":{"enabled":true,"allowedbiddercodes":["ix"]}}}}}`), + json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":{"pubmatic":{"enabled":true,"allowedbiddercodes":["groupm"]}}}}}`), + }, + wantError: false, + }, + { + desc: "request ext having alternatebiddercodes for multiple bidder (config alternatebiddercodes not defined)", + inExt: json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":{"pubmatic":{"enabled":true,"allowedbiddercodes":["groupm"]},"appnexus":{"enabled":true,"allowedbiddercodes":["ix"]}}}}}`), + inCfgABC: &openrtb_ext.ExtAlternateBidderCodes{Enabled: false}, + wantExt: []json.RawMessage{ + json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":{"appnexus":{"enabled":true,"allowedbiddercodes":["ix"]}}}}}`), + json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":{"pubmatic":{"enabled":true,"allowedbiddercodes":["groupm"]}}}}}`), + }, + wantError: false, + }, + { + desc: "Nil request ext, alternatebiddercodes config enabled with bidder code for only one bidder", + inExt: nil, + inCfgABC: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "pubmatic": { + Enabled: true, + AllowedBidderCodes: []string{"groupm"}, + }, + }, + }, + wantExt: []json.RawMessage{ + json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":null}}}`), + json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":{"pubmatic":{"enabled":true,"allowedbiddercodes":["groupm"]}}}}}`), + }, + wantError: false, + }, + } + + for _, test := range testCases { + req := newBidRequestWithBidderParams(t) + req.Ext = nil + var extRequest *openrtb_ext.ExtRequest + if test.inExt != nil { + req.Ext = test.inExt + unmarshaledExt, err := extractBidRequestExt(req) + assert.NoErrorf(t, err, test.desc+":Error unmarshaling inExt") + extRequest = unmarshaledExt + } + + auctionReq := AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: req}, + UserSyncs: &emptyUsersync{}, + Account: config.Account{AlternateBidderCodes: test.inCfgABC}, + } + gdprPermissionsBuilder := fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + }, + }.Builder + tcf2ConfigBuilder := fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + }.Builder + bidderToSyncerKey := map[string]string{} + metrics := metrics.MetricsEngineMock{} + + bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, bidderToSyncerKey, &metrics, gdpr.SignalNo, config.Privacy{}, gdprPermissionsBuilder, tcf2ConfigBuilder, nil) + assert.Equal(t, test.wantError, len(errs) != 0, test.desc) + sort.Slice(bidderRequests, func(i, j int) bool { + return bidderRequests[i].BidderCoreName < bidderRequests[j].BidderCoreName + }) + for i, wantBidderRequest := range test.wantExt { + assert.Equal(t, wantBidderRequest, bidderRequests[i].BidRequest.Ext, test.desc+" : "+string(bidderRequests[i].BidderCoreName)+"\n\t\tGotRequestExt : "+string(bidderRequests[i].BidRequest.Ext)) + } + } +} diff --git a/experiment/adscert/SignerLogger.go b/experiment/adscert/SignerLogger.go new file mode 100644 index 00000000000..1d99bcc63f5 --- /dev/null +++ b/experiment/adscert/SignerLogger.go @@ -0,0 +1,38 @@ +package adscert + +import ( + "fmt" + "github.com/golang/glog" +) + +type SignerLogger struct { +} + +func (sl *SignerLogger) Debugf(format string, args ...interface{}) { + //there is no Debug level in glog + glog.Infof(format, args...) +} + +func (sl *SignerLogger) Infof(format string, args ...interface{}) { + glog.Infof(format, args...) +} + +func (sl *SignerLogger) Info(format string) { + glog.Info(format) +} + +func (sl *SignerLogger) Warningf(format string, args ...interface{}) { + glog.Warningf(format, args...) +} + +func (sl *SignerLogger) Errorf(format string, args ...interface{}) { + glog.Errorf(format, args...) +} + +func (sl *SignerLogger) Fatalf(format string, args ...interface{}) { + glog.Fatalf(format, args...) +} + +func (sl *SignerLogger) Panicf(format string, args ...interface{}) { + panic(fmt.Sprintf(format, args...)) +} diff --git a/experiment/adscert/inprocesssigner.go b/experiment/adscert/inprocesssigner.go new file mode 100644 index 00000000000..604287f9ed6 --- /dev/null +++ b/experiment/adscert/inprocesssigner.go @@ -0,0 +1,42 @@ +package adscert + +import ( + "crypto/rand" + "github.com/IABTechLab/adscert/pkg/adscert/api" + "github.com/IABTechLab/adscert/pkg/adscert/discovery" + "github.com/IABTechLab/adscert/pkg/adscert/signatory" + "github.com/benbjohnson/clock" + "github.com/prebid/prebid-server/config" + "time" +) + +// inProcessSigner holds the signatory to add adsCert header to requests using in process go library +type inProcessSigner struct { + signatory signatory.AuthenticatedConnectionsSignatory +} + +// Sign adds adsCert header to requests using in process go library +func (ips *inProcessSigner) Sign(destinationURL string, body []byte) (string, error) { + req := &api.AuthenticatedConnectionSignatureRequest{ + RequestInfo: createRequestInfo(destinationURL, body), + } + signatureResponse, err := ips.signatory.SignAuthenticatedConnection(req) + if err != nil { + return "", err + } + return getSignatureMessage(signatureResponse) +} + +func newInProcessSigner(inProcessSignerConfig config.AdsCertInProcess) (*inProcessSigner, error) { + return &inProcessSigner{ + signatory: signatory.NewLocalAuthenticatedConnectionsSignatory( + inProcessSignerConfig.Origin, + rand.Reader, + clock.New(), + discovery.NewDefaultDnsResolver(), + discovery.NewDefaultDomainStore(), + time.Duration(inProcessSignerConfig.DNSCheckIntervalInSeconds)*time.Second, + time.Duration(inProcessSignerConfig.DNSRenewalIntervalInSeconds)*time.Second, + []string{inProcessSignerConfig.PrivateKey}), + }, nil +} diff --git a/experiment/adscert/inprocesssigner_test.go b/experiment/adscert/inprocesssigner_test.go new file mode 100644 index 00000000000..603eb698be9 --- /dev/null +++ b/experiment/adscert/inprocesssigner_test.go @@ -0,0 +1,50 @@ +package adscert + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestInProcessSigner(t *testing.T) { + type aTest struct { + desc string + generateError bool + operationStatusOk bool + } + testCases := []aTest{ + { + desc: "generate signer error", + generateError: true, + operationStatusOk: false, + }, + { + desc: "generate valid response without signature operation error", + generateError: false, + operationStatusOk: true, + }, + { + desc: "generate valid response with signature operation error", + generateError: false, + operationStatusOk: false, + }, + } + + for _, test := range testCases { + signatory := &MockLocalAuthenticatedConnectionsSignatory{ + returnError: test.generateError, + operationStatusOk: test.operationStatusOk, + } + signer := &inProcessSigner{signatory: signatory} + signatureMessage, err := signer.Sign("http://test.com", []byte{}) + if test.generateError { + assert.EqualError(t, err, "Test error", "incorrect error returned for test: %s", test.desc) + } else { + if test.operationStatusOk { + assert.NoError(t, err, "incorrect result for test: %s", test.desc) + assert.Equal(t, "Success", signatureMessage, "incorrect message returned for test : %s", test.desc) + } else { + assert.EqualError(t, err, "error signing request: SIGNATURE_OPERATION_STATUS_UNDEFINED", "incorrect error type returned for test: %s", test.desc) + } + } + } +} diff --git a/experiment/adscert/remotesigner.go b/experiment/adscert/remotesigner.go new file mode 100644 index 00000000000..3c9479560b2 --- /dev/null +++ b/experiment/adscert/remotesigner.go @@ -0,0 +1,44 @@ +package adscert + +import ( + "fmt" + "github.com/IABTechLab/adscert/pkg/adscert/api" + "github.com/IABTechLab/adscert/pkg/adscert/signatory" + "github.com/prebid/prebid-server/config" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "time" +) + +// remoteSigner holds the signatory to add adsCert header to requests using remote signing server +type remoteSigner struct { + signatory signatory.AuthenticatedConnectionsSignatory +} + +// Sign adds adsCert header to requests using remote signing server +func (rs *remoteSigner) Sign(destinationURL string, body []byte) (string, error) { + signatureResponse, err := rs.signatory.SignAuthenticatedConnection( + &api.AuthenticatedConnectionSignatureRequest{ + RequestInfo: createRequestInfo(destinationURL, []byte(body)), + }) + if err != nil { + return "", err + } + return getSignatureMessage(signatureResponse) +} + +func newRemoteSigner(remoteSignerConfig config.AdsCertRemote) (*remoteSigner, error) { + // Establish the gRPC connection that the client will use to connect to the + // signatory server. Secure connections are not implemented at this time. + opts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())} + conn, err := grpc.Dial(remoteSignerConfig.Url, opts...) + if err != nil { + return nil, fmt.Errorf("failed to dial remote signer: %v", err) + } + + clientOpts := &signatory.AuthenticatedConnectionsSignatoryClientOptions{ + Timeout: time.Duration(remoteSignerConfig.SigningTimeoutMs) * time.Millisecond} + signatoryClient := signatory.NewAuthenticatedConnectionsSignatoryClient(conn, clientOpts) + return &remoteSigner{signatory: signatoryClient}, nil + +} diff --git a/experiment/adscert/remotesigner_test.go b/experiment/adscert/remotesigner_test.go new file mode 100644 index 00000000000..7cf56f6d826 --- /dev/null +++ b/experiment/adscert/remotesigner_test.go @@ -0,0 +1,50 @@ +package adscert + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestRemoteSigner(t *testing.T) { + type aTest struct { + desc string + generateError bool + operationStatusOk bool + } + testCases := []aTest{ + { + desc: "generate signer error", + generateError: true, + operationStatusOk: false, + }, + { + desc: "generate valid response without signature operation error", + generateError: false, + operationStatusOk: true, + }, + { + desc: "generate valid response with signature operation error", + generateError: false, + operationStatusOk: false, + }, + } + + for _, test := range testCases { + signatory := &MockLocalAuthenticatedConnectionsSignatory{ + returnError: test.generateError, + operationStatusOk: test.operationStatusOk, + } + signer := &remoteSigner{signatory: signatory} + signatureMessage, err := signer.Sign("http://test.com", []byte{}) + if test.generateError { + assert.EqualError(t, err, "Test error", "incorrect error returned for test: %s", test.desc) + } else { + if test.operationStatusOk { + assert.NoError(t, err, "incorrect result for test: %s", test.desc) + assert.Equal(t, "Success", signatureMessage, "incorrect message returned for test: %s", test.desc) + } else { + assert.EqualError(t, err, "error signing request: SIGNATURE_OPERATION_STATUS_UNDEFINED", "incorrect error type returned for test: %s", test.desc) + } + } + } +} diff --git a/experiment/adscert/signer.go b/experiment/adscert/signer.go new file mode 100644 index 00000000000..08b3f655fa2 --- /dev/null +++ b/experiment/adscert/signer.go @@ -0,0 +1,51 @@ +package adscert + +import ( + "fmt" + "github.com/IABTechLab/adscert/pkg/adscert/api" + "github.com/IABTechLab/adscert/pkg/adscert/logger" + "github.com/IABTechLab/adscert/pkg/adscert/signatory" + "github.com/prebid/prebid-server/config" +) + +const SignHeader = "X-Ads-Cert-Auth" + +// Signer represents interface to access request Ads Cert signing functionality +type Signer interface { + Sign(destinationURL string, body []byte) (string, error) +} + +type NilSigner struct { +} + +func (ns *NilSigner) Sign(destinationURL string, body []byte) (string, error) { + return "", nil +} + +func NewAdCertsSigner(experimentAdCertsConfig config.ExperimentAdsCert) (Signer, error) { + logger.SetLoggerImpl(&SignerLogger{}) + if experimentAdCertsConfig.Mode == config.AdCertsSignerModeInprocess { + return newInProcessSigner(experimentAdCertsConfig.InProcess) + } + if experimentAdCertsConfig.Mode == config.AdCertsSignerModeRemote { + return newRemoteSigner(experimentAdCertsConfig.Remote) + } + return &NilSigner{}, nil +} + +func createRequestInfo(destinationURL string, body []byte) *api.RequestInfo { + // The RequestInfo proto contains details about the individual ad request + // being signed. A SetRequestInfo helper function derives a hash of the + // destination URL and body, setting these value on the RequestInfo message. + reqInfo := &api.RequestInfo{} + signatory.SetRequestInfo(reqInfo, destinationURL, body) + return reqInfo +} + +func getSignatureMessage(signatureResponse *api.AuthenticatedConnectionSignatureResponse) (string, error) { + if signatureResponse.GetSignatureOperationStatus() == api.SignatureOperationStatus_SIGNATURE_OPERATION_STATUS_OK { + signatureMessage := signatureResponse.RequestInfo.SignatureInfo[0].SignatureMessage + return signatureMessage, nil + } + return "", fmt.Errorf("error signing request: %s", signatureResponse.GetSignatureOperationStatus()) +} diff --git a/experiment/adscert/signer_test.go b/experiment/adscert/signer_test.go new file mode 100644 index 00000000000..d6d02175d95 --- /dev/null +++ b/experiment/adscert/signer_test.go @@ -0,0 +1,54 @@ +package adscert + +import ( + "errors" + "github.com/IABTechLab/adscert/pkg/adscert/api" + "github.com/prebid/prebid-server/config" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestNilSigner(t *testing.T) { + config := config.ExperimentAdsCert{Mode: "off", InProcess: config.AdsCertInProcess{Origin: ""}, Remote: config.AdsCertRemote{Url: ""}} + signer, err := NewAdCertsSigner(config) + assert.NoError(t, err, "error should not be returned if not inprocess nor remote signer defined, NilSigner should be returned instead") + message, err := signer.Sign("test.com", nil) + assert.NoError(t, err, "NilSigner should not return an error") + assert.Equal(t, "", message, "incorrect message returned NilSigner") +} + +func TestNilSignerForAdsCertDisabled(t *testing.T) { + config := config.ExperimentAdsCert{Mode: "off", InProcess: config.AdsCertInProcess{Origin: ""}, Remote: config.AdsCertRemote{Url: ""}} + signer, err := NewAdCertsSigner(config) + assert.NoError(t, err, "error should not be returned if AdsCerts feature is disabled") + message, err := signer.Sign("test.com", nil) + assert.NoError(t, err, "NilSigner should not return an error") + assert.Equal(t, "", message, "incorrect message returned NilSigner") +} + +type MockLocalAuthenticatedConnectionsSignatory struct { + returnError bool + operationStatusOk bool +} + +func (ips *MockLocalAuthenticatedConnectionsSignatory) SignAuthenticatedConnection(request *api.AuthenticatedConnectionSignatureRequest) (*api.AuthenticatedConnectionSignatureResponse, error) { + if ips.returnError { + return nil, errors.New("Test error") + } + response := &api.AuthenticatedConnectionSignatureResponse{ + RequestInfo: &api.RequestInfo{ + SignatureInfo: []*api.SignatureInfo{ + {SignatureMessage: "Success"}, + }, + }, + } + if ips.operationStatusOk { + response.SignatureOperationStatus = api.SignatureOperationStatus_SIGNATURE_OPERATION_STATUS_OK + } else { + response.SignatureOperationStatus = api.SignatureOperationStatus_SIGNATURE_OPERATION_STATUS_UNDEFINED + } + return response, nil +} +func (ips *MockLocalAuthenticatedConnectionsSignatory) VerifyAuthenticatedConnection(request *api.AuthenticatedConnectionVerificationRequest) (*api.AuthenticatedConnectionVerificationResponse, error) { + return nil, nil +} diff --git a/firstpartydata/first_party_data.go b/firstpartydata/first_party_data.go index 78ab50768e7..bdd7e76610c 100644 --- a/firstpartydata/first_party_data.go +++ b/firstpartydata/first_party_data.go @@ -4,7 +4,7 @@ import ( "encoding/json" "fmt" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" jsonpatch "gopkg.in/evanphx/json-patch.v4" "github.com/prebid/prebid-server/errortypes" @@ -36,6 +36,7 @@ const ( bundleKey = "bundle" storeUrlKey = "storeurl" verKey = "ver" + contentKey = "content" ) type ResolvedFirstPartyData struct { @@ -97,7 +98,7 @@ func ExtractGlobalFPD(req *openrtb_ext.RequestWrapper) (map[string][]byte, error return fpdReqData, nil } -//ExtractOpenRtbGlobalFPD extracts and deletes user.data and {app/site}.content.data from request +// 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) @@ -120,7 +121,7 @@ func ExtractOpenRtbGlobalFPD(bidRequest *openrtb2.BidRequest) map[string][]openr } -//ResolveFPD consolidates First Party Data from different sources and returns valid FPD that will be applied to bidders later or returns errors +// 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 @@ -186,13 +187,11 @@ func resolveUser(fpdConfig *openrtb_ext.ORTB2, bidRequestUser *openrtb2.User, gl 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 := openrtb2.User{} + if bidRequestUser != nil { + newUser = *bidRequestUser } - newUser := *bidRequestUser var err error //apply global fpd @@ -243,7 +242,13 @@ func unmarshalJSONToStringArray(input json.RawMessage) ([]string, error) { return result, err } -//resolveExtension inserts remaining {site/app/user} attributes back to {site/app/user}.ext.data +func unmarshalJSONToContent(input json.RawMessage) (*openrtb2.Content, error) { + var result openrtb2.Content + err := json.Unmarshal(input, &result) + return &result, 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 @@ -344,19 +349,18 @@ func resolveSite(fpdConfig *openrtb_ext.ORTB2, bidRequestSite *openrtb2.Site, gl newSite.Ext = extData } } - if openRtbGlobalFPD != nil && len(openRtbGlobalFPD[siteContentDataKey]) > 0 { - if newSite.Content != nil { + // apply global openRTB fpd if exists + if len(openRtbGlobalFPD) > 0 && len(openRtbGlobalFPD[siteContentDataKey]) > 0 { + if newSite.Content == nil { + newSite.Content = &openrtb2.Content{} + } else { contentCopy := *newSite.Content - contentCopy.Data = openRtbGlobalFPD[siteContentDataKey] newSite.Content = &contentCopy - } else { - newSite.Content = &openrtb2.Content{Data: openRtbGlobalFPD[siteContentDataKey]} } + newSite.Content.Data = openRtbGlobalFPD[siteContentDataKey] } - if fpdConfigSite != nil { - newSite, err = mergeSites(&newSite, fpdConfigSite, bidderName) - } + newSite, err = mergeSites(&newSite, fpdConfigSite, bidderName) return &newSite, err } @@ -365,6 +369,11 @@ func mergeSites(originalSite *openrtb2.Site, fpdConfigSite map[string]json.RawMe var err error newSite := *originalSite + if fpdConfigSite == nil { + return newSite, err + } + + //apply bidder specific fpd if present if page, present := fpdConfigSite[pageKey]; present { sitePage, err := unmarshalJSONToString(page) if err != nil { @@ -437,6 +446,13 @@ func mergeSites(originalSite *openrtb2.Site, fpdConfigSite map[string]json.RawMe } delete(fpdConfigSite, refKey) } + if siteContent, present := fpdConfigSite[contentKey]; present { + newSite.Content, err = mergeContents(originalSite.Content, siteContent) + if err != nil { + return newSite, err + } + delete(fpdConfigSite, contentKey) + } if len(fpdConfigSite) > 0 { newSite.Ext, err = resolveExtension(fpdConfigSite, originalSite.Ext) @@ -475,20 +491,19 @@ func resolveApp(fpdConfig *openrtb_ext.ORTB2, bidRequestApp *openrtb2.App, globa newApp.Ext = extData } } - if openRtbGlobalFPD != nil && len(openRtbGlobalFPD[appContentDataKey]) > 0 { - if newApp.Content != nil { + + // apply global openRTB fpd if exists + if len(openRtbGlobalFPD) > 0 && len(openRtbGlobalFPD[appContentDataKey]) > 0 { + if newApp.Content == nil { + newApp.Content = &openrtb2.Content{} + } else { contentCopy := *newApp.Content - contentCopy.Data = openRtbGlobalFPD[appContentDataKey] newApp.Content = &contentCopy - } else { - newApp.Content = &openrtb2.Content{Data: openRtbGlobalFPD[appContentDataKey]} } + newApp.Content.Data = openRtbGlobalFPD[appContentDataKey] } - if fpdConfigApp != nil { - //apply bidder specific fpd if present - newApp, err = mergeApps(&newApp, fpdConfigApp) - } + newApp, err = mergeApps(&newApp, fpdConfigApp) return &newApp, err } @@ -498,6 +513,10 @@ func mergeApps(originalApp *openrtb2.App, fpdConfigApp map[string]json.RawMessag var err error newApp := *originalApp + if fpdConfigApp == nil { + return newApp, err + } + //apply bidder specific fpd if present if name, present := fpdConfigApp[nameKey]; present { newApp.Name, err = unmarshalJSONToString(name) if err != nil { @@ -561,6 +580,13 @@ func mergeApps(originalApp *openrtb2.App, fpdConfigApp map[string]json.RawMessag } delete(fpdConfigApp, keywordsKey) } + if appContent, present := fpdConfigApp[contentKey]; present { + newApp.Content, err = mergeContents(originalApp.Content, appContent) + if err != nil { + return newApp, err + } + delete(fpdConfigApp, contentKey) + } if len(fpdConfigApp) > 0 { newApp.Ext, err = resolveExtension(fpdConfigApp, originalApp.Ext) @@ -577,7 +603,7 @@ func buildExtData(data []byte) []byte { return res } -//ExtractBidderConfigFPD extracts bidder specific configs from req.ext.prebid.bidderconfig +// 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) @@ -616,7 +642,7 @@ func ExtractBidderConfigFPD(reqExt *openrtb_ext.RequestExt) (map[openrtb_ext.Bid } -//ExtractFPDForBidders extracts FPD data from request if specified +// ExtractFPDForBidders extracts FPD data from request if specified func ExtractFPDForBidders(req *openrtb_ext.RequestWrapper) (map[openrtb_ext.BidderName]*ResolvedFirstPartyData, []error) { reqExt, err := req.GetRequestExt() @@ -656,3 +682,22 @@ func ExtractFPDForBidders(req *openrtb_ext.RequestWrapper) (map[openrtb_ext.Bidd return ResolveFPD(req.BidRequest, fbdBidderConfigData, globalFpd, openRtbGlobalFPD, biddersWithGlobalFPD) } + +func mergeContents(originalContent *openrtb2.Content, fpdBidderConfigContent json.RawMessage) (*openrtb2.Content, error) { + if originalContent == nil { + return unmarshalJSONToContent(fpdBidderConfigContent) + } + originalContentBytes, err := json.Marshal(originalContent) + if err != nil { + return nil, err + } + newFinalContentBytes, err := jsonpatch.MergePatch(originalContentBytes, fpdBidderConfigContent) + if err != nil { + return nil, err + } + newFinalContent, err := unmarshalJSONToContent(newFinalContentBytes) + if err != nil { + return nil, err + } + return newFinalContent, nil +} diff --git a/firstpartydata/first_party_data_test.go b/firstpartydata/first_party_data_test.go index 1e0dc01566c..6f7e5eab1ea 100644 --- a/firstpartydata/first_party_data_test.go +++ b/firstpartydata/first_party_data_test.go @@ -2,12 +2,13 @@ package firstpartydata import ( "encoding/json" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "os" + "testing" + + "github.com/prebid/openrtb/v17/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) { @@ -467,7 +468,7 @@ func TestExtractOpenRtbGlobalFPD(t *testing.T) { func TestExtractBidderConfigFPD(t *testing.T) { - if specFiles, err := ioutil.ReadDir("./tests/extractbidderconfigfpd"); err == nil { + if specFiles, err := os.ReadDir("./tests/extractbidderconfigfpd"); err == nil { for _, specFile := range specFiles { fileName := "./tests/extractbidderconfigfpd/" + specFile.Name() @@ -532,7 +533,7 @@ func TestExtractBidderConfigFPD(t *testing.T) { func TestResolveFPD(t *testing.T) { - if specFiles, err := ioutil.ReadDir("./tests/resolvefpd"); err == nil { + if specFiles, err := os.ReadDir("./tests/resolvefpd"); err == nil { for _, specFile := range specFiles { fileName := "./tests/resolvefpd/" + specFile.Name() @@ -644,9 +645,10 @@ func TestResolveFPD(t *testing.T) { func TestExtractFPDForBidders(t *testing.T) { - if specFiles, err := ioutil.ReadDir("./tests/extractfpdforbidders"); err == nil { + if specFiles, err := os.ReadDir("./tests/extractfpdforbidders"); err == nil { for _, specFile := range specFiles { fileName := "./tests/extractfpdforbidders/" + specFile.Name() + fpdFile, err := loadFpdFile(fileName) if err != nil { @@ -734,7 +736,7 @@ func TestExtractFPDForBidders(t *testing.T) { func loadFpdFile(filename string) (fpdFile, error) { var fileData fpdFile - fileContents, err := ioutil.ReadFile(filename) + fileContents, err := os.ReadFile(filename) if err != nil { return fileData, err } @@ -850,17 +852,6 @@ func TestResolveUserNilValues(t *testing.T) { 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{ diff --git a/firstpartydata/tests/extractfpdforbidders/req-empty-user-fpd-not-empty-user.json b/firstpartydata/tests/extractfpdforbidders/req-empty-user-fpd-not-empty-user.json new file mode 100644 index 00000000000..7ba65d1b075 --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/req-empty-user-fpd-not-empty-user.json @@ -0,0 +1,80 @@ +{ + "description": "req.user is not present but req.ext.prebid.bidderconfig contains user for bidder; expect req.user to present in the resolved bidders FPD", + "inputRequestData": { + "id": "bid_id", + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": ["appnexus"] + }, + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": { + "ortb2": { + "user": { + "id": "appnexusFpdUser", + "yob": 2011, + "gender": "F", + "keywords": "fpd keywords", + "data": [ + { + "id": "FpdUserDataId1", + "name": "FpdUserDataName1" + }, + { + "id": "FpdUserDataId2", + "name": "FpdUserDataName2" + } + ], + "ext": { + "data": { + "userdata": "appnexusFpdUserExtData" + } + } + } + } + } + } + ] + } + } + }, + "outputRequestData": { + "id": "bid_id", + "test": 1 + }, + "biddersFPDResolved": { + "appnexus": { + "user": { + "yob": 2011, + "gender": "F", + "keywords": "fpd keywords", + "data": [ + { + "id": "FpdUserDataId1", + "name": "FpdUserDataName1" + }, + { + "id": "FpdUserDataId2", + "name": "FpdUserDataName2" + } + ], + "ext": { + "data": { + "ext": { + "data": { + "userdata": "appnexusFpdUserExtData" + } + }, + "id": "appnexusFpdUser" + } + } + } + } + }, + "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-content-data.json similarity index 83% rename from firstpartydata/tests/extractfpdforbidders/two-bidders-global-and-non-global-app-user.json rename to firstpartydata/tests/extractfpdforbidders/two-bidders-global-and-non-global-app-content-data.json index 6fed14e9059..2ae0456a4cb 100644 --- a/firstpartydata/tests/extractfpdforbidders/two-bidders-global-and-non-global-app-user.json +++ b/firstpartydata/tests/extractfpdforbidders/two-bidders-global-and-non-global-app-content-data.json @@ -132,36 +132,21 @@ }, "content": { "len": 900, - "title": "reqContentTitle", - "season": "reqContentSeason", + "title": "apnFpdAppIdContentTitle", + "season": "apnFpdAppIdContentSeason", "data": [ { - "id": "reqContentDataId1", - "name": "reqContentDataName1" + "id": "apnFpdAppIdContentDataId1", + "name": "apnFpdAppIdContentDataName1" }, { - "id": "reqContentDataId2", - "name": "reqContentDataName2" + "id": "apnFpdAppIdContentDataId2", + "name": "apnFpdAppIdContentDataName2" } ] }, "ext": { "data": { - "content": { - "len": 900, - "title": "apnFpdAppIdContentTitle", - "season": "apnFpdAppIdContentSeason", - "data": [ - { - "id": "apnFpdAppIdContentDataId1", - "name": "apnFpdAppIdContentDataName1" - }, - { - "id": "apnFpdAppIdContentDataId2", - "name": "apnFpdAppIdContentDataName2" - } - ] - }, "id": "apnFpdAppId", "publisher": { "id": "1" 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-content-data.json similarity index 84% rename from firstpartydata/tests/extractfpdforbidders/two-bidders-global-and-non-global-site-user.json rename to firstpartydata/tests/extractfpdforbidders/two-bidders-global-and-non-global-site-content-data.json index 129087444a3..5dfcee9cd52 100644 --- a/firstpartydata/tests/extractfpdforbidders/two-bidders-global-and-non-global-site-user.json +++ b/firstpartydata/tests/extractfpdforbidders/two-bidders-global-and-non-global-site-content-data.json @@ -134,36 +134,21 @@ }, "content": { "len": 900, - "title": "reqContentTitle", - "season": "reqContentSeason", + "title": "apnFpdSiteIdContentTitle", + "season": "apnFpdSiteIdContentSeason", "data": [ { - "id": "reqContentDataId1", - "name": "reqContentDataName1" + "id": "apnFpdSiteIdContentDataId1", + "name": "apnFpdSiteIdContentDataName1" }, { - "id": "reqContentDataId2", - "name": "reqContentDataName2" + "id": "apnFpdSiteIdContentDataId2", + "name": "apnFpdSiteIdContentDataName2" } ] }, "ext": { "data": { - "content": { - "len": 900, - "title": "apnFpdSiteIdContentTitle", - "season": "apnFpdSiteIdContentSeason", - "data": [ - { - "id": "apnFpdSiteIdContentDataId1", - "name": "apnFpdSiteIdContentDataName1" - }, - { - "id": "apnFpdSiteIdContentDataId2", - "name": "apnFpdSiteIdContentDataName2" - } - ] - }, "id": "apnFpdSiteId", "publisher": { "id": "1" 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 index ed81b4158af..88a79002f26 100644 --- a/firstpartydata/tests/extractfpdforbidders/two-bidders-global-and-non-global-user.json +++ b/firstpartydata/tests/extractfpdforbidders/two-bidders-global-and-non-global-user.json @@ -158,16 +158,16 @@ }, "content": { "len": 900, - "title": "reqContentTitle", - "season": "reqContentSeason", + "season": "apnFpdSiteIdContentSeason", + "title": "apnFpdSiteIdContentTitle", "data": [ { - "id": "reqContentDataId1", - "name": "reqContentDataName1" + "id": "apnFpdSiteIdContentDataId1", + "name": "apnFpdSiteIdContentDataName1" }, { - "id": "reqContentDataId2", - "name": "reqContentDataName2" + "id": "apnFpdSiteIdContentDataId2", + "name": "apnFpdSiteIdContentDataName2" } ] }, @@ -176,21 +176,6 @@ "id": "apnFpdSiteId", "publisher": { "id": "1" - }, - "content": { - "len": 900, - "title": "apnFpdSiteIdContentTitle", - "season": "apnFpdSiteIdContentSeason", - "data": [ - { - "id": "apnFpdSiteIdContentDataId1", - "name": "apnFpdSiteIdContentDataName1" - }, - { - "id": "apnFpdSiteIdContentDataId2", - "name": "apnFpdSiteIdContentDataName2" - } - ] } } } diff --git a/firstpartydata/tests/extractfpdforbidders/two-bidders-global-without-config-and-global-app-content-data.json b/firstpartydata/tests/extractfpdforbidders/two-bidders-global-without-config-and-global-app-content-data.json new file mode 100644 index 00000000000..cbebe0fc89b --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/two-bidders-global-without-config-and-global-app-content-data.json @@ -0,0 +1,187 @@ +{ + "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", + "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", "telaria" + ] + }, + "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" + } + } + } + } + ] + } + } + }, + "outputRequestData": { + "id": "bid_id", + "app": { + "id": "reqAppId", + "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": "apnFpdAppIdContentTitle", + "season": "apnFpdAppIdContentSeason", + "data": [ + { + "id": "apnFpdAppIdContentDataId1", + "name": "apnFpdAppIdContentDataName1" + }, + { + "id": "apnFpdAppIdContentDataId2", + "name": "apnFpdAppIdContentDataName2" + } + ] + }, + "ext": { + "data": { + "id": "apnFpdAppId", + "publisher": { + "id": "1" + } + } + } + }, + "user": { + "id": "reqUserId", + "keywords": "apnFpdUserKeyword", + "ext": { + "data": { + "id":"apnUserId" + } + } + } + }, + "telaria": { + "app": { + "id": "reqAppId", + "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" + } + } + }, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/two-bidders-global-without-config-and-global-site-content-data.json b/firstpartydata/tests/extractfpdforbidders/two-bidders-global-without-config-and-global-site-content-data.json new file mode 100644 index 00000000000..717d174102a --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/two-bidders-global-without-config-and-global-site-content-data.json @@ -0,0 +1,192 @@ +{ + "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", + "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", "telaria" + ] + }, + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": { + "ortb2": { + "site": { + "id": "apnFpdAppId", + "name": "apnFpdAppIdAppName", + "page": "http://www.foobar.com/1234.html", + "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" + } + } + } + } + ] + } + } + }, + "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", + "name": "apnFpdAppIdAppName", + "page": "http://www.foobar.com/1234.html", + "cat": [ + "books", + "news" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "apnFpdAppIdContentTitle", + "season": "apnFpdAppIdContentSeason", + "data": [ + { + "id": "apnFpdAppIdContentDataId1", + "name": "apnFpdAppIdContentDataName1" + }, + { + "id": "apnFpdAppIdContentDataId2", + "name": "apnFpdAppIdContentDataName2" + } + ] + }, + "ext": { + "data": { + "id": "apnFpdAppId", + "publisher": { + "id": "1" + } + } + } + }, + "user": { + "id": "reqUserId", + "keywords": "apnFpdUserKeyword", + "ext": { + "data": { + "id":"apnUserId" + } + } + } + }, + "telaria": { + "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" + } + } + }, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/two-bidders-with-incorrect-fpd.json b/firstpartydata/tests/extractfpdforbidders/two-bidders-with-incorrect-fpd.json index a23b45397f4..dd06e8b4d3c 100644 --- a/firstpartydata/tests/extractfpdforbidders/two-bidders-with-incorrect-fpd.json +++ b/firstpartydata/tests/extractfpdforbidders/two-bidders-with-incorrect-fpd.json @@ -52,7 +52,6 @@ "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/global-and-bidder-fpd-site-content-data.json b/firstpartydata/tests/resolvefpd/bidder-fpd-site-content-data-only.json similarity index 69% rename from firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site-content-data.json rename to firstpartydata/tests/resolvefpd/bidder-fpd-site-content-data-only.json index 2843834966e..723d934c6e9 100644 --- a/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site-content-data.json +++ b/firstpartydata/tests/resolvefpd/bidder-fpd-site-content-data-only.json @@ -1,5 +1,5 @@ { - "description": "Global and bidder FPD defined for site. Global FPD has site.content.data", + "description": "Global and bidder FPD defined for site. Bidder FPD has site.content.data", "inputRequestData": { "site": { "id": "reqSiteId", @@ -18,13 +18,7 @@ "title": "episodeName", "series": "TvName", "season": "season3", - "len": 900, - "data": [ - { - "id": "reqSiteData", - "name": "reqSiteName" - } - ] + "len": 900 } }, "device": { @@ -67,24 +61,14 @@ "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", + "page": "http://www.foobar.com/1234.html", "cat": [ "books", "novels" @@ -94,39 +78,24 @@ "id": "1" }, "content": { - "episode": 6, - "title": "episodeName", + "episode": 7, + "title": "apnEpisodeName", "series": "TvName", "season": "season3", - "len": 900, + "len": 600, "data": [ { - "id": "siteData1", - "name": "siteName1" - }, - { - "id": "siteData2", - "name": "siteName2" + "id": "siteData3", + "name": "siteName3" } ] }, "ext": { "data": { "id": "apnSiteId", - "content": { - "episode": 7, - "title": "apnEpisodeName", - "len": 600, - "data": [ - { - "id": "siteData3", - "name": "siteName3" - } - ] - }, "morefpdData": "morefpddata", "siteFpddata": "siteFpddata", - "testSiteFpd": "testSite", + "testSiteFpd":"testSite", "moreFpd": { "fpd": 123 } diff --git a/firstpartydata/tests/resolvefpd/global-and-no-bidder-fpd-site-app-user.json b/firstpartydata/tests/resolvefpd/global-and-no-bidder-fpd-site-app-user.json new file mode 100644 index 00000000000..17acd451ee8 --- /dev/null +++ b/firstpartydata/tests/resolvefpd/global-and-no-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" + }, + "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", + "content": { + "episode": 8, + "title": "episodeName8", + "series": "TvName", + "season": "season4", + "len": 900 + }, + "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": { + "title": "episodeName8", + "series": "TvName", + "season": "season4", + "episode": 8, + "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-no-bidder-fpd-site-content-app-user.json b/firstpartydata/tests/resolvefpd/global-and-no-bidder-fpd-site-content-app-user.json new file mode 100644 index 00000000000..80b47a56de7 --- /dev/null +++ b/firstpartydata/tests/resolvefpd/global-and-no-bidder-fpd-site-content-app-user.json @@ -0,0 +1,118 @@ +{ + "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" + }, + "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": { + "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-fpd-only-app.json b/firstpartydata/tests/resolvefpd/global-fpd-only-app.json new file mode 100644 index 00000000000..d7bd3589aee --- /dev/null +++ b/firstpartydata/tests/resolvefpd/global-fpd-only-app.json @@ -0,0 +1,120 @@ +{ + "description": "Global and bidder FPD defined for site and user. Global FPD has app.content.data", + "inputRequestData": { + "app": { + "id": "reqAppId", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900 + }, + "ext": { + "testAppExt": 123 + } + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + }, + "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" + } + ] + }, + "outputRequestData": { + "app": { + "id": "reqAppId", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900, + "data": [ + { + "id": "appData1", + "name": "appName1" + }, + { + "id": "appData2", + "name": "appName2" + } + ] + }, + "ext": { + "testAppExt": 123, + "data": { + "id": "apnAppId", + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata", + "appFpd": 123, + "appFpddata": "appFpddata" + } + } + }, + "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-app-user.json b/firstpartydata/tests/resolvefpd/global-fpd-only-site.json similarity index 100% rename from firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site-app-user.json rename to firstpartydata/tests/resolvefpd/global-fpd-only-site.json diff --git a/firstpartydata/tests/resolvefpd/req-and-bidder-fpd-site-content.json b/firstpartydata/tests/resolvefpd/req-and-bidder-fpd-site-content.json new file mode 100644 index 00000000000..f3e5fdd22bc --- /dev/null +++ b/firstpartydata/tests/resolvefpd/req-and-bidder-fpd-site-content.json @@ -0,0 +1,147 @@ +{ + "description": "Global FPD and bidder FPD have 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, + "data": [ + { + "id": "reqSiteData1", + "name": "reqSiteName1" + }, + { + "id": "reqSiteData2", + "name": "reqSiteName2" + } + ] + }, + "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", + "content": { + "episode": 8, + "title": "episodeName8", + "series": "TvName", + "season": "season4", + "len": 900, + "data": [ + { + "id": "siteData1", + "name": "siteName1" + }, + { + "id": "siteData2", + "name": "siteName2" + } + ] + }, + "ext": { + "data": { + "morefpdData": "morefpddata", + "siteFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + } + }, + "globalFPD": { + "site": { + "siteFpd": 123 + }, + "app": { + "appFpd": { + "testValue": true + } + }, + "user": { + "testUserFpd": "testuser" + } + }, + "outputRequestData": { + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "publisher": { + "id": "1" + }, + "content": { + "episode": 8, + "title": "episodeName8", + "series": "TvName", + "season": "season4", + "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/gdpr/aggregated_config.go b/gdpr/aggregated_config.go index 9f0007e0323..bbfb503225d 100644 --- a/gdpr/aggregated_config.go +++ b/gdpr/aggregated_config.go @@ -9,14 +9,15 @@ import ( // TCF2ConfigReader is an interface to access TCF2 configurations type TCF2ConfigReader interface { - BasicEnforcementVendor(openrtb_ext.BidderName) bool + BasicEnforcementVendors() map[string]struct{} FeatureOneEnforced() bool FeatureOneVendorException(openrtb_ext.BidderName) bool - IntegrationEnabled(config.IntegrationType) bool + ChannelEnabled(config.ChannelType) bool IsEnabled() bool PurposeEnforced(consentconstants.Purpose) bool + PurposeEnforcementAlgo(consentconstants.Purpose) config.TCF2EnforcementAlgo PurposeEnforcingVendors(consentconstants.Purpose) bool - PurposeVendorException(consentconstants.Purpose, openrtb_ext.BidderName) bool + PurposeVendorExceptions(consentconstants.Purpose) map[openrtb_ext.BidderName]struct{} PurposeOneTreatmentEnabled() bool PurposeOneTreatmentAccessAllowed() bool } @@ -41,10 +42,10 @@ func (tc *tcf2Config) IsEnabled() bool { return tc.HostConfig.Enabled } -// IntegrationEnabled checks if a given integration type is enabled at the account level. If it is not set at the -// account level, the host TCF2 enabled flag is used to determine if the integration type is enabled. -func (tc *tcf2Config) IntegrationEnabled(integrationType config.IntegrationType) bool { - if accountEnabled := tc.AccountConfig.EnabledForIntegrationType(integrationType); accountEnabled != nil { +// ChannelEnabled checks if a given channel type is enabled at the account level. If it is not set at the +// account level, the host TCF2 enabled flag is used to determine if the channel type is enabled. +func (tc *tcf2Config) ChannelEnabled(channelType config.ChannelType) bool { + if accountEnabled := tc.AccountConfig.EnabledForChannelType(channelType); accountEnabled != nil { return *accountEnabled } return tc.HostConfig.Enabled @@ -62,6 +63,15 @@ func (tc *tcf2Config) PurposeEnforced(purpose consentconstants.Purpose) bool { return value } +// PurposeEnforcementAlgo checks the purpose enforcement algo for a given purpose by first +// looking at the account settings, and if not set there, defaulting to the host configuration. +func (tc *tcf2Config) PurposeEnforcementAlgo(purpose consentconstants.Purpose) config.TCF2EnforcementAlgo { + if value, exists := tc.AccountConfig.PurposeEnforcementAlgo(purpose); exists { + return value + } + return tc.HostConfig.PurposeEnforcementAlgo(purpose) +} + // PurposeEnforcingVendors checks if enforcing vendors is turned on for a given purpose by first looking at the // account settings, and if not set there, defaulting to the host configuration. With enforcing vendors enabled, // the GDPR full enforcement algorithm considers the GVL when determining legal basis; otherwise it's skipped. @@ -74,16 +84,14 @@ func (tc *tcf2Config) PurposeEnforcingVendors(purpose consentconstants.Purpose) return value } -// PurposeVendorException checks if the specified bidder is considered a vendor exception for a given purpose by first -// looking at the account settings, and if not set there, defaulting to the host configuration. If a bidder is a vendor -// exception, the GDPR full enforcement algorithm will bypass the legal basis calculation assuming the request is valid -// and there isn't a "deny all" publisher restriction -func (tc *tcf2Config) PurposeVendorException(purpose consentconstants.Purpose, bidder openrtb_ext.BidderName) bool { - if value, exists := tc.AccountConfig.PurposeVendorException(purpose, bidder); exists { +// PurposeVendorExceptions returns the vendor exception map for the specified purpose if it exists for the account; +// otherwise it returns a nil map. If a bidder is a vendor exception, the GDPR full enforcement algorithm will +// bypass the legal basis calculation assuming the request is valid and there isn't a "deny all" publisher restriction +func (tc *tcf2Config) PurposeVendorExceptions(purpose consentconstants.Purpose) map[openrtb_ext.BidderName]struct{} { + if value, exists := tc.AccountConfig.PurposeVendorExceptions(purpose); exists { return value } - value := tc.HostConfig.PurposeVendorException(purpose, bidder) - return value + return tc.HostConfig.PurposeVendorExceptions(purpose) } // FeatureOneEnforced checks if special feature one is enforced by first looking at the account settings, and if not @@ -128,14 +136,14 @@ func (tc *tcf2Config) PurposeOneTreatmentAccessAllowed() bool { return value } -// BasicEnforcementVendor checks if the given bidder is considered a basic enforcement vendor by looking at the account -// settings, and if not set there, defaulting to false. If set, the legal basis calculation for the bidder only considers -// consent to the purpose, not the vendor. The idea is that the publisher trusts this vendor to enforce the -// appropriate rules on their own. This only comes into play when enforceVendors is true as it lists those vendors that -// are exempt for vendor enforcement. -func (tc *tcf2Config) BasicEnforcementVendor(bidder openrtb_ext.BidderName) bool { - if value, exists := tc.AccountConfig.BasicEnforcementVendor(bidder); exists { - return value +// BasicEnforcementVendors returns the basic enforcement map if it exists for the account; otherwise it returns +// an empty map. If a bidder is considered a basic enforcement vendor, the legal basis calculation for the bidder +// only considers consent to the purpose, not the vendor. The idea is that the publisher trusts this vendor to +// enforce the appropriate rules on their own. This only comes into play when enforceVendors is true as it lists +// those vendors that are exempt for vendor enforcement. +func (tc *tcf2Config) BasicEnforcementVendors() map[string]struct{} { + if tc.AccountConfig.BasicEnforcementVendorsMap != nil { + return tc.AccountConfig.BasicEnforcementVendorsMap } - return false + return make(map[string]struct{}, 0) } diff --git a/gdpr/aggregated_config_test.go b/gdpr/aggregated_config_test.go index 59de5dae28f..bf2d3bbb8f8 100644 --- a/gdpr/aggregated_config_test.go +++ b/gdpr/aggregated_config_test.go @@ -82,54 +82,57 @@ func TestIntegrationEnabled(t *testing.T) { }, } - result := cfg.IntegrationEnabled(config.IntegrationTypeWeb) + result := cfg.ChannelEnabled(config.ChannelWeb) assert.Equal(t, tt.wantIntegrationEnabled, result, tt.description) } } func TestPurposeEnforced(t *testing.T) { + False := false + True := true + tests := []struct { description string - givePurpose1HostEnforcement string - givePurpose1AccountEnforcement string - givePurpose2HostEnforcement string - givePurpose2AccountEnforcement string + givePurpose1HostEnforcement bool + givePurpose1AccountEnforcement *bool + givePurpose2HostEnforcement bool + givePurpose2AccountEnforcement *bool givePurpose consentconstants.Purpose wantEnforced bool }{ { description: "Purpose 1 set at account level - use account setting false", - givePurpose1HostEnforcement: config.TCF2FullEnforcement, - givePurpose1AccountEnforcement: config.TCF2NoEnforcement, + givePurpose1HostEnforcement: true, + givePurpose1AccountEnforcement: &False, givePurpose: 1, wantEnforced: false, }, { description: "Purpose 1 set at account level - use account setting true", - givePurpose1HostEnforcement: config.TCF2NoEnforcement, - givePurpose1AccountEnforcement: config.TCF2FullEnforcement, + givePurpose1HostEnforcement: false, + givePurpose1AccountEnforcement: &True, givePurpose: 1, wantEnforced: true, }, { description: "Purpose 1 not set at account level - use host setting false", - givePurpose1HostEnforcement: config.TCF2NoEnforcement, - givePurpose1AccountEnforcement: "", + givePurpose1HostEnforcement: false, + givePurpose1AccountEnforcement: nil, givePurpose: 1, wantEnforced: false, }, { description: "Purpose 1 not set at account level - use host setting true", - givePurpose1HostEnforcement: config.TCF2FullEnforcement, - givePurpose1AccountEnforcement: "", + givePurpose1HostEnforcement: true, + givePurpose1AccountEnforcement: nil, givePurpose: 1, wantEnforced: true, }, { description: "Some other purpose set at account level - use account setting true", - givePurpose2HostEnforcement: config.TCF2NoEnforcement, - givePurpose2AccountEnforcement: config.TCF2FullEnforcement, + givePurpose2HostEnforcement: false, + givePurpose2AccountEnforcement: &True, givePurpose: 2, wantEnforced: true, }, @@ -162,6 +165,81 @@ func TestPurposeEnforced(t *testing.T) { } } +func TestPurposeEnforcementAlgo(t *testing.T) { + + tests := []struct { + description string + givePurpose1HostAlgo config.TCF2EnforcementAlgo + givePurpose1AccountAlgo config.TCF2EnforcementAlgo + givePurpose2HostAlgo config.TCF2EnforcementAlgo + givePurpose2AccountAlgo config.TCF2EnforcementAlgo + givePurpose consentconstants.Purpose + wantAlgo config.TCF2EnforcementAlgo + }{ + { + description: "Purpose 1 set at account level - use account setting basic", + givePurpose1HostAlgo: config.TCF2FullEnforcement, + givePurpose1AccountAlgo: config.TCF2BasicEnforcement, + givePurpose: 1, + wantAlgo: config.TCF2BasicEnforcement, + }, + { + description: "Purpose 1 set at account level - use account setting full", + givePurpose1HostAlgo: config.TCF2BasicEnforcement, + givePurpose1AccountAlgo: config.TCF2FullEnforcement, + givePurpose: 1, + wantAlgo: config.TCF2FullEnforcement, + }, + { + description: "Purpose 1 not set at account level - use host setting basic", + givePurpose1HostAlgo: config.TCF2BasicEnforcement, + givePurpose1AccountAlgo: config.TCF2UndefinedEnforcement, + givePurpose: 1, + wantAlgo: config.TCF2BasicEnforcement, + }, + { + description: "Purpose 1 not set at account level - use host setting full", + givePurpose1HostAlgo: config.TCF2FullEnforcement, + givePurpose1AccountAlgo: config.TCF2UndefinedEnforcement, + givePurpose: 1, + wantAlgo: config.TCF2FullEnforcement, + }, + { + description: "Some other purpose set at account level - use account setting basic", + givePurpose2HostAlgo: config.TCF2FullEnforcement, + givePurpose2AccountAlgo: config.TCF2BasicEnforcement, + givePurpose: 2, + wantAlgo: config.TCF2BasicEnforcement, + }, + } + + for _, tt := range tests { + cfg := tcf2Config{ + AccountConfig: config.AccountGDPR{ + Purpose1: config.AccountGDPRPurpose{ + EnforceAlgoID: tt.givePurpose1AccountAlgo, + }, + Purpose2: config.AccountGDPRPurpose{ + EnforceAlgoID: tt.givePurpose2AccountAlgo, + }, + }, + HostConfig: config.TCF2{ + Purpose1: config.TCF2Purpose{ + EnforceAlgoID: tt.givePurpose1HostAlgo, + }, + Purpose2: config.TCF2Purpose{ + EnforceAlgoID: tt.givePurpose2HostAlgo, + }, + }, + } + MakeTCF2ConfigPurposeMaps(&cfg) + + result := cfg.PurposeEnforcementAlgo(consentconstants.Purpose(tt.givePurpose)) + + assert.Equal(t, tt.wantAlgo, result, tt.description) + } +} + func TestPurposeEnforcingVendors(t *testing.T) { tests := []struct { description string @@ -236,64 +314,57 @@ func TestPurposeEnforcingVendors(t *testing.T) { } } -func TestPurposeVendorException(t *testing.T) { +func TestPurposeVendorExceptions(t *testing.T) { tests := []struct { - description string - givePurpose1HostVendorExceptionMap map[openrtb_ext.BidderName]struct{} - givePurpose1AccountVendorExceptionMap map[openrtb_ext.BidderName]struct{} - givePurpose2HostVendorExceptionMap map[openrtb_ext.BidderName]struct{} - givePurpose2AccountVendorExceptionMap map[openrtb_ext.BidderName]struct{} - givePurpose consentconstants.Purpose - giveBidder openrtb_ext.BidderName - wantVendorException bool + description string + givePurpose1HostExceptionMap map[openrtb_ext.BidderName]struct{} + givePurpose1AccountExceptionMap map[openrtb_ext.BidderName]struct{} + givePurpose2HostExceptionMap map[openrtb_ext.BidderName]struct{} + givePurpose2AccountExceptionMap map[openrtb_ext.BidderName]struct{} + givePurpose consentconstants.Purpose + wantExceptionMap map[openrtb_ext.BidderName]struct{} }{ { - description: "Purpose 1 exception list set at account level - vendor found", - givePurpose1HostVendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, - givePurpose1AccountVendorExceptionMap: map[openrtb_ext.BidderName]struct{}{"appnexus": {}, "rubicon": {}}, - givePurpose: 1, - giveBidder: "appnexus", - wantVendorException: true, + description: "Purpose 1 exception list set at account level - use empty account list", + givePurpose1HostExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + givePurpose1AccountExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + givePurpose: 1, + wantExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, { - description: "Purpose 1 exception list set at account level - vendor not found", - givePurpose1HostVendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, - givePurpose1AccountVendorExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}}, - givePurpose: 1, - giveBidder: "appnexus", - wantVendorException: false, + description: "Purpose 1 exception list set at account level - use nonempty account list", + givePurpose1HostExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + givePurpose1AccountExceptionMap: map[openrtb_ext.BidderName]struct{}{"appnexus": {}, "rubicon": {}}, + givePurpose: 1, + wantExceptionMap: map[openrtb_ext.BidderName]struct{}{"appnexus": {}, "rubicon": {}}, }, { - description: "Purpose 1 exception list not set at account level - vendor found in host list", - givePurpose1HostVendorExceptionMap: map[openrtb_ext.BidderName]struct{}{"appnexus": {}, "rubicon": {}}, - givePurpose1AccountVendorExceptionMap: nil, - givePurpose: 1, - giveBidder: "appnexus", - wantVendorException: true, + description: "Purpose 1 exception list not set at account level - use empty host list", + givePurpose1HostExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + givePurpose1AccountExceptionMap: nil, + givePurpose: 1, + wantExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, { - description: "Purpose 1 exception list not set at account level - vendor not found in host list", - givePurpose1HostVendorExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}}, - givePurpose1AccountVendorExceptionMap: nil, - givePurpose: 1, - giveBidder: "appnexus", - wantVendorException: false, + description: "Purpose 1 exception list not set at account level - use nonempty host list", + givePurpose1HostExceptionMap: map[openrtb_ext.BidderName]struct{}{"appnexus": {}, "rubicon": {}}, + givePurpose1AccountExceptionMap: nil, + givePurpose: 1, + wantExceptionMap: map[openrtb_ext.BidderName]struct{}{"appnexus": {}, "rubicon": {}}, }, { - description: "Purpose 1 exception list not set at account level or host level - vendor not found", - givePurpose1HostVendorExceptionMap: nil, - givePurpose1AccountVendorExceptionMap: nil, - givePurpose: 1, - giveBidder: "appnexus", - wantVendorException: false, + description: "Purpose 1 exception list not set at account level or host level", + givePurpose1HostExceptionMap: nil, + givePurpose1AccountExceptionMap: nil, + givePurpose: 1, + wantExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, { - description: "Some other purpose exception list set at account level - vendor found", - givePurpose2HostVendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, - givePurpose2AccountVendorExceptionMap: map[openrtb_ext.BidderName]struct{}{"appnexus": {}, "rubicon": {}}, - givePurpose: 2, - giveBidder: "appnexus", - wantVendorException: true, + description: "Some other purpose exception list set at account level", + givePurpose2HostExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + givePurpose2AccountExceptionMap: map[openrtb_ext.BidderName]struct{}{"appnexus": {}, "rubicon": {}}, + givePurpose: 2, + wantExceptionMap: map[openrtb_ext.BidderName]struct{}{"appnexus": {}, "rubicon": {}}, }, } @@ -301,26 +372,26 @@ func TestPurposeVendorException(t *testing.T) { cfg := tcf2Config{ AccountConfig: config.AccountGDPR{ Purpose1: config.AccountGDPRPurpose{ - VendorExceptionMap: tt.givePurpose1AccountVendorExceptionMap, + VendorExceptionMap: tt.givePurpose1AccountExceptionMap, }, Purpose2: config.AccountGDPRPurpose{ - VendorExceptionMap: tt.givePurpose2AccountVendorExceptionMap, + VendorExceptionMap: tt.givePurpose2AccountExceptionMap, }, }, HostConfig: config.TCF2{ Purpose1: config.TCF2Purpose{ - VendorExceptionMap: tt.givePurpose1HostVendorExceptionMap, + VendorExceptionMap: tt.givePurpose1HostExceptionMap, }, Purpose2: config.TCF2Purpose{ - VendorExceptionMap: tt.givePurpose2HostVendorExceptionMap, + VendorExceptionMap: tt.givePurpose2HostExceptionMap, }, }, } MakeTCF2ConfigPurposeMaps(&cfg) - result := cfg.PurposeVendorException(consentconstants.Purpose(tt.givePurpose), tt.giveBidder) + result := cfg.PurposeVendorExceptions(consentconstants.Purpose(tt.givePurpose)) - assert.Equal(t, tt.wantVendorException, result, tt.description) + assert.Equal(t, tt.wantExceptionMap, result, tt.description) } } @@ -549,42 +620,39 @@ func TestPurposeOneTreatmentAllowed(t *testing.T) { } } -func TestBasicEnforcementVendor(t *testing.T) { +func TestBasicEnforcementVendors(t *testing.T) { tests := []struct { - description string - giveAccountBasicEnforcementVendorMap map[string]struct{} - giveBidder openrtb_ext.BidderName - wantBasicEnforcement bool + description string + giveAccountBasicVendorMap map[string]struct{} + wantBasicVendorMap map[string]struct{} }{ { - description: "Basic enforcement vendor list set at account level - vendor found", - giveAccountBasicEnforcementVendorMap: map[string]struct{}{"appnexus": {}, "rubicon": {}}, - giveBidder: "appnexus", - wantBasicEnforcement: true, + description: "Purpose 1 basic exception vendor list not set at account level", + giveAccountBasicVendorMap: nil, + wantBasicVendorMap: map[string]struct{}{}, }, { - description: "Basic enforcement vendor list set at account level - vendor not found", - giveAccountBasicEnforcementVendorMap: map[string]struct{}{"rubicon": {}}, - giveBidder: "appnexus", - wantBasicEnforcement: false, + description: "Purpose 1 basic exception vendor list set at account level as empty list", + giveAccountBasicVendorMap: map[string]struct{}{}, + wantBasicVendorMap: map[string]struct{}{}, }, { - description: "Basic enforcement vendor list not set at account level - vendor not found", - giveAccountBasicEnforcementVendorMap: nil, - giveBidder: "appnexus", - wantBasicEnforcement: false, + description: "Purpose 1 basic exception vendor list not set at account level as nonempty list", + giveAccountBasicVendorMap: map[string]struct{}{"appnexus": {}, "rubicon": {}}, + wantBasicVendorMap: map[string]struct{}{"appnexus": {}, "rubicon": {}}, }, } for _, tt := range tests { cfg := tcf2Config{ AccountConfig: config.AccountGDPR{ - BasicEnforcementVendorsMap: tt.giveAccountBasicEnforcementVendorMap, + BasicEnforcementVendorsMap: tt.giveAccountBasicVendorMap, }, } + MakeTCF2ConfigPurposeMaps(&cfg) - result := cfg.BasicEnforcementVendor(tt.giveBidder) + result := cfg.BasicEnforcementVendors() - assert.Equal(t, tt.wantBasicEnforcement, result, tt.description) + assert.Equal(t, tt.wantBasicVendorMap, result, tt.description) } } diff --git a/gdpr/basic_enforcement.go b/gdpr/basic_enforcement.go new file mode 100644 index 00000000000..f4559c4643d --- /dev/null +++ b/gdpr/basic_enforcement.go @@ -0,0 +1,58 @@ +package gdpr + +import ( + tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2" + "github.com/prebid/prebid-server/openrtb_ext" +) + +// BasicEnforcement determines if legal basis is satisfied for a given purpose and bidder using +// the TCF2 basic enforcement algorithm. The algorithm is a high-level mode of consent confirmation +// that looks for a good-faith indication that the user has provided consent or legal basis signals +// necessary to perform a privacy-protected activity. The algorithm does not involve the GVL. +// BasicEnforcement implements the PurposeEnforcer interface +type BasicEnforcement struct { + cfg purposeConfig +} + +// LegalBasis determines if legal basis is satisfied for a given purpose and bidder based on user consent +// and legal basis signals. +func (be *BasicEnforcement) LegalBasis(vendorInfo VendorInfo, bidder openrtb_ext.BidderName, consent tcf2.ConsentMetadata, overrides Overrides) bool { + enforcePurpose, enforceVendors := be.applyEnforceOverrides(overrides) + + if !enforcePurpose && !enforceVendors { + return true + } + if be.cfg.vendorException(bidder) && !overrides.blockVendorExceptions { + return true + } + if !enforcePurpose && be.cfg.basicEnforcementVendor(bidder) { + return true + } + if enforcePurpose && consent.PurposeAllowed(be.cfg.PurposeID) && be.cfg.basicEnforcementVendor(bidder) { + return true + } + if enforcePurpose && consent.PurposeLITransparency(be.cfg.PurposeID) && overrides.allowLITransparency { + return true + } + if enforcePurpose && !consent.PurposeAllowed(be.cfg.PurposeID) { + return false + } + if !enforceVendors { + return true + } + return consent.VendorConsent(vendorInfo.vendorID) +} + +// applyEnforceOverrides returns the enforce purpose and enforce vendor configuration values unless +// those values have been overridden, in which case they return true +func (be *BasicEnforcement) applyEnforceOverrides(overrides Overrides) (enforcePurpose, enforceVendors bool) { + enforcePurpose = be.cfg.EnforcePurpose + if overrides.enforcePurpose { + enforcePurpose = true + } + enforceVendors = be.cfg.EnforceVendors + if overrides.enforceVendors { + enforceVendors = true + } + return +} diff --git a/gdpr/basic_enforcement_test.go b/gdpr/basic_enforcement_test.go new file mode 100644 index 00000000000..c49e59ea595 --- /dev/null +++ b/gdpr/basic_enforcement_test.go @@ -0,0 +1,240 @@ +package gdpr + +import ( + "testing" + + "github.com/prebid/go-gdpr/consentconstants" + "github.com/prebid/go-gdpr/vendorconsent" + tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2" + "github.com/prebid/prebid-server/openrtb_ext" + + "github.com/stretchr/testify/assert" +) + +func TestBasicLegalBasis(t *testing.T) { + appnexusID := uint16(32) + + noConsents := "CPerMsAPerMsAAAAAAENCfCAAAAAAAAAAAAAAAAAAAAA" + purpose2Consent := "CPerMsAPerMsAAAAAAENCfCAAEAAAAAAAAAAAAAAAAAA" + purpose2LI := "CPerMsAPerMsAAAAAAENCfCAAAAAAEAAAAAAAAAAAAAA" + vendor32Consent := "CPerMsAPerMsAAAAAAENCfCAAAAAAAAAAAAAAQAAAAAEAAAAAAAA" + purpose2AndVendor32Consent := "CPerMsAPerMsAAAAAAENCfCAAEAAAAAAAAAAAQAAAAAEAAAAAAAA" + + tests := []struct { + description string + config purposeConfig + consent string + overrides Overrides + wantResult bool + }{ + { + description: "enforce purpose & vendors are off", + consent: noConsents, + config: purposeConfig{ + PurposeID: consentconstants.Purpose(2), + EnforcePurpose: false, + EnforceVendors: false, + }, + wantResult: true, + }, + { + description: "enforce purpose on, purpose consent N", + consent: noConsents, + config: purposeConfig{ + PurposeID: consentconstants.Purpose(2), + EnforcePurpose: true, + EnforceVendors: false, + }, + wantResult: false, + }, + { + description: "enforce purpose on, purpose consent Y", + consent: purpose2Consent, + config: purposeConfig{ + PurposeID: consentconstants.Purpose(2), + EnforcePurpose: true, + EnforceVendors: false, + }, + wantResult: true, + }, + { + description: "enforce purpose on, purpose consent Y, enforce vendors off but overrides treats it as on", + consent: purpose2Consent, + config: purposeConfig{ + PurposeID: consentconstants.Purpose(2), + EnforcePurpose: true, + EnforceVendors: false, + }, + overrides: Overrides{enforceVendors: true}, + wantResult: false, + }, + { + description: "enforce purpose on, purpose consent Y, vendor consent Y, enforce vendors off but overrides treats it as on", + consent: purpose2AndVendor32Consent, + config: purposeConfig{ + PurposeID: consentconstants.Purpose(2), + EnforcePurpose: true, + EnforceVendors: false, + }, + overrides: Overrides{enforceVendors: true}, + wantResult: true, + }, + { + description: "enforce purpose on, purpose LI Transparency Y", + consent: purpose2LI, + config: purposeConfig{ + PurposeID: consentconstants.Purpose(2), + EnforcePurpose: true, + EnforceVendors: false, + }, + wantResult: false, + }, + { + description: "enforce purpose on, purpose LI Transparency Y but overrides allow it", + consent: purpose2LI, + config: purposeConfig{ + PurposeID: consentconstants.Purpose(2), + EnforcePurpose: true, + EnforceVendors: false, + }, + overrides: Overrides{allowLITransparency: true}, + wantResult: true, + }, + { + description: "enforce vendors on, vendor consent N", + consent: noConsents, + config: purposeConfig{ + PurposeID: consentconstants.Purpose(2), + EnforcePurpose: false, + EnforceVendors: true, + }, + wantResult: false, + }, + { + description: "enforce vendors on, vendor consent Y", + consent: vendor32Consent, + config: purposeConfig{ + PurposeID: consentconstants.Purpose(2), + EnforcePurpose: false, + EnforceVendors: true, + }, + wantResult: true, + }, + { + description: "enforce vendors on, vendor consent Y, enforce purpose off but overrides treats it as on", + consent: vendor32Consent, + config: purposeConfig{ + PurposeID: consentconstants.Purpose(2), + EnforcePurpose: false, + EnforceVendors: true, + }, + overrides: Overrides{enforcePurpose: true}, + wantResult: false, + }, + { + description: "enforce vendors on, purpose consent Y, vendor consent Y, enforce purpose off but overrides treats it as on", + consent: purpose2AndVendor32Consent, + config: purposeConfig{ + PurposeID: consentconstants.Purpose(2), + EnforcePurpose: false, + EnforceVendors: true, + }, + overrides: Overrides{enforcePurpose: true}, + wantResult: true, + }, + { + description: "enforce vendors on, vendor consent N, bidder is a basic vendor", + consent: noConsents, + config: purposeConfig{ + PurposeID: consentconstants.Purpose(2), + EnforcePurpose: false, + EnforceVendors: true, + BasicEnforcementVendorsMap: map[string]struct{}{string(openrtb_ext.BidderAppnexus): {}}, + }, + wantResult: true, + }, + { + description: "enforce purpose & vendors are on", + consent: noConsents, + config: purposeConfig{ + PurposeID: consentconstants.Purpose(2), + EnforcePurpose: true, + EnforceVendors: true, + }, + wantResult: false, + }, + { + description: "enforce purpose & vendors are on, bidder is a vendor exception", + consent: noConsents, + config: purposeConfig{ + PurposeID: consentconstants.Purpose(2), + EnforcePurpose: true, + EnforceVendors: true, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}}, + }, + wantResult: true, + }, + { + description: "enforce purpose & vendors are on, bidder is a vendor exception but overrides disallow them", + consent: noConsents, + config: purposeConfig{ + PurposeID: consentconstants.Purpose(2), + EnforcePurpose: true, + EnforceVendors: true, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}}, + }, + overrides: Overrides{blockVendorExceptions: true}, + wantResult: false, + }, + { + description: "enforce purpose & vendors are on, purpose consent Y, vendor consent N", + consent: purpose2Consent, + config: purposeConfig{ + PurposeID: consentconstants.Purpose(2), + EnforcePurpose: true, + EnforceVendors: true, + }, + wantResult: false, + }, + { + description: "enforce purpose & vendors are on, purpose consent Y, vendor consent N, bidder is a basic vendor", + consent: purpose2Consent, + config: purposeConfig{ + PurposeID: consentconstants.Purpose(2), + EnforcePurpose: true, + EnforceVendors: true, + BasicEnforcementVendorsMap: map[string]struct{}{string(openrtb_ext.BidderAppnexus): {}}, + }, + wantResult: true, + }, + { + description: "enforce purpose & vendors are on, purpose consent Y, vendor consent Y", + consent: purpose2AndVendor32Consent, + config: purposeConfig{ + PurposeID: consentconstants.Purpose(2), + EnforcePurpose: true, + EnforceVendors: true, + }, + wantResult: true, + }, + } + + for _, tt := range tests { + // convert consent string to TCF2 object + parsedConsent, err := vendorconsent.ParseString(tt.consent) + if err != nil { + t.Fatalf("Failed to parse consent %s: %s\n", tt.consent, tt.description) + } + consentMeta, ok := parsedConsent.(tcf2.ConsentMetadata) + if !ok { + t.Fatalf("Failed to convert consent %s: %s\n", tt.consent, tt.description) + } + + enforcer := BasicEnforcement{cfg: tt.config} + + vendorInfo := VendorInfo{vendorID: appnexusID, vendor: nil} + result := enforcer.LegalBasis(vendorInfo, openrtb_ext.BidderAppnexus, consentMeta, tt.overrides) + + assert.Equal(t, tt.wantResult, result, tt.description) + } +} diff --git a/gdpr/full_enforcement.go b/gdpr/full_enforcement.go new file mode 100644 index 00000000000..b86f7a3ff88 --- /dev/null +++ b/gdpr/full_enforcement.go @@ -0,0 +1,97 @@ +package gdpr + +import ( + tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2" + "github.com/prebid/prebid-server/openrtb_ext" +) + +const ( + pubRestrictNotAllowed = 0 + pubRestrictRequireConsent = 1 + pubRestrictRequireLegitInterest = 2 +) + +// FullEnforcement determines if legal basis is satisfied for a given purpose and bidder using +// the TCF2 full enforcement algorithm. The algorithm is a detailed confirmation that reads the +// GVL, interprets the consent string and performs legal basis analysis necessary to perform a +// privacy-protected activity. +// FullEnforcement implements the PurposeEnforcer interface +type FullEnforcement struct { + cfg purposeConfig +} + +// LegalBasis determines if legal basis is satisfied for a given purpose and bidder based on the +// vendor claims in the GVL, publisher restrictions and user consent. +func (fe *FullEnforcement) LegalBasis(vendorInfo VendorInfo, bidder openrtb_ext.BidderName, consent tcf2.ConsentMetadata, overrides Overrides) bool { + enforcePurpose, enforceVendors := fe.applyEnforceOverrides(overrides) + + if consent.CheckPubRestriction(uint8(fe.cfg.PurposeID), pubRestrictNotAllowed, vendorInfo.vendorID) { + return false + } + if !enforcePurpose && !enforceVendors { + return true + } + if fe.cfg.vendorException(bidder) && !overrides.blockVendorExceptions { + return true + } + + purposeAllowed := fe.consentEstablished(consent, vendorInfo, enforcePurpose, enforceVendors) + legitInterest := fe.legitInterestEstablished(consent, vendorInfo, enforcePurpose, enforceVendors) + + if consent.CheckPubRestriction(uint8(fe.cfg.PurposeID), pubRestrictRequireConsent, vendorInfo.vendorID) { + return purposeAllowed + } + if consent.CheckPubRestriction(uint8(fe.cfg.PurposeID), pubRestrictRequireLegitInterest, vendorInfo.vendorID) { + return legitInterest + } + + return purposeAllowed || legitInterest +} + +// applyEnforceOverrides returns the enforce purpose and enforce vendor configuration values unless +// those values have been overridden, in which case they return true +func (fe *FullEnforcement) applyEnforceOverrides(overrides Overrides) (enforcePurpose, enforceVendors bool) { + enforcePurpose = fe.cfg.EnforcePurpose + if overrides.enforcePurpose { + enforcePurpose = true + } + enforceVendors = fe.cfg.EnforceVendors + if overrides.enforceVendors { + enforceVendors = true + } + return +} + +// consentEstablished determines if consent has been established for a given purpose and bidder +// based on the purpose config, user consent and the GVL. For consent to be established, the vendor +// must declare the purpose as either consent or flex and the user must consent in accordance with +// the purpose configs. +func (fe *FullEnforcement) consentEstablished(consent tcf2.ConsentMetadata, vi VendorInfo, enforcePurpose bool, enforceVendors bool) bool { + if !vi.vendor.Purpose(fe.cfg.PurposeID) { + return false + } + if enforcePurpose && !consent.PurposeAllowed(fe.cfg.PurposeID) { + return false + } + if enforceVendors && !consent.VendorConsent(vi.vendorID) { + return false + } + return true +} + +// legitInterestEstablished determines if legitimate interest has been established for a given +// purpose and bidder based on the purpose config, user consent and the GVL. For consent to be +// established, the vendor must declare the purpose as either legit interest or flex and the user +// must have been provided notice for the legit interest basis in accordance with the purpose configs. +func (fe *FullEnforcement) legitInterestEstablished(consent tcf2.ConsentMetadata, vi VendorInfo, enforcePurpose bool, enforceVendors bool) bool { + if !vi.vendor.LegitimateInterest(fe.cfg.PurposeID) { + return false + } + if enforcePurpose && !consent.PurposeLITransparency(fe.cfg.PurposeID) { + return false + } + if enforceVendors && !consent.VendorLegitInterest(vi.vendorID) { + return false + } + return true +} diff --git a/gdpr/full_enforcement_test.go b/gdpr/full_enforcement_test.go new file mode 100644 index 00000000000..e8ccd1e4c0c --- /dev/null +++ b/gdpr/full_enforcement_test.go @@ -0,0 +1,928 @@ +package gdpr + +import ( + "encoding/json" + "testing" + + "github.com/prebid/go-gdpr/consentconstants" + "github.com/prebid/go-gdpr/vendorconsent" + tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2" + "github.com/prebid/go-gdpr/vendorlist" + "github.com/prebid/go-gdpr/vendorlist2" + "github.com/prebid/prebid-server/openrtb_ext" + + "github.com/stretchr/testify/assert" +) + +func TestLegalBasisWithPubRestrictionAllowNone(t *testing.T) { + appnexusID := uint16(32) + + NoConsentsWithP1P2P3V32RestrictionAllowNone := "CPfMKEAPfMKEAAAAAAENCgCAAAAAAAAAAAAAAQAAAAAAAIAAAAAAAGCAAgAgCAAQAQBgAIAIAAAA" + P1P2P3PurposeConsentAndV32VendorConsentWithP1P2P3V32RestrictionAllowNone := "CPfMKEAPfMKEAAAAAAENCgCAAOAAAAAAAAAAAQAAAAAEAIAAAAAAAGCAAgAgCAAQAQBgAIAIAAAA" + + tests := []struct { + description string + config purposeConfig + consent string + wantConsentPurposeResult bool + wantLIPurposeResult bool + wantFlexPurposeResult bool + }{ + { + description: "enforce purpose & vendors off", + config: purposeConfig{ + EnforcePurpose: false, + EnforceVendors: false, + }, + consent: NoConsentsWithP1P2P3V32RestrictionAllowNone, + wantConsentPurposeResult: false, + wantLIPurposeResult: false, + wantFlexPurposeResult: false, + }, + { + description: "enforce purpose & vendors on, bidder is a vendor exception", + config: purposeConfig{ + EnforcePurpose: true, + EnforceVendors: true, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}}, + }, + consent: NoConsentsWithP1P2P3V32RestrictionAllowNone, + wantConsentPurposeResult: false, + wantLIPurposeResult: false, + wantFlexPurposeResult: false, + }, + { + description: "enforce purpose & vendors on, purpose consent Y, vendor consent Y", + config: purposeConfig{ + EnforcePurpose: true, + EnforceVendors: true, + }, + consent: P1P2P3PurposeConsentAndV32VendorConsentWithP1P2P3V32RestrictionAllowNone, + wantConsentPurposeResult: false, + wantLIPurposeResult: false, + wantFlexPurposeResult: false, + }, + } + + for _, tt := range tests { + // convert consent string to TCF2 object + parsedConsent, err := vendorconsent.ParseString(tt.consent) + if err != nil { + t.Fatalf("Failed to parse consent %s: %s\n", tt.consent, tt.description) + } + consentMeta, ok := parsedConsent.(tcf2.ConsentMetadata) + if !ok { + t.Fatalf("Failed to convert consent %s: %s\n", tt.consent, tt.description) + } + + vendor := getVendorList(t).Vendor(appnexusID) + vendorInfo := VendorInfo{vendorID: appnexusID, vendor: vendor} + enforcer := FullEnforcement{cfg: tt.config} + + enforcer.cfg.PurposeID = consentconstants.Purpose(1) + consentPurposeResult := enforcer.LegalBasis(vendorInfo, openrtb_ext.BidderAppnexus, consentMeta, Overrides{}) + assert.Equal(t, tt.wantConsentPurposeResult, consentPurposeResult, tt.description+" -- GVL consent purpose") + + enforcer.cfg.PurposeID = consentconstants.Purpose(2) + LIPurposeresult := enforcer.LegalBasis(vendorInfo, openrtb_ext.BidderAppnexus, consentMeta, Overrides{}) + assert.Equal(t, tt.wantLIPurposeResult, LIPurposeresult, tt.description+" -- GVL LI purpose") + + enforcer.cfg.PurposeID = consentconstants.Purpose(3) + flexPurposeResult := enforcer.LegalBasis(vendorInfo, openrtb_ext.BidderAppnexus, consentMeta, Overrides{}) + assert.Equal(t, tt.wantFlexPurposeResult, flexPurposeResult, tt.description+" -- GVL flex purpose") + } +} + +func TestLegalBasisWithNoPubRestrictionsAndWithPubRestrictionAllowAll(t *testing.T) { + appnexusID := uint16(32) + + NoConsents := "CPfCRQAPfCRQAAAAAAENCgCAAAAAAAAAAAAAAAAAAAAA" + P1P2P3PurposeConsent := "CPfCRQAPfCRQAAAAAAENCgCAAOAAAAAAAAAAAAAAAAAA" + P1P2P3PurposeLI := "CPfCRQAPfCRQAAAAAAENCgCAAAAAAOAAAAAAAAAAAAAA" + V32VendorConsent := "CPfCRQAPfCRQAAAAAAENCgCAAAAAAAAAAAAAAQAAAAAEAAAAAAAA" + V32VendorLI := "CPfCRQAPfCRQAAAAAAENCgCAAAAAAAAAAAAAAQAAAAAAAIAAAAACAAAA" + P1P2P3PurposeConsentAndV32VendorConsent := "CPfCRQAPfCRQAAAAAAENCgCAAOAAAAAAAAAAAQAAAAAEAIAAAAAAAAAA" + P1P2P3PurposeLIAndV32VendorLI := "CPfCRQAPfCRQAAAAAAENCgCAAAAAAOAAAAAAAQAAAAAAAIAAAAACAAAA" + + NoConsentsWithP1P2P3V32RestrictionAllowAll := "CPfMKEAPfMKEAAAAAAENCgCAAAAAAAAAAAAAAQAAAAAAAIAAAAAAAGDgAgAgCwAQAQB4AIAIAAAA" + P1P2P3PurposeConsentWithP1P2P3V32RestrictionAllowAll := "CPfMKEAPfMKEAAAAAAENCgCAAOAAAAAAAAAAAQAAAAAAAIAAAAAAAGDgAgAgCwAQAQB4AIAIAAAA" + P1P2P3PurposeLIWithP1P2P3V32RestrictionAllowAll := "CPfMKEAPfMKEAAAAAAENCgCAAAAAAOAAAAAAAQAAAAAAAIAAAAAAAGDgAgAgCwAQAQB4AIAIAAAA" + V32VendorConsentWithP1P2P3V32RestrictionAllowAll := "CPfMKEAPfMKEAAAAAAENCgCAAAAAAAAAAAAAAQAAAAAEAIAAAAAAAGDgAgAgCwAQAQB4AIAIAAAA" + V32VendorLIWithP1P2P3V32RestrictionAllowAll := "CPfMKEAPfMKEAAAAAAENCgCAAAAAAAAAAAAAAQAAAAAAAIAAAAACAGDgAgAgCwAQAQB4AIAIAAAA" + P1P2P3PurposeConsentAndV32VendorConsentWithP1P2P3V32RestrictionAllowAll := "CPfMKEAPfMKEAAAAAAENCgCAAOAAAAAAAAAAAQAAAAAEAIAAAAAAAGDgAgAgCwAQAQB4AIAIAAAA" + P1P2P3PurposeLIAndV32VendorLIWithP1P2P3V32RestrictionAllowAll := "CPfMKEAPfMKEAAAAAAENCgCAAAAAAOAAAAAAAQAAAAAAAIAAAAACAGDgAgAgCwAQAQB4AIAIAAAA" + + tests := []struct { + description string + config purposeConfig + consentNoPubRestriction string + consentWithPubRestriction string + overrides Overrides + wantConsentPurposeResult bool + wantLIPurposeResult bool + wantFlexPurposeResult bool + }{ + { + description: "enforce purpose & vendors off", + config: purposeConfig{ + EnforcePurpose: false, + EnforceVendors: false, + }, + consentNoPubRestriction: NoConsents, + consentWithPubRestriction: NoConsentsWithP1P2P3V32RestrictionAllowAll, + wantConsentPurposeResult: true, + wantLIPurposeResult: true, + wantFlexPurposeResult: true, + }, + { + description: "enforce purpose on, purpose consent N, legit interest N", + config: purposeConfig{ + EnforcePurpose: true, + }, + consentNoPubRestriction: NoConsents, + consentWithPubRestriction: NoConsentsWithP1P2P3V32RestrictionAllowAll, + wantConsentPurposeResult: false, + wantLIPurposeResult: false, + wantFlexPurposeResult: false, + }, + { + description: "enforce purpose on, purpose consent Y", + config: purposeConfig{ + EnforcePurpose: true, + }, + consentNoPubRestriction: P1P2P3PurposeConsent, + consentWithPubRestriction: P1P2P3PurposeConsentWithP1P2P3V32RestrictionAllowAll, + wantConsentPurposeResult: true, + wantLIPurposeResult: false, + wantFlexPurposeResult: true, + }, + { + description: "enforce purpose on, legit interest Y", + config: purposeConfig{ + EnforcePurpose: true, + }, + consentNoPubRestriction: P1P2P3PurposeLI, + consentWithPubRestriction: P1P2P3PurposeLIWithP1P2P3V32RestrictionAllowAll, + wantConsentPurposeResult: false, + wantLIPurposeResult: true, + wantFlexPurposeResult: true, + }, + { + description: "enforce purpose on, purpose consent Y, enforce vendors off but overrides treats it as on", + config: purposeConfig{ + EnforcePurpose: true, + }, + consentNoPubRestriction: P1P2P3PurposeConsent, + consentWithPubRestriction: P1P2P3PurposeConsentWithP1P2P3V32RestrictionAllowAll, + overrides: Overrides{enforceVendors: true}, + wantConsentPurposeResult: false, + wantLIPurposeResult: false, + wantFlexPurposeResult: false, + }, + { + description: "enforce purpose on, purpose consent Y, vendor consent Y, enforce vendors off but overrides treats it as on", + config: purposeConfig{ + EnforcePurpose: true, + }, + consentNoPubRestriction: P1P2P3PurposeConsentAndV32VendorConsent, + consentWithPubRestriction: P1P2P3PurposeConsentAndV32VendorConsentWithP1P2P3V32RestrictionAllowAll, + overrides: Overrides{enforceVendors: true}, + wantConsentPurposeResult: true, + wantLIPurposeResult: false, + wantFlexPurposeResult: true, + }, + { + description: "enforce vendors on, vendor consent N, vendor legit interest N", + config: purposeConfig{ + EnforceVendors: true, + }, + consentNoPubRestriction: NoConsents, + consentWithPubRestriction: NoConsentsWithP1P2P3V32RestrictionAllowAll, + wantConsentPurposeResult: false, + wantLIPurposeResult: false, + wantFlexPurposeResult: false, + }, + { + description: "enforce vendors on, vendor consent Y", + config: purposeConfig{ + EnforceVendors: true, + }, + consentNoPubRestriction: V32VendorConsent, + consentWithPubRestriction: V32VendorConsentWithP1P2P3V32RestrictionAllowAll, + wantConsentPurposeResult: true, + wantLIPurposeResult: false, + wantFlexPurposeResult: true, + }, + { + description: "enforce vendors on, vendor legit interest Y", + config: purposeConfig{ + EnforceVendors: true, + }, + consentNoPubRestriction: V32VendorLI, + consentWithPubRestriction: V32VendorLIWithP1P2P3V32RestrictionAllowAll, + wantConsentPurposeResult: false, + wantLIPurposeResult: true, + wantFlexPurposeResult: true, + }, + { + description: "enforce vendors on, vendor consent Y, enforce purpose off but overrides treats it as on", + config: purposeConfig{ + EnforceVendors: true, + }, + consentNoPubRestriction: V32VendorConsent, + consentWithPubRestriction: V32VendorConsentWithP1P2P3V32RestrictionAllowAll, + overrides: Overrides{enforcePurpose: true}, + wantConsentPurposeResult: false, + wantLIPurposeResult: false, + wantFlexPurposeResult: false, + }, + { + description: "enforce vendors on, purpose consent Y, vendor consent Y, enforce purpose off but overrides treats it as on", + config: purposeConfig{ + EnforceVendors: true, + }, + consentNoPubRestriction: P1P2P3PurposeConsentAndV32VendorConsent, + consentWithPubRestriction: P1P2P3PurposeConsentAndV32VendorConsentWithP1P2P3V32RestrictionAllowAll, + overrides: Overrides{enforcePurpose: true}, + wantConsentPurposeResult: true, + wantLIPurposeResult: false, + wantFlexPurposeResult: true, + }, + { + description: "enforce purpose & vendors on, purpose consent Y, vendor consent N", + config: purposeConfig{ + EnforcePurpose: true, + EnforceVendors: true, + }, + consentNoPubRestriction: P1P2P3PurposeConsent, + consentWithPubRestriction: P1P2P3PurposeConsentWithP1P2P3V32RestrictionAllowAll, + wantConsentPurposeResult: false, + wantLIPurposeResult: false, + wantFlexPurposeResult: false, + }, + { + description: "enforce purpose & vendors on, purpose consent N, vendor consent Y", + config: purposeConfig{ + EnforcePurpose: true, + EnforceVendors: true, + }, + consentNoPubRestriction: V32VendorConsent, + consentWithPubRestriction: V32VendorConsentWithP1P2P3V32RestrictionAllowAll, + wantConsentPurposeResult: false, + wantLIPurposeResult: false, + wantFlexPurposeResult: false, + }, + { + description: "enforce purpose & vendors on, purpose consent Y, vendor consent Y", + config: purposeConfig{ + EnforcePurpose: true, + EnforceVendors: true, + }, + consentNoPubRestriction: P1P2P3PurposeConsentAndV32VendorConsent, + consentWithPubRestriction: P1P2P3PurposeConsentAndV32VendorConsentWithP1P2P3V32RestrictionAllowAll, + wantConsentPurposeResult: true, + wantLIPurposeResult: false, + wantFlexPurposeResult: true, + }, + { + description: "enforce purpose & vendors on, legit interest Y, vendor legit interest N", + config: purposeConfig{ + EnforcePurpose: true, + EnforceVendors: true, + }, + consentNoPubRestriction: P1P2P3PurposeLI, + consentWithPubRestriction: P1P2P3PurposeLIWithP1P2P3V32RestrictionAllowAll, + wantConsentPurposeResult: false, + wantLIPurposeResult: false, + wantFlexPurposeResult: false, + }, + { + description: "enforce purpose & vendors on, legit interest N, vendor legit interest Y", + config: purposeConfig{ + EnforcePurpose: true, + EnforceVendors: true, + }, + consentNoPubRestriction: V32VendorLI, + consentWithPubRestriction: V32VendorLIWithP1P2P3V32RestrictionAllowAll, + wantConsentPurposeResult: false, + wantLIPurposeResult: false, + wantFlexPurposeResult: false, + }, + { + description: "enforce purpose & vendors on, legit interest Y, vendor legit interest Y", + config: purposeConfig{ + EnforcePurpose: true, + EnforceVendors: true, + }, + consentNoPubRestriction: P1P2P3PurposeLIAndV32VendorLI, + consentWithPubRestriction: P1P2P3PurposeLIAndV32VendorLIWithP1P2P3V32RestrictionAllowAll, + wantConsentPurposeResult: false, + wantLIPurposeResult: true, + wantFlexPurposeResult: true, + }, + { + description: "enforce purpose & vendors on, bidder is a vendor exception", + config: purposeConfig{ + EnforcePurpose: true, + EnforceVendors: true, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}}, + }, + consentNoPubRestriction: NoConsents, + consentWithPubRestriction: NoConsentsWithP1P2P3V32RestrictionAllowAll, + wantConsentPurposeResult: true, + wantLIPurposeResult: true, + wantFlexPurposeResult: true, + }, + { + description: "enforce purpose & vendors on, bidder is a vendor exception but overrides disallow them", + config: purposeConfig{ + EnforcePurpose: true, + EnforceVendors: true, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}}, + }, + consentNoPubRestriction: NoConsents, + consentWithPubRestriction: NoConsentsWithP1P2P3V32RestrictionAllowAll, + overrides: Overrides{blockVendorExceptions: true}, + wantConsentPurposeResult: false, + wantLIPurposeResult: false, + wantFlexPurposeResult: false, + }, + } + + for _, tt := range tests { + consents := [2]string{tt.consentNoPubRestriction, tt.consentWithPubRestriction} + + for i := 0; i < len(consents); i++ { + consent := consents[i] + + // convert consent string to TCF2 object + parsedConsent, err := vendorconsent.ParseString(consent) + if err != nil { + t.Fatalf("Failed to parse consent %s: %s\n", consent, tt.description) + } + consentMeta, ok := parsedConsent.(tcf2.ConsentMetadata) + if !ok { + t.Fatalf("Failed to convert consent %s: %s\n", consent, tt.description) + } + + vendor := getVendorList(t).Vendor(appnexusID) + vendorInfo := VendorInfo{vendorID: appnexusID, vendor: vendor} + enforcer := FullEnforcement{cfg: tt.config} + + enforcer.cfg.PurposeID = consentconstants.Purpose(1) + consentPurposeResult := enforcer.LegalBasis(vendorInfo, openrtb_ext.BidderAppnexus, consentMeta, tt.overrides) + assert.Equal(t, tt.wantConsentPurposeResult, consentPurposeResult, tt.description+" -- GVL consent purpose -- consent string %d of %d", i+1, len(consents)) + + enforcer.cfg.PurposeID = consentconstants.Purpose(2) + LIPurposeresult := enforcer.LegalBasis(vendorInfo, openrtb_ext.BidderAppnexus, consentMeta, tt.overrides) + assert.Equal(t, tt.wantLIPurposeResult, LIPurposeresult, tt.description+" -- GVL LI purpose -- consent string %d of %d", i+1, len(consents)) + + enforcer.cfg.PurposeID = consentconstants.Purpose(3) + flexPurposeResult := enforcer.LegalBasis(vendorInfo, openrtb_ext.BidderAppnexus, consentMeta, tt.overrides) + assert.Equal(t, tt.wantFlexPurposeResult, flexPurposeResult, tt.description+" -- GVL flex purpose -- consent string %d of %d", i+1, len(consents)) + } + } +} + +func TestLegalBasisWithPubRestrictionRequireConsent(t *testing.T) { + appnexusID := uint16(32) + + NoConsentsWithP1P2P3V32RestrictionRequireConsent := "CPfFkMAPfFkMAAAAAAENCgCAAAAAAAAAAAAAAQAAAAAAAIAAAAAAAGCgAgAgCQAQAQBoAIAIAAAA" + P1P2P3PurposeConsentWithP1P2P3V32RestrictionRequireConsent := "CPfFkMAPfFkMAAAAAAENCgCAAOAAAAAAAAAAAQAAAAAAAIAAAAAAAGCgAgAgCQAQAQBoAIAIAAAA" + P1P2P3PurposeLIWithP1P2P3V32RestrictionRequireConsent := "CPfFkMAPfFkMAAAAAAENCgCAAAAAAOAAAAAAAQAAAAAAAIAAAAAAAGCgAgAgCQAQAQBoAIAIAAAA" + V32VendorConsentWithP1P2P3V32RestrictionRequireConsent := "CPfFkMAPfFkMAAAAAAENCgCAAAAAAAAAAAAAAQAAAAAEAIAAAAAAAGCgAgAgCQAQAQBoAIAIAAAA" + V32VendorLIWithP1P2P3V32RestrictionRequireConsent := "CPfFkMAPfFkMAAAAAAENCgCAAAAAAAAAAAAAAQAAAAAAAIAAAAACAGCgAgAgCQAQAQBoAIAIAAAA" + P1P2P3PurposeConsentAndV32VendorConsentWithP1P2P3V32RestrictionRequireConsent := "CPfFkMAPfFkMAAAAAAENCgCAAOAAAAAAAAAAAQAAAAAEAIAAAAAAAGCgAgAgCQAQAQBoAIAIAAAA" + P1P2P3PurposeLIAndV32VendorLIWithP1P2P3V32RestrictionRequireConsent := "CPfFkMAPfFkMAAAAAAENCgCAAAAAAOAAAAAAAQAAAAAAAIAAAAACAGCgAgAgCQAQAQBoAIAIAAAA" + + tests := []struct { + description string + config purposeConfig + consent string + overrides Overrides + wantConsentPurposeResult bool + wantLIPurposeResult bool + wantFlexPurposeResult bool + }{ + { + description: "enforce purpose & vendors off", + config: purposeConfig{ + EnforcePurpose: false, + EnforceVendors: false, + }, + consent: NoConsentsWithP1P2P3V32RestrictionRequireConsent, + wantConsentPurposeResult: true, + wantLIPurposeResult: true, + wantFlexPurposeResult: true, + }, + { + description: "enforce purpose on, purpose consent N, legit interest N", + config: purposeConfig{ + EnforcePurpose: true, + }, + consent: NoConsentsWithP1P2P3V32RestrictionRequireConsent, + wantConsentPurposeResult: false, + wantLIPurposeResult: false, + wantFlexPurposeResult: false, + }, + { + description: "enforce purpose on, purpose consent Y", + config: purposeConfig{ + EnforcePurpose: true, + }, + consent: P1P2P3PurposeConsentWithP1P2P3V32RestrictionRequireConsent, + wantConsentPurposeResult: true, + wantLIPurposeResult: false, + wantFlexPurposeResult: true, + }, + { + description: "enforce purpose on, legit interest Y", + config: purposeConfig{ + EnforcePurpose: true, + }, + consent: P1P2P3PurposeLIWithP1P2P3V32RestrictionRequireConsent, + wantConsentPurposeResult: false, + wantLIPurposeResult: false, + wantFlexPurposeResult: false, + }, + { + description: "enforce purpose on, purpose consent Y, enforce vendors off but overrides treats it as on", + config: purposeConfig{ + EnforcePurpose: true, + }, + consent: P1P2P3PurposeConsentWithP1P2P3V32RestrictionRequireConsent, + overrides: Overrides{enforceVendors: true}, + wantConsentPurposeResult: false, + wantLIPurposeResult: false, + wantFlexPurposeResult: false, + }, + { + description: "enforce purpose on, purpose consent Y, vendor consent Y, enforce vendors off but overrides treats it as on", + config: purposeConfig{ + EnforcePurpose: true, + }, + consent: P1P2P3PurposeConsentAndV32VendorConsentWithP1P2P3V32RestrictionRequireConsent, + overrides: Overrides{enforceVendors: true}, + wantConsentPurposeResult: true, + wantLIPurposeResult: false, + wantFlexPurposeResult: true, + }, + { + description: "enforce vendors on, vendor consent N, vendor legit interest N", + config: purposeConfig{ + EnforceVendors: true, + }, + consent: NoConsentsWithP1P2P3V32RestrictionRequireConsent, + wantConsentPurposeResult: false, + wantLIPurposeResult: false, + wantFlexPurposeResult: false, + }, + { + description: "enforce vendors on, vendor consent Y", + config: purposeConfig{ + EnforceVendors: true, + }, + consent: V32VendorConsentWithP1P2P3V32RestrictionRequireConsent, + wantConsentPurposeResult: true, + wantLIPurposeResult: false, + wantFlexPurposeResult: true, + }, + { + description: "enforce vendors on, vendor legit interest Y", + config: purposeConfig{ + EnforceVendors: true, + }, + consent: V32VendorLIWithP1P2P3V32RestrictionRequireConsent, + wantConsentPurposeResult: false, + wantLIPurposeResult: false, + wantFlexPurposeResult: false, + }, + { + description: "enforce vendors on, vendor consent Y, enforce purpose off but overrides treats it as on", + config: purposeConfig{ + EnforceVendors: true, + }, + consent: V32VendorConsentWithP1P2P3V32RestrictionRequireConsent, + overrides: Overrides{enforcePurpose: true}, + wantConsentPurposeResult: false, + wantLIPurposeResult: false, + wantFlexPurposeResult: false, + }, + { + description: "enforce vendors on, purpose consent Y, vendor consent Y, enforce purpose off but overrides treats it as on", + config: purposeConfig{ + EnforceVendors: true, + }, + consent: P1P2P3PurposeConsentAndV32VendorConsentWithP1P2P3V32RestrictionRequireConsent, + overrides: Overrides{enforcePurpose: true}, + wantConsentPurposeResult: true, + wantLIPurposeResult: false, + wantFlexPurposeResult: true, + }, + { + description: "enforce purpose & vendors on, purpose consent Y, vendor consent N", + config: purposeConfig{ + EnforcePurpose: true, + EnforceVendors: true, + }, + consent: P1P2P3PurposeConsentWithP1P2P3V32RestrictionRequireConsent, + wantConsentPurposeResult: false, + wantLIPurposeResult: false, + wantFlexPurposeResult: false, + }, + { + description: "enforce purpose & vendors on, purpose consent N, vendor consent Y", + config: purposeConfig{ + EnforcePurpose: true, + EnforceVendors: true, + }, + consent: V32VendorConsentWithP1P2P3V32RestrictionRequireConsent, + wantConsentPurposeResult: false, + wantLIPurposeResult: false, + wantFlexPurposeResult: false, + }, + { + description: "enforce purpose & vendors on, purpose consent Y, vendor consent Y", + config: purposeConfig{ + EnforcePurpose: true, + EnforceVendors: true, + }, + consent: P1P2P3PurposeConsentAndV32VendorConsentWithP1P2P3V32RestrictionRequireConsent, + wantConsentPurposeResult: true, + wantLIPurposeResult: false, + wantFlexPurposeResult: true, + }, + { + description: "enforce purpose & vendors on, legit interest Y, vendor legit interest N", + config: purposeConfig{ + EnforcePurpose: true, + EnforceVendors: true, + }, + consent: P1P2P3PurposeLIWithP1P2P3V32RestrictionRequireConsent, + wantConsentPurposeResult: false, + wantLIPurposeResult: false, + wantFlexPurposeResult: false, + }, + { + description: "enforce purpose & vendors on, legit interest N, vendor legit interest Y", + config: purposeConfig{ + EnforcePurpose: true, + EnforceVendors: true, + }, + consent: V32VendorLIWithP1P2P3V32RestrictionRequireConsent, + wantConsentPurposeResult: false, + wantLIPurposeResult: false, + wantFlexPurposeResult: false, + }, + { + description: "enforce purpose & vendors on, legit interest Y, vendor legit interest Y", + config: purposeConfig{ + EnforcePurpose: true, + EnforceVendors: true, + }, + consent: P1P2P3PurposeLIAndV32VendorLIWithP1P2P3V32RestrictionRequireConsent, + wantConsentPurposeResult: false, + wantLIPurposeResult: false, + wantFlexPurposeResult: false, + }, + { + description: "enforce purpose & vendors on, bidder is a vendor exception", + config: purposeConfig{ + EnforcePurpose: true, + EnforceVendors: true, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}}, + }, + consent: NoConsentsWithP1P2P3V32RestrictionRequireConsent, + wantConsentPurposeResult: true, + wantLIPurposeResult: true, + wantFlexPurposeResult: true, + }, + { + description: "enforce purpose & vendors on, bidder is a vendor exception but overrides disallow them", + config: purposeConfig{ + EnforcePurpose: true, + EnforceVendors: true, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}}, + }, + consent: NoConsentsWithP1P2P3V32RestrictionRequireConsent, + overrides: Overrides{blockVendorExceptions: true}, + wantConsentPurposeResult: false, + wantLIPurposeResult: false, + wantFlexPurposeResult: false, + }, + } + + for _, tt := range tests { + // convert consent string to TCF2 object + parsedConsent, err := vendorconsent.ParseString(tt.consent) + if err != nil { + t.Fatalf("Failed to parse consent %s: %s\n", tt.consent, tt.description) + } + consentMeta, ok := parsedConsent.(tcf2.ConsentMetadata) + if !ok { + t.Fatalf("Failed to convert consent %s: %s\n", tt.consent, tt.description) + } + + vendor := getVendorList(t).Vendor(appnexusID) + vendorInfo := VendorInfo{vendorID: appnexusID, vendor: vendor} + enforcer := FullEnforcement{cfg: tt.config} + + enforcer.cfg.PurposeID = consentconstants.Purpose(1) + consentPurposeResult := enforcer.LegalBasis(vendorInfo, openrtb_ext.BidderAppnexus, consentMeta, tt.overrides) + assert.Equal(t, tt.wantConsentPurposeResult, consentPurposeResult, tt.description+" -- GVL consent purpose") + + enforcer.cfg.PurposeID = consentconstants.Purpose(2) + LIPurposeresult := enforcer.LegalBasis(vendorInfo, openrtb_ext.BidderAppnexus, consentMeta, tt.overrides) + assert.Equal(t, tt.wantLIPurposeResult, LIPurposeresult, tt.description+" -- GVL LI purpose") + + enforcer.cfg.PurposeID = consentconstants.Purpose(3) + flexPurposeResult := enforcer.LegalBasis(vendorInfo, openrtb_ext.BidderAppnexus, consentMeta, tt.overrides) + assert.Equal(t, tt.wantFlexPurposeResult, flexPurposeResult, tt.description+" -- GVL flex purpose") + } +} + +func TestLegalBasisWithPubRestrictionRequireLI(t *testing.T) { + appnexusID := uint16(32) + + NoConsentsWithP1P2P3V32RestrictionRequireLI := "CPfFkMAPfFkMAAAAAAENCgCAAAAAAAAAAAAAAQAAAAAAAIAAAAAAAGDAAgAgCgAQAQBwAIAIAAAA" + P1P2P3PurposeConsentWithP1P2P3V32RestrictionRequireLI := "CPfFkMAPfFkMAAAAAAENCgCAAOAAAAAAAAAAAQAAAAAAAIAAAAAAAGDAAgAgCgAQAQBwAIAIAAAA" + P1P2P3PurposeLIWithP1P2P3V32RestrictionRequireLI := "CPfFkMAPfFkMAAAAAAENCgCAAAAAAOAAAAAAAQAAAAAAAIAAAAAAAGDAAgAgCgAQAQBwAIAIAAAA" + V32VendorConsentWithP1P2P3V32RestrictionRequireLI := "CPfFkMAPfFkMAAAAAAENCgCAAAAAAAAAAAAAAQAAAAAEAIAAAAAAAGDAAgAgCgAQAQBwAIAIAAAA" + V32VendorLIWithP1P2P3V32RestrictionRequireLI := "CPfFkMAPfFkMAAAAAAENCgCAAAAAAAAAAAAAAQAAAAAAAIAAAAACAGDAAgAgCgAQAQBwAIAIAAAA" + P1P2P3PurposeConsentAndV32VendorConsentWithP1P2P3V32RestrictionRequireLI := "CPfFkMAPfFkMAAAAAAENCgCAAOAAAAAAAAAAAQAAAAAEAIAAAAAAAGDAAgAgCgAQAQBwAIAIAAAA" + P1P2P3PurposeLIAndV32VendorLIWithP1P2P3V32RestrictionRequireLI := "CPfFkMAPfFkMAAAAAAENCgCAAAAAAOAAAAAAAQAAAAAAAIAAAAACAGDAAgAgCgAQAQBwAIAIAAAA" + + tests := []struct { + description string + config purposeConfig + consent string + overrides Overrides + wantConsentPurposeResult bool + wantLIPurposeResult bool + wantFlexPurposeResult bool + }{ + { + description: "enforce purpose & vendors off", + config: purposeConfig{ + EnforcePurpose: false, + EnforceVendors: false, + }, + consent: NoConsentsWithP1P2P3V32RestrictionRequireLI, + wantConsentPurposeResult: true, + wantLIPurposeResult: true, + wantFlexPurposeResult: true, + }, + { + description: "enforce purpose on, purpose consent N, legit interest N", + config: purposeConfig{ + EnforcePurpose: true, + }, + consent: NoConsentsWithP1P2P3V32RestrictionRequireLI, + wantConsentPurposeResult: false, + wantLIPurposeResult: false, + wantFlexPurposeResult: false, + }, + { + description: "enforce purpose on, purpose consent Y", + config: purposeConfig{ + EnforcePurpose: true, + }, + consent: P1P2P3PurposeConsentWithP1P2P3V32RestrictionRequireLI, + wantConsentPurposeResult: false, + wantLIPurposeResult: false, + wantFlexPurposeResult: false, + }, + { + description: "enforce purpose on, legit interest Y", + config: purposeConfig{ + EnforcePurpose: true, + }, + consent: P1P2P3PurposeLIWithP1P2P3V32RestrictionRequireLI, + wantConsentPurposeResult: false, + wantLIPurposeResult: true, + wantFlexPurposeResult: true, + }, + { + description: "enforce purpose on, vendor legit interest Y, enforce vendors off but overrides treats it as on", + config: purposeConfig{ + EnforcePurpose: true, + }, + consent: P1P2P3PurposeLIWithP1P2P3V32RestrictionRequireLI, + overrides: Overrides{enforceVendors: true}, + wantConsentPurposeResult: false, + wantLIPurposeResult: false, + wantFlexPurposeResult: false, + }, + { + description: "enforce purpose on, vendor legit interest Y, vendor consent Y, enforce vendors off but overrides treats it as on", + config: purposeConfig{ + EnforcePurpose: true, + }, + consent: P1P2P3PurposeLIAndV32VendorLIWithP1P2P3V32RestrictionRequireLI, + overrides: Overrides{enforceVendors: true}, + wantConsentPurposeResult: false, + wantLIPurposeResult: true, + wantFlexPurposeResult: true, + }, + { + description: "enforce vendors on, vendor consent N, vendor legit interest N", + config: purposeConfig{ + EnforceVendors: true, + }, + consent: NoConsentsWithP1P2P3V32RestrictionRequireLI, + wantConsentPurposeResult: false, + wantLIPurposeResult: false, + wantFlexPurposeResult: false, + }, + { + description: "enforce vendors on, vendor consent Y", + config: purposeConfig{ + EnforceVendors: true, + }, + consent: V32VendorConsentWithP1P2P3V32RestrictionRequireLI, + wantConsentPurposeResult: false, + wantLIPurposeResult: false, + wantFlexPurposeResult: false, + }, + { + description: "enforce vendors on, vendor legit interest Y", + config: purposeConfig{ + EnforceVendors: true, + }, + consent: V32VendorLIWithP1P2P3V32RestrictionRequireLI, + wantConsentPurposeResult: false, + wantLIPurposeResult: true, + wantFlexPurposeResult: true, + }, + { + description: "enforce vendors on, vendor legit interest Y, enforce purpose off but overrides treats it as on", + config: purposeConfig{ + EnforceVendors: true, + }, + consent: V32VendorLIWithP1P2P3V32RestrictionRequireLI, + overrides: Overrides{enforcePurpose: true}, + wantConsentPurposeResult: false, + wantLIPurposeResult: false, + wantFlexPurposeResult: false, + }, + { + description: "enforce vendors on, vendor legit interest Y, vendor consent Y, enforce purpose off but overrides treats it as on", + config: purposeConfig{ + EnforceVendors: true, + }, + consent: P1P2P3PurposeLIAndV32VendorLIWithP1P2P3V32RestrictionRequireLI, + overrides: Overrides{enforcePurpose: true}, + wantConsentPurposeResult: false, + wantLIPurposeResult: true, + wantFlexPurposeResult: true, + }, + { + description: "enforce purpose & vendors on, purpose consent Y, vendor consent N", + config: purposeConfig{ + EnforcePurpose: true, + EnforceVendors: true, + }, + consent: P1P2P3PurposeConsentWithP1P2P3V32RestrictionRequireLI, + wantConsentPurposeResult: false, + wantLIPurposeResult: false, + wantFlexPurposeResult: false, + }, + { + description: "enforce purpose & vendors on, purpose consent N, vendor consent Y", + config: purposeConfig{ + EnforcePurpose: true, + EnforceVendors: true, + }, + consent: V32VendorConsentWithP1P2P3V32RestrictionRequireLI, + wantConsentPurposeResult: false, + wantLIPurposeResult: false, + wantFlexPurposeResult: false, + }, + { + description: "enforce purpose & vendors on, purpose consent Y, vendor consent Y", + config: purposeConfig{ + EnforcePurpose: true, + EnforceVendors: true, + }, + consent: P1P2P3PurposeConsentAndV32VendorConsentWithP1P2P3V32RestrictionRequireLI, + wantConsentPurposeResult: false, + wantLIPurposeResult: false, + wantFlexPurposeResult: false, + }, + { + description: "enforce purpose & vendors on, legit interest Y, vendor legit interest N", + config: purposeConfig{ + EnforcePurpose: true, + EnforceVendors: true, + }, + consent: P1P2P3PurposeLIWithP1P2P3V32RestrictionRequireLI, + wantConsentPurposeResult: false, + wantLIPurposeResult: false, + wantFlexPurposeResult: false, + }, + { + description: "enforce purpose & vendors on, legit interest N, vendor legit interest Y", + config: purposeConfig{ + EnforcePurpose: true, + EnforceVendors: true, + }, + consent: V32VendorLIWithP1P2P3V32RestrictionRequireLI, + wantConsentPurposeResult: false, + wantLIPurposeResult: false, + wantFlexPurposeResult: false, + }, + { + description: "enforce purpose & vendors on, legit interest Y, vendor legit interest Y", + config: purposeConfig{ + EnforcePurpose: true, + EnforceVendors: true, + }, + consent: P1P2P3PurposeLIAndV32VendorLIWithP1P2P3V32RestrictionRequireLI, + wantConsentPurposeResult: false, + wantLIPurposeResult: true, + wantFlexPurposeResult: true, + }, + { + description: "enforce purpose & vendors on, bidder is a vendor exception", + config: purposeConfig{ + EnforcePurpose: true, + EnforceVendors: true, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}}, + }, + consent: NoConsentsWithP1P2P3V32RestrictionRequireLI, + wantConsentPurposeResult: true, + wantLIPurposeResult: true, + wantFlexPurposeResult: true, + }, + { + description: "enforce purpose & vendors on, bidder is a vendor exception but overrides disallow them", + config: purposeConfig{ + EnforcePurpose: true, + EnforceVendors: true, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}}, + }, + consent: NoConsentsWithP1P2P3V32RestrictionRequireLI, + overrides: Overrides{blockVendorExceptions: true}, + wantConsentPurposeResult: false, + wantLIPurposeResult: false, + wantFlexPurposeResult: false, + }, + } + + for _, tt := range tests { + // convert consent string to TCF2 object + parsedConsent, err := vendorconsent.ParseString(tt.consent) + if err != nil { + t.Fatalf("Failed to parse consent %s: %s\n", tt.consent, tt.description) + } + consentMeta, ok := parsedConsent.(tcf2.ConsentMetadata) + if !ok { + t.Fatalf("Failed to convert consent %s: %s\n", tt.consent, tt.description) + } + + vendor := getVendorList(t).Vendor(appnexusID) + vendorInfo := VendorInfo{vendorID: appnexusID, vendor: vendor} + enforcer := FullEnforcement{cfg: tt.config} + + enforcer.cfg.PurposeID = consentconstants.Purpose(1) + consentPurposeResult := enforcer.LegalBasis(vendorInfo, openrtb_ext.BidderAppnexus, consentMeta, tt.overrides) + assert.Equal(t, tt.wantConsentPurposeResult, consentPurposeResult, tt.description+" -- GVL consent purpose") + + enforcer.cfg.PurposeID = consentconstants.Purpose(2) + LIPurposeresult := enforcer.LegalBasis(vendorInfo, openrtb_ext.BidderAppnexus, consentMeta, tt.overrides) + assert.Equal(t, tt.wantLIPurposeResult, LIPurposeresult, tt.description+" -- GVL LI purpose") + + enforcer.cfg.PurposeID = consentconstants.Purpose(3) + flexPurposeResult := enforcer.LegalBasis(vendorInfo, openrtb_ext.BidderAppnexus, consentMeta, tt.overrides) + assert.Equal(t, tt.wantFlexPurposeResult, flexPurposeResult, tt.description+" -- GVL flex purpose") + } +} + +func getVendorList(t *testing.T) vendorlist.VendorList { + GVL := makeVendorList() + + marshaledGVL, err := json.Marshal(GVL) + if err != nil { + t.Fatalf("Failed to marshal GVL") + } + + parsedGVL, err := vendorlist2.ParseEagerly(marshaledGVL) + if err != nil { + t.Fatalf("Failed to parse vendor list data. %v", err) + } + return parsedGVL +} + +func makeVendorList() vendorList { + return vendorList{ + VendorListVersion: 2, + Vendors: map[string]*vendor{ + "32": { + ID: 32, + Purposes: []int{1}, + LegIntPurposes: []int{2}, + FlexiblePurposes: []int{3}, + }, + }, + } +} diff --git a/gdpr/gdpr.go b/gdpr/gdpr.go index 94ef864fe55..db6aa125383 100644 --- a/gdpr/gdpr.go +++ b/gdpr/gdpr.go @@ -11,34 +11,55 @@ type Permissions interface { // Determines whether or not the host company is allowed to read/write cookies. // // If the consent string was nonsensical, the returned error will be an ErrorMalformedConsent. - HostCookiesAllowed(ctx context.Context, gdprSignal Signal, consent string) (bool, error) + HostCookiesAllowed(ctx context.Context) (bool, error) // Determines whether or not the given bidder is allowed to user personal info for ad targeting. // // If the consent string was nonsensical, the returned error will be an ErrorMalformedConsent. - BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, gdprSignal Signal, consent string) (bool, error) + BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName) (bool, error) // Determines whether or not to send PI information to a bidder, or mask it out. // // If the consent string was nonsensical, the returned error will be an ErrorMalformedConsent. - AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string, aliasGVLIDs map[string]uint16) (permissions AuctionPermissions, err error) + AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions AuctionPermissions, err error) } -type PermissionsBuilder func(config.GDPR, TCF2ConfigReader, map[openrtb_ext.BidderName]uint16, VendorListFetcher) Permissions +type PermissionsBuilder func(TCF2ConfigReader, RequestInfo) Permissions -// NewPermissions gets an instance of the Permissions for use elsewhere in the project. -func NewPermissions(cfg config.GDPR, tcf2Config TCF2ConfigReader, vendorIDs map[openrtb_ext.BidderName]uint16, fetcher VendorListFetcher) Permissions { +type RequestInfo struct { + AliasGVLIDs map[string]uint16 + Consent string + GDPRSignal Signal + PublisherID string +} + +// NewPermissionsBuilder takes host config data used to configure the builder function it returns +func NewPermissionsBuilder(cfg config.GDPR, gvlVendorIDs map[openrtb_ext.BidderName]uint16, vendorListFetcher VendorListFetcher) PermissionsBuilder { + return func(tcf2Cfg TCF2ConfigReader, requestInfo RequestInfo) Permissions { + purposeEnforcerBuilder := NewPurposeEnforcerBuilder(tcf2Cfg) + + return NewPermissions(cfg, tcf2Cfg, gvlVendorIDs, vendorListFetcher, purposeEnforcerBuilder, requestInfo) + } +} + +// NewPermissions gets a per-request Permissions object that can then be used to check GDPR permissions for a given bidder. +func NewPermissions(cfg config.GDPR, tcf2Config TCF2ConfigReader, vendorIDs map[openrtb_ext.BidderName]uint16, fetcher VendorListFetcher, purposeEnforcerBuilder PurposeEnforcerBuilder, requestInfo RequestInfo) Permissions { if !cfg.Enabled { return &AlwaysAllow{} } permissionsImpl := &permissionsImpl{ - fetchVendorList: fetcher, - gdprDefaultValue: cfg.DefaultValue, - hostVendorID: cfg.HostVendorID, - nonStandardPublishers: cfg.NonStandardPublisherMap, - cfg: tcf2Config, - vendorIDs: vendorIDs, + fetchVendorList: fetcher, + gdprDefaultValue: cfg.DefaultValue, + hostVendorID: cfg.HostVendorID, + nonStandardPublishers: cfg.NonStandardPublisherMap, + cfg: tcf2Config, + vendorIDs: vendorIDs, + publisherID: requestInfo.PublisherID, + gdprSignal: SignalNormalize(requestInfo.GDPRSignal, cfg.DefaultValue), + consent: requestInfo.Consent, + aliasGVLIDs: requestInfo.AliasGVLIDs, + purposeEnforcerBuilder: purposeEnforcerBuilder, } if cfg.HostVendorID == 0 { diff --git a/gdpr/gdpr_test.go b/gdpr/gdpr_test.go index 906a27e227d..138da5d9d38 100644 --- a/gdpr/gdpr_test.go +++ b/gdpr/gdpr_test.go @@ -4,9 +4,11 @@ import ( "context" "testing" + "github.com/prebid/go-gdpr/consentconstants" "github.com/prebid/go-gdpr/vendorlist" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/stretchr/testify/assert" ) @@ -45,8 +47,19 @@ func TestNewPermissions(t *testing.T) { return nil, nil } - perms := NewPermissions(config, &tcf2Config{}, vendorIDs, vendorListFetcher) + fakePurposeEnforcerBuilder := fakePurposeEnforcerBuilder{ + purposeEnforcer: nil, + }.Builder + perms := NewPermissions(config, &tcf2Config{}, vendorIDs, vendorListFetcher, fakePurposeEnforcerBuilder, RequestInfo{}) assert.IsType(t, tt.wantType, perms, tt.description) } } + +type fakePurposeEnforcerBuilder struct { + purposeEnforcer PurposeEnforcer +} + +func (fpeb fakePurposeEnforcerBuilder) Builder(consentconstants.Purpose, openrtb_ext.BidderName) PurposeEnforcer { + return fpeb.purposeEnforcer +} diff --git a/gdpr/impl.go b/gdpr/impl.go index fd08d839d11..9f9b47cd766 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -7,79 +7,133 @@ import ( "github.com/prebid/go-gdpr/api" "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/prebid-server/openrtb_ext" ) +const noBidder openrtb_ext.BidderName = "" + +// permissionsImpl contains global and request-specific GDPR config data and is used to determine +// whether various cookie sync and auction activities are permitted for a request +// permissionsImpl implements the Permissions interface type permissionsImpl struct { - fetchVendorList VendorListFetcher - gdprDefaultValue string - hostVendorID int - nonStandardPublishers map[string]struct{} - cfg TCF2ConfigReader - vendorIDs map[openrtb_ext.BidderName]uint16 + // global + fetchVendorList VendorListFetcher + gdprDefaultValue string + hostVendorID int + nonStandardPublishers map[string]struct{} + purposeEnforcerBuilder PurposeEnforcerBuilder + vendorIDs map[openrtb_ext.BidderName]uint16 + // request-specific + aliasGVLIDs map[string]uint16 + cfg TCF2ConfigReader + consent string + gdprSignal Signal + publisherID string } -func (p *permissionsImpl) HostCookiesAllowed(ctx context.Context, gdprSignal Signal, consent string) (bool, error) { - gdprSignal = SignalNormalize(gdprSignal, p.gdprDefaultValue) - - if gdprSignal == SignalNo { +// HostCookiesAllowed determines whether the host is allowed to set cookies on the user's device +func (p *permissionsImpl) HostCookiesAllowed(ctx context.Context) (bool, error) { + if p.gdprSignal != SignalYes { return true, nil } - return p.allowSync(ctx, uint16(p.hostVendorID), consent, false) + return p.allowSync(ctx, uint16(p.hostVendorID), noBidder, false) } -func (p *permissionsImpl) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, gdprSignal Signal, consent string) (bool, error) { - gdprSignal = SignalNormalize(gdprSignal, p.gdprDefaultValue) - - if gdprSignal == SignalNo { +// BidderSyncAllowed determines whether a given bidder is allowed to perform a cookie sync +func (p *permissionsImpl) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName) (bool, error) { + if p.gdprSignal != SignalYes { return true, nil } id, ok := p.vendorIDs[bidder] if ok { - vendorException := p.cfg.PurposeVendorException(consentconstants.Purpose(1), bidder) - return p.allowSync(ctx, id, consent, vendorException) + vendorExceptions := p.cfg.PurposeVendorExceptions(consentconstants.Purpose(1)) + _, vendorException := vendorExceptions[bidder] + return p.allowSync(ctx, id, bidder, vendorException) } return false, nil } -func (p *permissionsImpl) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string, aliasGVLIDs map[string]uint16) (permissions AuctionPermissions, err error) { - if _, ok := p.nonStandardPublishers[PublisherID]; ok { +// AuctionActivitiesAllowed determines whether auction activities are permitted for a given bidder +func (p *permissionsImpl) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions AuctionPermissions, err error) { + if _, ok := p.nonStandardPublishers[p.publisherID]; ok { return AllowAll, nil } - gdprSignal = SignalNormalize(gdprSignal, p.gdprDefaultValue) - - if gdprSignal == SignalNo { + if p.gdprSignal != SignalYes { return AllowAll, nil } - if consent == "" && gdprSignal == SignalYes { + if p.consent == "" { + return p.defaultPermissions(), nil + } + + // note the bidder here is guaranteed to be enabled + vendorID, vendorFound := p.resolveVendorID(bidderCoreName, bidder) + basicEnforcementVendors := p.cfg.BasicEnforcementVendors() + _, weakVendorEnforcement := basicEnforcementVendors[string(bidder)] + + if !vendorFound && !weakVendorEnforcement { return DenyAll, nil } - weakVendorEnforcement := p.cfg.BasicEnforcementVendor(bidder) + parsedConsent, vendor, err := p.parseVendor(ctx, vendorID, p.consent) + if err != nil { + return p.defaultPermissions(), err + } + + // vendor will be nil if not a valid TCF2 consent string + if vendor == nil { + if weakVendorEnforcement && parsedConsent.Version() == 2 { + vendor = vendorTrue{} + } else { + return p.defaultPermissions(), nil + } + } + + if !p.cfg.IsEnabled() { + return AllowBidRequestOnly, nil + } - if id, ok := p.resolveVendorId(bidderCoreName, bidder, aliasGVLIDs); ok { - return p.allowActivities(ctx, id, bidderCoreName, consent, weakVendorEnforcement) - } else if weakVendorEnforcement { - return p.allowActivities(ctx, 0, bidderCoreName, consent, weakVendorEnforcement) + consentMeta, ok := parsedConsent.(tcf2.ConsentMetadata) + if !ok { + err = fmt.Errorf("Unable to access TCF2 parsed consent") + return p.defaultPermissions(), err } - return p.defaultVendorPermissions() + vendorInfo := VendorInfo{vendorID: vendorID, vendor: vendor} + permissions = AuctionPermissions{} + permissions.AllowBidRequest = p.allowBidRequest(bidderCoreName, consentMeta, vendorInfo) + permissions.PassGeo = p.allowGeo(bidderCoreName, consentMeta, vendor) + permissions.PassID = p.allowID(bidderCoreName, consentMeta, vendorInfo) + + return permissions, nil } -func (p *permissionsImpl) defaultVendorPermissions() (permissions AuctionPermissions, err error) { - return DenyAll, nil +// defaultPermissions returns a permissions object that denies passing user IDs while +// allowing passing geo information and sending bid requests based on whether purpose 2 +// and feature one are enforced respectively +// if the consent string is empty or malformed we should use the default permissions +func (p *permissionsImpl) defaultPermissions() AuctionPermissions { + perms := AuctionPermissions{} + + if !p.cfg.PurposeEnforced(consentconstants.Purpose(2)) { + perms.AllowBidRequest = true + } + if !p.cfg.FeatureOneEnforced() { + perms.PassGeo = true + } + return perms } -func (p *permissionsImpl) resolveVendorId(bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName, aliasGVLIDs map[string]uint16) (id uint16, ok bool) { - if id, ok = aliasGVLIDs[string(bidder)]; ok { +// resolveVendorID gets the vendor ID for the specified bidder from either the alias GVL IDs +// provided in the request or from the bidder configs loaded at startup +func (p *permissionsImpl) resolveVendorID(bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (id uint16, ok bool) { + if id, ok = p.aliasGVLIDs[string(bidder)]; ok { return id, ok } @@ -88,12 +142,14 @@ func (p *permissionsImpl) resolveVendorId(bidderCoreName openrtb_ext.BidderName, return id, ok } -func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, consent string, vendorException bool) (bool, error) { - if consent == "" { +// allowSync computes cookie sync activity legal basis for a given bidder using the enforcement +// algorithms selected by the purpose enforcer builder +func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, bidder openrtb_ext.BidderName, vendorException bool) (bool, error) { + if p.consent == "" { return false, nil } - parsedConsent, vendor, err := p.parseVendor(ctx, vendorID, consent) + parsedConsent, vendor, err := p.parseVendor(ctx, vendorID, p.consent) if err != nil { return false, err } @@ -115,120 +171,65 @@ func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, consen return p.cfg.PurposeOneTreatmentAccessAllowed(), nil } - enforceVendors := p.cfg.PurposeEnforcingVendors(tcf2ConsentConstants.InfoStorageAccess) - return p.checkPurpose(consentMeta, vendor, vendorID, tcf2ConsentConstants.InfoStorageAccess, enforceVendors, vendorException, false), nil -} - -func (p *permissionsImpl) allowActivities(ctx context.Context, vendorID uint16, bidder openrtb_ext.BidderName, consent string, weakVendorEnforcement bool) (AuctionPermissions, error) { - parsedConsent, vendor, err := p.parseVendor(ctx, vendorID, consent) - if err != nil { - return DenyAll, err - } - - // vendor will be nil if not a valid TCF2 consent string - if vendor == nil { - if weakVendorEnforcement && parsedConsent.Version() == 2 { - vendor = vendorTrue{} - } else { - return DenyAll, nil - } - } - - if !p.cfg.IsEnabled() { - return AllowBidRequestOnly, nil - } + purpose := consentconstants.Purpose(1) + enforcer := p.purposeEnforcerBuilder(purpose, bidder) - consentMeta, ok := parsedConsent.(tcf2.ConsentMetadata) - if !ok { - err = fmt.Errorf("Unable to access TCF2 parsed consent") - return DenyAll, err - } - - permissions := AuctionPermissions{} - if p.cfg.FeatureOneEnforced() { - vendorException := p.cfg.FeatureOneVendorException(bidder) - permissions.PassGeo = vendorException || (consentMeta.SpecialFeatureOptIn(1) && (vendor.SpecialFeature(1) || weakVendorEnforcement)) - } else { - permissions.PassGeo = true - } - if p.cfg.PurposeEnforced(consentconstants.Purpose(2)) { - enforceVendors := p.cfg.PurposeEnforcingVendors(consentconstants.Purpose(2)) - vendorException := p.cfg.PurposeVendorException(consentconstants.Purpose(2), bidder) - permissions.AllowBidRequest = p.checkPurpose(consentMeta, vendor, vendorID, consentconstants.Purpose(2), enforceVendors, vendorException, weakVendorEnforcement) - } else { - permissions.AllowBidRequest = true - } - for i := 2; i <= 10; i++ { - enforceVendors := p.cfg.PurposeEnforcingVendors(consentconstants.Purpose(i)) - vendorException := p.cfg.PurposeVendorException(consentconstants.Purpose(i), bidder) - if p.checkPurpose(consentMeta, vendor, vendorID, consentconstants.Purpose(i), enforceVendors, vendorException, weakVendorEnforcement) { - permissions.PassID = true - break - } + vendorInfo := VendorInfo{vendorID: vendorID, vendor: vendor} + if enforcer.LegalBasis(vendorInfo, bidder, consentMeta, Overrides{blockVendorExceptions: !vendorException}) { + return true, nil } - - return permissions, nil + return false, nil } -const pubRestrictNotAllowed = 0 -const pubRestrictRequireConsent = 1 -const pubRestrictRequireLegitInterest = 2 - -func (p *permissionsImpl) checkPurpose(consent tcf2.ConsentMetadata, vendor api.Vendor, vendorID uint16, purpose consentconstants.Purpose, enforceVendors, vendorException, weakVendorEnforcement bool) bool { - if consent.CheckPubRestriction(uint8(purpose), pubRestrictNotAllowed, vendorID) { - return false - } - - if vendorException { - return true - } - - purposeAllowed := p.consentEstablished(consent, vendor, vendorID, purpose, enforceVendors, weakVendorEnforcement) - legitInterest := p.legitInterestEstablished(consent, vendor, vendorID, purpose, enforceVendors, weakVendorEnforcement) +// allowBidRequest computes legal basis for a given bidder using the enforcement algorithms selected +// by the purpose enforcer builder +func (p *permissionsImpl) allowBidRequest(bidder openrtb_ext.BidderName, consentMeta tcf2.ConsentMetadata, vendorInfo VendorInfo) bool { + enforcer := p.purposeEnforcerBuilder(consentconstants.Purpose(2), bidder) - if consent.CheckPubRestriction(uint8(purpose), pubRestrictRequireConsent, vendorID) { - return purposeAllowed - } - if consent.CheckPubRestriction(uint8(purpose), pubRestrictRequireLegitInterest, vendorID) { - // Need LITransparency here - return legitInterest + overrides := Overrides{} + if _, ok := enforcer.(*BasicEnforcement); ok { + overrides.allowLITransparency = true } - - return purposeAllowed || legitInterest + return enforcer.LegalBasis(vendorInfo, bidder, consentMeta, overrides) } -func (p *permissionsImpl) consentEstablished(consent tcf2.ConsentMetadata, vendor api.Vendor, vendorID uint16, purpose consentconstants.Purpose, enforceVendors, weakVendorEnforcement bool) bool { - if !consent.PurposeAllowed(purpose) { - return false - } - if weakVendorEnforcement { - return true - } - if !enforceVendors { +// allowGeo computes legal basis for a given bidder using the configs, consent and GVL pertaining to +// feature one +func (p *permissionsImpl) allowGeo(bidder openrtb_ext.BidderName, consentMeta tcf2.ConsentMetadata, vendor api.Vendor) bool { + if !p.cfg.FeatureOneEnforced() { return true } - if vendor.Purpose(purpose) && consent.VendorConsent(vendorID) { + if p.cfg.FeatureOneVendorException(bidder) { return true } - return false + + basicEnforcementVendors := p.cfg.BasicEnforcementVendors() + _, weakVendorEnforcement := basicEnforcementVendors[string(bidder)] + return consentMeta.SpecialFeatureOptIn(1) && (vendor.SpecialFeature(1) || weakVendorEnforcement) } -func (p *permissionsImpl) legitInterestEstablished(consent tcf2.ConsentMetadata, vendor api.Vendor, vendorID uint16, purpose consentconstants.Purpose, enforceVendors, weakVendorEnforcement bool) bool { - if !consent.PurposeLITransparency(purpose) { - return false - } - if weakVendorEnforcement { - return true - } - if !enforceVendors { - return true - } - if vendor.LegitimateInterest(purpose) && consent.VendorLegitInterest(vendorID) { - return true +// allowID computes the pass user ID activity legal basis for a given bidder using the enforcement algorithms +// selected by the purpose enforcer builder. For the user ID activity, the selected enforcement algorithm must +// always assume we are enforcing the purpose. +// If the purpose for which we are computing legal basis is purpose 2, the algorithm should allow LI transparency. +func (p *permissionsImpl) allowID(bidder openrtb_ext.BidderName, consentMeta tcf2.ConsentMetadata, vendorInfo VendorInfo) bool { + for i := 2; i <= 10; i++ { + purpose := consentconstants.Purpose(i) + enforcer := p.purposeEnforcerBuilder(purpose, bidder) + + overrides := Overrides{enforcePurpose: true, enforceVendors: true} + if _, ok := enforcer.(*BasicEnforcement); ok && purpose == consentconstants.Purpose(2) { + overrides.allowLITransparency = true + } + if enforcer.LegalBasis(vendorInfo, bidder, consentMeta, overrides) { + return true + } } + return false } +// parseVendor parses the consent string and fetches the specified vendor's information from the GVL 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 { @@ -259,22 +260,20 @@ type AllowHostCookies struct { } // HostCookiesAllowed always returns true -func (p *AllowHostCookies) HostCookiesAllowed(ctx context.Context, gdprSignal Signal, consent string) (bool, error) { +func (p *AllowHostCookies) HostCookiesAllowed(ctx context.Context) (bool, error) { return true, nil } // Exporting to allow for easy test setups type AlwaysAllow struct{} -func (a AlwaysAllow) HostCookiesAllowed(ctx context.Context, gdprSignal Signal, consent string) (bool, error) { +func (a AlwaysAllow) HostCookiesAllowed(ctx context.Context) (bool, error) { return true, nil } - -func (a AlwaysAllow) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, gdprSignal Signal, consent string) (bool, error) { +func (a AlwaysAllow) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName) (bool, error) { return true, nil } - -func (a AlwaysAllow) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string, aliasGVLIDs map[string]uint16) (permissions AuctionPermissions, err error) { +func (a AlwaysAllow) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions AuctionPermissions, err error) { return AllowAll, nil } diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index b51c376f65c..3512a271061 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -16,31 +16,37 @@ import ( ) func TestDisallowOnEmptyConsent(t *testing.T) { + emptyConsent := "" perms := permissionsImpl{ cfg: &tcf2Config{}, fetchVendorList: failedListFetcher, gdprDefaultValue: "0", hostVendorID: 3, vendorIDs: nil, + gdprSignal: SignalYes, + consent: emptyConsent, } - allowSync, err := perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderAppnexus, SignalYes, "") + allowSync, err := perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderAppnexus) assertBoolsEqual(t, false, allowSync) assertNilErr(t, err) - allowSync, err = perms.HostCookiesAllowed(context.Background(), SignalYes, "") + allowSync, err = perms.HostCookiesAllowed(context.Background()) assertBoolsEqual(t, false, allowSync) assertNilErr(t, err) } func TestAllowOnSignalNo(t *testing.T) { - perms := permissionsImpl{} emptyConsent := "" + perms := permissionsImpl{ + gdprSignal: SignalNo, + consent: emptyConsent, + } - allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalNo, emptyConsent) + allowSync, err := perms.HostCookiesAllowed(context.Background()) assert.Equal(t, true, allowSync) assert.Nil(t, err) - allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderAppnexus, SignalNo, emptyConsent) + allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderAppnexus) assert.Equal(t, true, allowSync) assert.Nil(t, err) } @@ -60,7 +66,7 @@ func TestAllowedSyncs(t *testing.T) { tcf2AggConfig := tcf2Config{ HostConfig: config.TCF2{ Purpose1: config.TCF2Purpose{ - EnforcePurpose: config.TCF2FullEnforcement, + EnforcePurpose: true, EnforceVendors: true, }, }, @@ -78,13 +84,16 @@ func TestAllowedSyncs(t *testing.T) { fetchVendorList: listFetcher(map[uint16]vendorlist.VendorList{ 1: parseVendorListDataV2(t, vendorListData), }), + purposeEnforcerBuilder: NewPurposeEnforcerBuilder(&tcf2AggConfig), + gdprSignal: SignalYes, + consent: vendor2AndPurpose1Consent, } - allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, vendor2AndPurpose1Consent) + allowSync, err := perms.HostCookiesAllowed(context.Background()) assertNilErr(t, err) assertBoolsEqual(t, true, allowSync) - allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderAppnexus, SignalYes, vendor2AndPurpose1Consent) + allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderAppnexus) assertNilErr(t, err) assertBoolsEqual(t, true, allowSync) } @@ -104,7 +113,7 @@ func TestProhibitedPurposes(t *testing.T) { tcf2AggConfig := tcf2Config{ HostConfig: config.TCF2{ Purpose1: config.TCF2Purpose{ - EnforcePurpose: config.TCF2FullEnforcement, + EnforcePurpose: true, }, }, } @@ -121,13 +130,16 @@ func TestProhibitedPurposes(t *testing.T) { fetchVendorList: listFetcher(map[uint16]vendorlist.VendorList{ 1: parseVendorListDataV2(t, vendorListData), }), + purposeEnforcerBuilder: NewPurposeEnforcerBuilder(&tcf2AggConfig), + gdprSignal: SignalYes, + consent: vendor2NoPurpose1Consent, } - allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, vendor2NoPurpose1Consent) + allowSync, err := perms.HostCookiesAllowed(context.Background()) assertNilErr(t, err) assertBoolsEqual(t, false, allowSync) - allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderAppnexus, SignalYes, vendor2NoPurpose1Consent) + allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderAppnexus) assertNilErr(t, err) assertBoolsEqual(t, false, allowSync) } @@ -147,7 +159,7 @@ func TestProhibitedVendors(t *testing.T) { tcf2AggConfig := tcf2Config{ HostConfig: config.TCF2{ Purpose1: config.TCF2Purpose{ - EnforcePurpose: config.TCF2FullEnforcement, + EnforcePurpose: true, EnforceVendors: true, }, }, @@ -165,13 +177,16 @@ func TestProhibitedVendors(t *testing.T) { fetchVendorList: listFetcher(map[uint16]vendorlist.VendorList{ 1: parseVendorListDataV2(t, vendorListData), }), + purposeEnforcerBuilder: NewPurposeEnforcerBuilder(&tcf2AggConfig), + gdprSignal: SignalYes, + consent: purpose1NoVendorConsent, } - allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, purpose1NoVendorConsent) + allowSync, err := perms.HostCookiesAllowed(context.Background()) assertNilErr(t, err) assertBoolsEqual(t, false, allowSync) - allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderPubmatic, SignalYes, purpose1NoVendorConsent) + allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderPubmatic) assertNilErr(t, err) assertBoolsEqual(t, false, allowSync) } @@ -180,9 +195,11 @@ func TestMalformedConsent(t *testing.T) { perms := permissionsImpl{ hostVendorID: 2, fetchVendorList: listFetcher(nil), + gdprSignal: SignalYes, + consent: "BON", } - sync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "BON") + sync, err := perms.HostCookiesAllowed(context.Background()) assertErr(t, err, true) assertBoolsEqual(t, false, sync) } @@ -198,7 +215,6 @@ func TestAllowActivities(t *testing.T) { bidderName openrtb_ext.BidderName bidderCoreName openrtb_ext.BidderName publisherID string - gdprDefaultValue string gdpr Signal consent string passID bool @@ -206,106 +222,79 @@ func TestAllowActivities(t *testing.T) { aliasGVLIDs map[string]uint16 }{ { - description: "Allow PI - Non standard publisher", - bidderName: bidderBlockedByConsent, - bidderCoreName: bidderBlockedByConsent, - publisherID: "appNexusAppID", - gdprDefaultValue: "1", - gdpr: SignalYes, - consent: vendor2AndPurpose2Consent, - passID: true, - }, - { - description: "Allow PI - known vendor with No GDPR", - bidderName: bidderBlockedByConsent, - bidderCoreName: bidderBlockedByConsent, - gdprDefaultValue: "1", - gdpr: SignalNo, - consent: vendor2AndPurpose2Consent, - passID: true, - }, - { - description: "Allow PI - known vendor with Yes GDPR", - bidderName: bidderAllowedByConsent, - bidderCoreName: bidderAllowedByConsent, - gdprDefaultValue: "1", - gdpr: SignalYes, - consent: vendor2AndPurpose2Consent, - passID: true, + description: "Allow PI - Non standard publisher", + bidderName: bidderBlockedByConsent, + bidderCoreName: bidderBlockedByConsent, + publisherID: "appNexusAppID", + gdpr: SignalYes, + consent: vendor2AndPurpose2Consent, + passID: true, }, { - description: "Allow PI - known Alias vendor GVLID with Yes GDPR", - bidderName: aliasedBidderAllowedByConsent, - bidderCoreName: bidderAllowedByConsent, - gdprDefaultValue: "1", - gdpr: SignalYes, - consent: vendor2AndPurpose2Consent, - passID: true, - aliasGVLIDs: map[string]uint16{"appnexus1": 2}, + description: "Allow PI - known vendor with No GDPR", + bidderName: bidderBlockedByConsent, + bidderCoreName: bidderBlockedByConsent, + gdpr: SignalNo, + consent: vendor2AndPurpose2Consent, + passID: true, }, { - description: "Don't allow PI - known alias vendor with Yes GDPR, alias vendor does not consent to purpose 2", - bidderName: aliasedBidderAllowedByConsent, - bidderCoreName: bidderAllowedByConsent, - gdprDefaultValue: "1", - gdpr: SignalYes, - consent: vendor2AndPurpose2Consent, - passID: false, - aliasGVLIDs: map[string]uint16{"appnexus1": 1}, + description: "Allow PI - known vendor with Yes GDPR", + bidderName: bidderAllowedByConsent, + bidderCoreName: bidderAllowedByConsent, + gdpr: SignalYes, + consent: vendor2AndPurpose2Consent, + passID: true, }, { - description: "PI allowed according to host setting gdprDefaultValue 0 - known vendor with ambiguous GDPR and empty consent", - bidderName: bidderAllowedByConsent, - bidderCoreName: bidderAllowedByConsent, - gdprDefaultValue: "0", - gdpr: SignalAmbiguous, - consent: "", - passID: true, + description: "Allow PI - known Alias vendor GVLID with Yes GDPR", + bidderName: aliasedBidderAllowedByConsent, + bidderCoreName: bidderAllowedByConsent, + gdpr: SignalYes, + consent: vendor2AndPurpose2Consent, + passID: true, + aliasGVLIDs: map[string]uint16{"appnexus1": 2}, }, { - description: "PI allowed according to host setting gdprDefaultValue 0 - known vendor with ambiguous GDPR and non-empty consent", - bidderName: bidderAllowedByConsent, - bidderCoreName: bidderAllowedByConsent, - gdprDefaultValue: "0", - gdpr: SignalAmbiguous, - consent: vendor2AndPurpose2Consent, - passID: true, + description: "Don't allow PI - known alias vendor with Yes GDPR, alias vendor does not consent to purpose 2", + bidderName: aliasedBidderAllowedByConsent, + bidderCoreName: bidderAllowedByConsent, + gdpr: SignalYes, + consent: vendor2AndPurpose2Consent, + passID: false, + aliasGVLIDs: map[string]uint16{"appnexus1": 1}, }, { - description: "PI allowed according to host setting gdprDefaultValue 1 - known vendor with ambiguous GDPR and empty consent", - bidderName: bidderAllowedByConsent, - bidderCoreName: bidderAllowedByConsent, - gdprDefaultValue: "1", - gdpr: SignalAmbiguous, - consent: "", - passID: false, + description: "Allow PI - known vendor with Ambiguous GDPR and empty consent", + bidderName: bidderAllowedByConsent, + bidderCoreName: bidderAllowedByConsent, + gdpr: SignalAmbiguous, + consent: "", + passID: true, }, { - description: "PI allowed according to host setting gdprDefaultValue 1 - known vendor with ambiguous GDPR and non-empty consent", - bidderName: bidderAllowedByConsent, - bidderCoreName: bidderAllowedByConsent, - gdprDefaultValue: "1", - gdpr: SignalAmbiguous, - consent: vendor2AndPurpose2Consent, - passID: true, + description: "Allow PI - known vendor with Ambiguous GDPR and non-empty consent", + bidderName: bidderAllowedByConsent, + bidderCoreName: bidderAllowedByConsent, + gdpr: SignalAmbiguous, + consent: vendor2AndPurpose2Consent, + passID: true, }, { - description: "Don't allow PI - known vendor with Yes GDPR and empty consent", - bidderName: bidderAllowedByConsent, - bidderCoreName: bidderAllowedByConsent, - gdprDefaultValue: "1", - gdpr: SignalYes, - consent: "", - passID: false, + description: "Don't allow PI - known vendor with Yes GDPR and empty consent", + bidderName: bidderAllowedByConsent, + bidderCoreName: bidderAllowedByConsent, + gdpr: SignalYes, + consent: "", + passID: false, }, { - description: "Don't allow PI - default vendor with Yes GDPR and non-empty consent", - bidderName: bidderBlockedByConsent, - bidderCoreName: bidderBlockedByConsent, - gdprDefaultValue: "1", - gdpr: SignalYes, - consent: vendor2AndPurpose2Consent, - passID: false, + description: "Don't allow PI - default vendor with Yes GDPR and non-empty consent", + bidderName: bidderBlockedByConsent, + bidderCoreName: bidderBlockedByConsent, + gdpr: SignalYes, + consent: vendor2AndPurpose2Consent, + passID: false, }, } vendorListData := MarshalVendorList(vendorList{ @@ -329,12 +318,16 @@ func TestAllowActivities(t *testing.T) { fetchVendorList: listFetcher(map[uint16]vendorlist.VendorList{ 1: parseVendorListDataV2(t, vendorListData), }), + purposeEnforcerBuilder: NewPurposeEnforcerBuilder(&tcf2AggConfig), } for _, tt := range tests { - perms.gdprDefaultValue = tt.gdprDefaultValue + perms.aliasGVLIDs = tt.aliasGVLIDs + perms.consent = tt.consent + perms.gdprSignal = tt.gdpr + perms.publisherID = tt.publisherID - permissions, err := perms.AuctionActivitiesAllowed(context.Background(), tt.bidderCoreName, tt.bidderName, tt.publisherID, tt.gdpr, tt.consent, tt.aliasGVLIDs) + permissions, err := perms.AuctionActivitiesAllowed(context.Background(), tt.bidderCoreName, tt.bidderName) assert.Nil(t, err, tt.description) assert.Equal(t, tt.passID, permissions.PassID, tt.description) @@ -384,16 +377,16 @@ func allPurposesEnabledTCF2Config() (TCF2AggConfig tcf2Config) { TCF2AggConfig = tcf2Config{ HostConfig: config.TCF2{ Enabled: 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}, + Purpose1: config.TCF2Purpose{EnforceAlgoID: config.TCF2FullEnforcement, EnforcePurpose: true, EnforceVendors: true}, + Purpose2: config.TCF2Purpose{EnforceAlgoID: config.TCF2FullEnforcement, EnforcePurpose: true, EnforceVendors: true}, + Purpose3: config.TCF2Purpose{EnforceAlgoID: config.TCF2FullEnforcement, EnforcePurpose: true, EnforceVendors: true}, + Purpose4: config.TCF2Purpose{EnforceAlgoID: config.TCF2FullEnforcement, EnforcePurpose: true, EnforceVendors: true}, + Purpose5: config.TCF2Purpose{EnforceAlgoID: config.TCF2FullEnforcement, EnforcePurpose: true, EnforceVendors: true}, + Purpose6: config.TCF2Purpose{EnforceAlgoID: config.TCF2FullEnforcement, EnforcePurpose: true, EnforceVendors: true}, + Purpose7: config.TCF2Purpose{EnforceAlgoID: config.TCF2FullEnforcement, EnforcePurpose: true, EnforceVendors: true}, + Purpose8: config.TCF2Purpose{EnforceAlgoID: config.TCF2FullEnforcement, EnforcePurpose: true, EnforceVendors: true}, + Purpose9: config.TCF2Purpose{EnforceAlgoID: config.TCF2FullEnforcement, EnforcePurpose: true, EnforceVendors: true}, + Purpose10: config.TCF2Purpose{EnforceAlgoID: config.TCF2FullEnforcement, EnforcePurpose: true, EnforceVendors: true}, SpecialFeature1: config.TCF2SpecialFeature{Enforce: true}, }, AccountConfig: config.AccountGDPR{ @@ -455,6 +448,7 @@ func TestAllowActivitiesGeoAndID(t *testing.T) { 34: parseVendorListDataV2(t, vendorListData), 74: parseVendorListDataV2(t, vendorListData), }), + gdprSignal: SignalYes, } // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : full consents to purposes and vendors 2, 6, 8 and special feature 1 opt-in @@ -547,8 +541,11 @@ func TestAllowActivitiesGeoAndID(t *testing.T) { tcf2AggConfig.AccountConfig.BasicEnforcementVendorsMap = map[string]struct{}{string(td.bidder): {}} } perms.cfg = &tcf2AggConfig + perms.aliasGVLIDs = td.aliasGVLIDs + perms.consent = td.consent + perms.purposeEnforcerBuilder = NewPurposeEnforcerBuilder(&tcf2AggConfig) - permissions, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidderCoreName, td.bidder, "", SignalYes, td.consent, td.aliasGVLIDs) + permissions, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidderCoreName, td.bidder) assert.NoErrorf(t, err, "Error processing AuctionActivitiesAllowed for %s", td.description) assert.EqualValuesf(t, td.allowBidRequest, permissions.AllowBidRequest, "AllowBid failure on %s", td.description) assert.EqualValuesf(t, td.passGeo, permissions.PassGeo, "PassGeo failure on %s", td.description) @@ -557,6 +554,9 @@ func TestAllowActivitiesGeoAndID(t *testing.T) { } func TestAllowActivitiesWhitelist(t *testing.T) { + // user specifies consent and LI for all purposes, and purpose and LI vendor consent for vendors 2, 6 and 8 + const fullConsentToPurposesAndVendorsTwoSixEight = "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA" + vendorListData := MarshalVendorList(buildVendorList34()) tcf2AggConfig := allPurposesEnabledTCF2Config() @@ -572,10 +572,15 @@ func TestAllowActivitiesWhitelist(t *testing.T) { fetchVendorList: listFetcher(map[uint16]vendorlist.VendorList{ 34: parseVendorListDataV2(t, vendorListData), }), + purposeEnforcerBuilder: NewPurposeEnforcerBuilder(&tcf2AggConfig), + aliasGVLIDs: map[string]uint16{}, + consent: fullConsentToPurposesAndVendorsTwoSixEight, + gdprSignal: SignalYes, + publisherID: "appNexusAppID", } // Assert that an item that otherwise would not be allowed PI access, gets approved because it is found in the GDPR.NonStandardPublishers array - permissions, err := perms.AuctionActivitiesAllowed(context.Background(), openrtb_ext.BidderAppnexus, openrtb_ext.BidderAppnexus, "appNexusAppID", SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", map[string]uint16{}) + permissions, err := perms.AuctionActivitiesAllowed(context.Background(), openrtb_ext.BidderAppnexus, openrtb_ext.BidderAppnexus) assert.NoErrorf(t, err, "Error processing AuctionActivitiesAllowed") assert.EqualValuesf(t, true, permissions.PassGeo, "PassGeo failure") assert.EqualValuesf(t, true, permissions.PassID, "PassID failure") @@ -596,6 +601,8 @@ func TestAllowActivitiesPubRestrict(t *testing.T) { fetchVendorList: listFetcher(map[uint16]vendorlist.VendorList{ 15: parseVendorListDataV2(t, vendorListData), }), + purposeEnforcerBuilder: NewPurposeEnforcerBuilder(&tcf2AggConfig), + gdprSignal: SignalYes, } // COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA - vendors 1-10 legit interest only, @@ -640,7 +647,10 @@ func TestAllowActivitiesPubRestrict(t *testing.T) { } for _, td := range testDefs { - permissions, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidderCoreName, td.bidder, "", SignalYes, td.consent, td.aliasGVLIDs) + perms.aliasGVLIDs = td.aliasGVLIDs + perms.consent = td.consent + + permissions, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidderCoreName, td.bidder) assert.NoErrorf(t, err, "Error processing AuctionActivitiesAllowed for %s", td.description) assert.EqualValuesf(t, td.passGeo, permissions.PassGeo, "PassGeo failure on %s", td.description) assert.EqualValuesf(t, td.passID, permissions.PassID, "PassID failure on %s", td.description) @@ -648,6 +658,8 @@ func TestAllowActivitiesPubRestrict(t *testing.T) { } func TestAllowSync(t *testing.T) { + const fullConsentToPurposesAndVendorsTwoSixEight = "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA" + vendorListData := MarshalVendorList(buildVendorList34()) tcf2AggConfig := allPurposesEnabledTCF2Config() @@ -662,19 +674,23 @@ func TestAllowSync(t *testing.T) { fetchVendorList: listFetcher(map[uint16]vendorlist.VendorList{ 34: parseVendorListDataV2(t, vendorListData), }), + purposeEnforcerBuilder: NewPurposeEnforcerBuilder(&tcf2AggConfig), + gdprSignal: SignalYes, + consent: fullConsentToPurposesAndVendorsTwoSixEight, } - // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : full consensts to purposes and vendors 2, 6, 8 - allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") + allowSync, err := perms.HostCookiesAllowed(context.Background()) assert.NoErrorf(t, err, "Error processing HostCookiesAllowed") assert.EqualValuesf(t, true, allowSync, "HostCookiesAllowed failure") - allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderRubicon, SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") + allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderRubicon) assert.NoErrorf(t, err, "Error processing BidderSyncAllowed") assert.EqualValuesf(t, true, allowSync, "BidderSyncAllowed failure") } func TestProhibitedPurposeSync(t *testing.T) { + const fullConsentToPurposesAndVendorsTwoSixEight = "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA" + vendorList34 := buildVendorList34() vendorList34.Vendors["8"].Purposes = []int{7} vendorListData := MarshalVendorList(vendorList34) @@ -692,19 +708,23 @@ func TestProhibitedPurposeSync(t *testing.T) { fetchVendorList: listFetcher(map[uint16]vendorlist.VendorList{ 34: parseVendorListDataV2(t, vendorListData), }), + purposeEnforcerBuilder: NewPurposeEnforcerBuilder(&tcf2AggConfig), + gdprSignal: SignalYes, + consent: fullConsentToPurposesAndVendorsTwoSixEight, } - // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : full consents to purposes for vendors 2, 6, 8 - allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") + allowSync, err := perms.HostCookiesAllowed(context.Background()) assert.NoErrorf(t, err, "Error processing HostCookiesAllowed") assert.EqualValuesf(t, false, allowSync, "HostCookiesAllowed failure") - allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderRubicon, SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") + allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderRubicon) assert.NoErrorf(t, err, "Error processing BidderSyncAllowed") assert.EqualValuesf(t, false, allowSync, "BidderSyncAllowed failure") } func TestProhibitedVendorSync(t *testing.T) { + const fullConsentToPurposesAndVendorsTwoSixEight = "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA" + vendorListData := MarshalVendorList(buildVendorList34()) tcf2AggConfig := allPurposesEnabledTCF2Config() @@ -720,15 +740,18 @@ func TestProhibitedVendorSync(t *testing.T) { fetchVendorList: listFetcher(map[uint16]vendorlist.VendorList{ 34: parseVendorListDataV2(t, vendorListData), }), + purposeEnforcerBuilder: NewPurposeEnforcerBuilder(&tcf2AggConfig), + gdprSignal: SignalYes, + consent: fullConsentToPurposesAndVendorsTwoSixEight, } // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : full consents to purposes for vendors 2, 6, 8 - allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") + allowSync, err := perms.HostCookiesAllowed(context.Background()) assert.NoErrorf(t, err, "Error processing HostCookiesAllowed") assert.EqualValuesf(t, false, allowSync, "HostCookiesAllowed failure") // Permission disallowed due to consent string not including vendor 10. - allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderOpenx, SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") + allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderOpenx) assert.NoErrorf(t, err, "Error processing BidderSyncAllowed") assert.EqualValuesf(t, false, allowSync, "BidderSyncAllowed failure") } @@ -790,7 +813,7 @@ func TestAllowActivitiesBidRequests(t *testing.T) { testDefs := []struct { description string - purpose2EnforcePurpose string + purpose2EnforcePurpose bool purpose2EnforceVendors bool bidder openrtb_ext.BidderName bidderCoreName openrtb_ext.BidderName @@ -802,7 +825,7 @@ func TestAllowActivitiesBidRequests(t *testing.T) { }{ { description: "Bid blocked - p2 enabled, user consents to p2 but not vendor, vendor consents to p2", - purpose2EnforcePurpose: config.TCF2FullEnforcement, + purpose2EnforcePurpose: true, purpose2EnforceVendors: true, bidder: openrtb_ext.BidderPubmatic, bidderCoreName: openrtb_ext.BidderPubmatic, @@ -813,7 +836,7 @@ func TestAllowActivitiesBidRequests(t *testing.T) { }, { description: "Bid allowed - p2 enabled, user consents to p2 and vendor, alias vendor consents to p2", - purpose2EnforcePurpose: config.TCF2FullEnforcement, + purpose2EnforcePurpose: true, purpose2EnforceVendors: true, bidder: "pubmatic1", bidderCoreName: openrtb_ext.BidderPubmatic, @@ -825,7 +848,7 @@ func TestAllowActivitiesBidRequests(t *testing.T) { }, { description: "Bid blocked - p2 enabled, user consents to p2 and vendor, alias vendor does not consent to p2", - purpose2EnforcePurpose: config.TCF2FullEnforcement, + purpose2EnforcePurpose: true, purpose2EnforceVendors: true, bidder: "pubmatic1", bidderCoreName: openrtb_ext.BidderPubmatic, @@ -837,29 +860,51 @@ func TestAllowActivitiesBidRequests(t *testing.T) { }, { description: "Bid allowed - p2 enabled not enforcing vendors, user consents to p2 but not vendor, vendor consents to p2", - purpose2EnforcePurpose: config.TCF2FullEnforcement, + purpose2EnforcePurpose: true, purpose2EnforceVendors: false, bidder: openrtb_ext.BidderPubmatic, bidderCoreName: openrtb_ext.BidderPubmatic, consent: purpose2ConsentWithoutVendorConsent, allowBidRequest: true, passGeo: false, - passID: true, + passID: false, }, { - description: "Bid allowed - p2 disabled, user consents to p2 but not vendor, vendor consents to p2", - purpose2EnforcePurpose: config.TCF2NoEnforcement, + description: "Bid allowed - p2 disabled and enforcing vendors, user consents to p2 but not vendor, vendor consents to p2", + purpose2EnforcePurpose: false, purpose2EnforceVendors: true, bidder: openrtb_ext.BidderPubmatic, bidderCoreName: openrtb_ext.BidderPubmatic, consent: purpose2ConsentWithoutVendorConsent, + allowBidRequest: false, + passGeo: false, + passID: false, + }, + { + description: "Bid allowed - p2 disabled not enforcing vendors, user consents to p2 but not vendor, vendor consents to p2", + purpose2EnforcePurpose: false, + purpose2EnforceVendors: false, + bidder: openrtb_ext.BidderPubmatic, + bidderCoreName: openrtb_ext.BidderPubmatic, + consent: purpose2ConsentWithoutVendorConsent, allowBidRequest: true, passGeo: false, passID: false, }, + { + description: "Bid allowed - p2 disabled and enforcing vendors, user consents to p2 and vendor, vendor consents to p2", + purpose2EnforcePurpose: false, + purpose2EnforceVendors: true, + bidder: openrtb_ext.BidderPubmatic, + bidderCoreName: openrtb_ext.BidderPubmatic, + consent: purpose2AndVendorConsent, + allowBidRequest: true, + passGeo: false, + passID: true, + }, { description: "Bid allowed - p2 enabled, user consents to p2 and vendor, vendor consents to p2", - purpose2EnforcePurpose: config.TCF2FullEnforcement, + purpose2EnforcePurpose: true, purpose2EnforceVendors: true, bidder: openrtb_ext.BidderPubmatic, bidderCoreName: openrtb_ext.BidderPubmatic, @@ -870,7 +915,7 @@ func TestAllowActivitiesBidRequests(t *testing.T) { }, { description: "Bid blocked - p2 enabled, user consents to p2 LI but not vendor, vendor consents to p2", - purpose2EnforcePurpose: config.TCF2FullEnforcement, + purpose2EnforcePurpose: true, purpose2EnforceVendors: true, bidder: openrtb_ext.BidderRubicon, bidderCoreName: openrtb_ext.BidderRubicon, @@ -881,7 +926,7 @@ func TestAllowActivitiesBidRequests(t *testing.T) { }, { description: "Bid allowed - p2 enabled, user consents to p2 LI and vendor, vendor consents to p2", - purpose2EnforcePurpose: config.TCF2FullEnforcement, + purpose2EnforcePurpose: true, purpose2EnforceVendors: true, bidder: openrtb_ext.BidderRubicon, bidderCoreName: openrtb_ext.BidderRubicon, @@ -892,14 +937,14 @@ 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", - purpose2EnforcePurpose: config.TCF2FullEnforcement, + purpose2EnforcePurpose: true, purpose2EnforceVendors: false, bidder: openrtb_ext.BidderPubmatic, bidderCoreName: openrtb_ext.BidderPubmatic, consent: purpose2AndVendorLI, allowBidRequest: true, passGeo: false, - passID: true, + passID: false, }, } @@ -915,6 +960,9 @@ func TestAllowActivitiesBidRequests(t *testing.T) { fetchVendorList: listFetcher(map[uint16]vendorlist.VendorList{ 34: parseVendorListDataV2(t, vendorListData), }), + aliasGVLIDs: td.aliasGVLIDs, + consent: td.consent, + gdprSignal: SignalYes, } tcf2AggConfig := allPurposesEnabledTCF2Config() @@ -926,8 +974,9 @@ func TestAllowActivitiesBidRequests(t *testing.T) { tcf2AggConfig.HostConfig.PurposeConfigs[consentconstants.Purpose(2)] = p2Config tcf2AggConfig.HostConfig.PurposeConfigs[consentconstants.Purpose(2)] = &tcf2AggConfig.HostConfig.Purpose2 perms.cfg = &tcf2AggConfig + perms.purposeEnforcerBuilder = NewPurposeEnforcerBuilder(&tcf2AggConfig) - permissions, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidderCoreName, td.bidder, "", SignalYes, td.consent, td.aliasGVLIDs) + permissions, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidderCoreName, td.bidder) assert.NoErrorf(t, err, "Error processing AuctionActivitiesAllowed for %s", td.description) assert.EqualValuesf(t, td.allowBidRequest, permissions.AllowBidRequest, "AllowBid failure on %s", td.description) assert.EqualValuesf(t, td.passGeo, permissions.PassGeo, "PassGeo failure on %s", td.description) @@ -944,13 +993,16 @@ func TestTCF1Consent(t *testing.T) { vendorIDs: map[openrtb_ext.BidderName]uint16{ openrtb_ext.BidderAppnexus: 2, }, + aliasGVLIDs: map[string]uint16{}, + consent: tcf1Consent, + gdprSignal: SignalYes, } - permissions, err := perms.AuctionActivitiesAllowed(context.Background(), bidderAllowedByConsent, bidderAllowedByConsent, "", SignalYes, tcf1Consent, map[string]uint16{}) + permissions, err := perms.AuctionActivitiesAllowed(context.Background(), bidderAllowedByConsent, bidderAllowedByConsent) assert.Nil(t, err, "TCF1 consent - no error returned") - assert.Equal(t, false, permissions.AllowBidRequest, "TCF1 consent - bid request not allowed") - assert.Equal(t, false, permissions.PassGeo, "TCF1 consent - passing geo not allowed") + assert.Equal(t, true, permissions.AllowBidRequest, "TCF1 consent - bid request not allowed") + assert.Equal(t, true, permissions.PassGeo, "TCF1 consent - passing geo not allowed") assert.Equal(t, false, permissions.PassID, "TCF1 consent - passing id not allowed") } @@ -1024,6 +1076,9 @@ func TestAllowActivitiesVendorException(t *testing.T) { fetchVendorList: listFetcher(map[uint16]vendorlist.VendorList{ 34: parseVendorListDataV2(t, vendorListData), }), + aliasGVLIDs: map[string]uint16{}, + consent: td.consent, + gdprSignal: SignalYes, } tcf2AggConfig := allPurposesEnabledTCF2Config() @@ -1033,8 +1088,9 @@ func TestAllowActivitiesVendorException(t *testing.T) { tcf2AggConfig.HostConfig.PurposeConfigs[consentconstants.Purpose(2)] = &tcf2AggConfig.HostConfig.Purpose2 tcf2AggConfig.HostConfig.PurposeConfigs[consentconstants.Purpose(3)] = &tcf2AggConfig.HostConfig.Purpose3 perms.cfg = &tcf2AggConfig + perms.purposeEnforcerBuilder = NewPurposeEnforcerBuilder(&tcf2AggConfig) - permissions, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidderCoreName, td.bidder, "", SignalYes, td.consent, map[string]uint16{}) + permissions, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidderCoreName, td.bidder) assert.NoErrorf(t, err, "Error processing AuctionActivitiesAllowed for %s", td.description) assert.EqualValuesf(t, td.allowBidRequest, permissions.AllowBidRequest, "AllowBid failure on %s", td.description) assert.EqualValuesf(t, td.passGeo, permissions.PassGeo, "PassGeo failure on %s", td.description) @@ -1086,15 +1142,78 @@ func TestBidderSyncAllowedVendorException(t *testing.T) { fetchVendorList: listFetcher(map[uint16]vendorlist.VendorList{ 34: parseVendorListDataV2(t, vendorListData), }), + consent: td.consent, + gdprSignal: SignalYes, } tcf2AggConfig := allPurposesEnabledTCF2Config() tcf2AggConfig.HostConfig.Purpose1.VendorExceptionMap = td.p1VendorExceptionMap tcf2AggConfig.HostConfig.PurposeConfigs[consentconstants.Purpose(1)] = &tcf2AggConfig.HostConfig.Purpose1 perms.cfg = &tcf2AggConfig + perms.purposeEnforcerBuilder = NewPurposeEnforcerBuilder(&tcf2AggConfig) - allowSync, err := perms.BidderSyncAllowed(context.Background(), td.bidder, SignalYes, td.consent) + allowSync, err := perms.BidderSyncAllowed(context.Background(), td.bidder) assert.NoErrorf(t, err, "Error processing BidderSyncAllowed for %s", td.description) assert.EqualValuesf(t, td.allowSync, allowSync, "AllowSync failure on %s", td.description) } } + +func TestDefaultPermissions(t *testing.T) { + tests := []struct { + description string + purpose2Enforced bool + feature1Enforced bool + wantPermissions AuctionPermissions + }{ + { + description: "Neither enforced", + wantPermissions: AuctionPermissions{ + AllowBidRequest: true, + PassGeo: true, + PassID: false, + }, + }, + { + description: "Purpose 2 enforced only", + purpose2Enforced: true, + wantPermissions: AuctionPermissions{ + AllowBidRequest: false, + PassGeo: true, + PassID: false, + }, + }, + { + description: "Feature 1 enforced only", + feature1Enforced: true, + wantPermissions: AuctionPermissions{ + AllowBidRequest: true, + PassGeo: false, + PassID: false, + }, + }, + { + description: "Both enforced", + purpose2Enforced: true, + feature1Enforced: true, + wantPermissions: AuctionPermissions{ + AllowBidRequest: false, + PassGeo: false, + PassID: false, + }, + }, + } + + for _, tt := range tests { + perms := permissionsImpl{} + + tcf2AggConfig := allPurposesEnabledTCF2Config() + tcf2AggConfig.HostConfig.Purpose2.EnforcePurpose = tt.purpose2Enforced + tcf2AggConfig.HostConfig.SpecialFeature1.Enforce = tt.feature1Enforced + tcf2AggConfig.HostConfig.PurposeConfigs[consentconstants.Purpose(2)] = &tcf2AggConfig.HostConfig.Purpose2 + perms.cfg = &tcf2AggConfig + + result := perms.defaultPermissions() + + assert.Equal(t, result, tt.wantPermissions, tt.description) + } +} diff --git a/gdpr/purpose_config.go b/gdpr/purpose_config.go new file mode 100644 index 00000000000..015f23269ef --- /dev/null +++ b/gdpr/purpose_config.go @@ -0,0 +1,38 @@ +package gdpr + +import ( + "github.com/prebid/go-gdpr/consentconstants" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" +) + +// purposeConfig represents all of the config info selected from the host and account configs for +// a particular purpose needed to determine legal basis using one of the GDPR enforcement algorithms +type purposeConfig struct { + PurposeID consentconstants.Purpose + EnforceAlgo config.TCF2EnforcementAlgo + EnforcePurpose bool + EnforceVendors bool + VendorExceptionMap map[openrtb_ext.BidderName]struct{} + BasicEnforcementVendorsMap map[string]struct{} +} + +// basicEnforcementVendor returns true if a given bidder is configured as a basic enforcement vendor +// for the purpose +func (pc *purposeConfig) basicEnforcementVendor(bidder openrtb_ext.BidderName) bool { + if pc.BasicEnforcementVendorsMap == nil { + return false + } + _, found := pc.BasicEnforcementVendorsMap[string(bidder)] + return found +} + +// vendorException returns true if a given bidder is configured as a vendor exception +// for the purpose +func (pc *purposeConfig) vendorException(bidder openrtb_ext.BidderName) bool { + if pc.VendorExceptionMap == nil { + return false + } + _, found := pc.VendorExceptionMap[bidder] + return found +} diff --git a/gdpr/purpose_config_test.go b/gdpr/purpose_config_test.go new file mode 100644 index 00000000000..e80733cc8ca --- /dev/null +++ b/gdpr/purpose_config_test.go @@ -0,0 +1,145 @@ +package gdpr + +import ( + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" + + "github.com/stretchr/testify/assert" +) + +func TestPurposeConfigBasicEnforcementVendor(t *testing.T) { + tests := []struct { + description string + giveBasicVendors map[string]struct{} + giveBidder openrtb_ext.BidderName + wantFound bool + }{ + { + description: "vendor map is nil", + giveBasicVendors: nil, + giveBidder: openrtb_ext.BidderAppnexus, + wantFound: false, + }, + { + description: "vendor map is empty", + giveBasicVendors: map[string]struct{}{}, + giveBidder: openrtb_ext.BidderAppnexus, + wantFound: false, + }, + { + description: "vendor map has one bidders - bidder not found", + giveBasicVendors: map[string]struct{}{ + string(openrtb_ext.BidderPubmatic): {}, + }, + giveBidder: openrtb_ext.BidderAppnexus, + wantFound: false, + }, + { + description: "vendor map has one bidders - bidder found", + giveBasicVendors: map[string]struct{}{ + string(openrtb_ext.BidderAppnexus): {}, + }, + giveBidder: openrtb_ext.BidderAppnexus, + wantFound: true, + }, + { + description: "vendor map has many bidderss - bidder not found", + giveBasicVendors: map[string]struct{}{ + string(openrtb_ext.BidderIx): {}, + string(openrtb_ext.BidderPubmatic): {}, + string(openrtb_ext.BidderRubicon): {}, + }, + giveBidder: openrtb_ext.BidderAppnexus, + wantFound: false, + }, + { + description: "vendor map has many bidderss - bidder found", + giveBasicVendors: map[string]struct{}{ + string(openrtb_ext.BidderIx): {}, + string(openrtb_ext.BidderPubmatic): {}, + string(openrtb_ext.BidderAppnexus): {}, + string(openrtb_ext.BidderRubicon): {}, + }, + giveBidder: openrtb_ext.BidderAppnexus, + wantFound: true, + }, + } + + for _, tt := range tests { + cfg := purposeConfig{ + BasicEnforcementVendorsMap: tt.giveBasicVendors, + } + found := cfg.basicEnforcementVendor(tt.giveBidder) + + assert.Equal(t, tt.wantFound, found, tt.description) + } +} + +func TestPurposeConfigVendorException(t *testing.T) { + tests := []struct { + description string + giveExceptions map[openrtb_ext.BidderName]struct{} + giveBidder openrtb_ext.BidderName + wantFound bool + }{ + { + description: "vendor exception map is nil", + giveExceptions: nil, + giveBidder: openrtb_ext.BidderAppnexus, + wantFound: false, + }, + { + description: "vendor exception map is empty", + giveExceptions: map[openrtb_ext.BidderName]struct{}{}, + giveBidder: openrtb_ext.BidderAppnexus, + wantFound: false, + }, + { + description: "vendor exception map has one bidders - bidder not found", + giveExceptions: map[openrtb_ext.BidderName]struct{}{ + openrtb_ext.BidderPubmatic: {}, + }, + giveBidder: openrtb_ext.BidderAppnexus, + wantFound: false, + }, + { + description: "vendor exception map has one bidders - bidder found", + giveExceptions: map[openrtb_ext.BidderName]struct{}{ + openrtb_ext.BidderAppnexus: {}, + }, + giveBidder: openrtb_ext.BidderAppnexus, + wantFound: true, + }, + { + description: "vendor exception map has many bidderss - bidder not found", + giveExceptions: map[openrtb_ext.BidderName]struct{}{ + openrtb_ext.BidderIx: {}, + openrtb_ext.BidderPubmatic: {}, + openrtb_ext.BidderRubicon: {}, + }, + giveBidder: openrtb_ext.BidderAppnexus, + wantFound: false, + }, + { + description: "vendor exception map has many bidderss - bidder found", + giveExceptions: map[openrtb_ext.BidderName]struct{}{ + openrtb_ext.BidderIx: {}, + openrtb_ext.BidderPubmatic: {}, + openrtb_ext.BidderAppnexus: {}, + openrtb_ext.BidderRubicon: {}, + }, + giveBidder: openrtb_ext.BidderAppnexus, + wantFound: true, + }, + } + + for _, tt := range tests { + cfg := purposeConfig{ + VendorExceptionMap: tt.giveExceptions, + } + found := cfg.vendorException(tt.giveBidder) + + assert.Equal(t, tt.wantFound, found, tt.description) + } +} diff --git a/gdpr/purpose_enforcer.go b/gdpr/purpose_enforcer.go new file mode 100644 index 00000000000..c8e76f988aa --- /dev/null +++ b/gdpr/purpose_enforcer.go @@ -0,0 +1,111 @@ +package gdpr + +import ( + "github.com/prebid/go-gdpr/api" + "github.com/prebid/go-gdpr/consentconstants" + tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" +) + +// PurposeEnforcer represents the enforcement strategy for determining if legal basis is achieved for a purpose +type PurposeEnforcer interface { + LegalBasis(vendorInfo VendorInfo, bidder openrtb_ext.BidderName, consent tcf2.ConsentMetadata, overrides Overrides) bool +} + +// PurposeEnforcerBuilder generates an instance of PurposeEnforcer for a given purpose and bidder +type PurposeEnforcerBuilder func(p consentconstants.Purpose, bidder openrtb_ext.BidderName) PurposeEnforcer + +// Overrides specifies enforcement algorithm rule adjustments +type Overrides struct { + allowLITransparency bool + blockVendorExceptions bool + enforcePurpose bool + enforceVendors bool +} + +type BidderInfo struct { + bidderCoreName openrtb_ext.BidderName + bidder openrtb_ext.BidderName +} +type VendorInfo struct { + vendorID uint16 + vendor api.Vendor +} + +// PurposeEnforcers holds the full and basic enforcers for a purpose +type PurposeEnforcers struct { + Full PurposeEnforcer + Basic PurposeEnforcer +} + +// NewPurposeEnforcerBuilder creates a new instance of PurposeEnforcerBuilder. This function uses +// closures so that any enforcer generated by the returned builder may use the config and also be +// cached and reused within a request context +func NewPurposeEnforcerBuilder(cfg TCF2ConfigReader) PurposeEnforcerBuilder { + cachedEnforcers := make([]PurposeEnforcers, 10) + + return func(purpose consentconstants.Purpose, bidder openrtb_ext.BidderName) PurposeEnforcer { + index := purpose - 1 + + var basicEnforcementVendor bool + if purpose == consentconstants.Purpose(1) { + basicEnforcementVendor = false + } else { + basicEnforcementVendors := cfg.BasicEnforcementVendors() + _, basicEnforcementVendor = basicEnforcementVendors[string(bidder)] + } + + enforceAlgo := cfg.PurposeEnforcementAlgo(purpose) + downgraded := isDowngraded(enforceAlgo, basicEnforcementVendor) + + if enforceAlgo == config.TCF2BasicEnforcement || downgraded { + if cachedEnforcers[index].Basic != nil { + return cachedEnforcers[index].Basic + } + + purposeCfg := purposeConfig{ + PurposeID: purpose, + EnforceAlgo: enforceAlgo, + EnforcePurpose: cfg.PurposeEnforced(purpose), + EnforceVendors: cfg.PurposeEnforcingVendors(purpose), + VendorExceptionMap: cfg.PurposeVendorExceptions(purpose), + BasicEnforcementVendorsMap: cfg.BasicEnforcementVendors(), + } + + enforcer := &BasicEnforcement{ + cfg: purposeCfg, + } + cachedEnforcers[index].Basic = enforcer + return enforcer + } else { + if cachedEnforcers[index].Full != nil { + return cachedEnforcers[index].Full + } + + purposeCfg := purposeConfig{ + PurposeID: purpose, + EnforceAlgo: enforceAlgo, + EnforcePurpose: cfg.PurposeEnforced(purpose), + EnforceVendors: cfg.PurposeEnforcingVendors(purpose), + VendorExceptionMap: cfg.PurposeVendorExceptions(purpose), + BasicEnforcementVendorsMap: cfg.BasicEnforcementVendors(), + } + + enforcer := &FullEnforcement{ + cfg: purposeCfg, + } + cachedEnforcers[index].Full = enforcer + return enforcer + } + } +} + +// isDowngraded determines if the enforcement algorithm used to determine legal basis for a +// purpose should be downgraded from full enforcement to basic +func isDowngraded(enforceAlgo config.TCF2EnforcementAlgo, basicEnforcementVendor bool) bool { + if enforceAlgo == config.TCF2FullEnforcement && basicEnforcementVendor { + return true + } + return false +} diff --git a/gdpr/purpose_enforcer_test.go b/gdpr/purpose_enforcer_test.go new file mode 100644 index 00000000000..ea2075d9c65 --- /dev/null +++ b/gdpr/purpose_enforcer_test.go @@ -0,0 +1,237 @@ +package gdpr + +import ( + "testing" + + "github.com/prebid/go-gdpr/consentconstants" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" + + "github.com/stretchr/testify/assert" +) + +func TestNewPurposeEnforcerBuilder(t *testing.T) { + tests := []struct { + description string + enforceAlgo config.TCF2EnforcementAlgo + enforcePurpose bool + enforceVendors bool + basicVendorsMap map[string]struct{} + vendorExceptionMap map[openrtb_ext.BidderName]struct{} + purpose consentconstants.Purpose + bidder openrtb_ext.BidderName + wantType PurposeEnforcer + }{ + { + description: "purpose 1 full algo -- full enforcer returned", + enforceAlgo: config.TCF2FullEnforcement, + enforcePurpose: true, + enforceVendors: true, + basicVendorsMap: map[string]struct{}{}, + vendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + purpose: consentconstants.Purpose(1), + bidder: openrtb_ext.BidderAppnexus, + wantType: &FullEnforcement{}, + }, + { + description: "purpose 1 full algo, basic enforcement vendor -- full enforcer returned", + enforceAlgo: config.TCF2FullEnforcement, + enforcePurpose: true, + enforceVendors: true, + basicVendorsMap: map[string]struct{}{string(openrtb_ext.BidderAppnexus): {}}, + vendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + purpose: consentconstants.Purpose(1), + bidder: openrtb_ext.BidderAppnexus, + wantType: &FullEnforcement{}, + }, + { + description: "purpose 1 basic algo -- basic enforcer returned", + enforceAlgo: config.TCF2BasicEnforcement, + enforcePurpose: true, + enforceVendors: true, + basicVendorsMap: map[string]struct{}{}, + vendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + purpose: consentconstants.Purpose(1), + bidder: openrtb_ext.BidderAppnexus, + wantType: &BasicEnforcement{}, + }, + { + description: "purpose 2 full algo -- full enforcer returned", + enforceAlgo: config.TCF2FullEnforcement, + enforcePurpose: true, + enforceVendors: true, + basicVendorsMap: map[string]struct{}{}, + vendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + purpose: consentconstants.Purpose(2), + bidder: openrtb_ext.BidderAppnexus, + wantType: &FullEnforcement{}, + }, + { + description: "purpose 2 full algo, basic enforcement vendor -- basic enforcer returned", + enforceAlgo: config.TCF2FullEnforcement, + enforcePurpose: true, + enforceVendors: true, + basicVendorsMap: map[string]struct{}{string(openrtb_ext.BidderAppnexus): {}}, + vendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + purpose: consentconstants.Purpose(2), + bidder: openrtb_ext.BidderAppnexus, + wantType: &BasicEnforcement{}, + }, + { + description: "purpose 2 basic algo -- basic enforcer returned", + enforceAlgo: config.TCF2BasicEnforcement, + enforcePurpose: true, + enforceVendors: true, + basicVendorsMap: map[string]struct{}{}, + vendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + purpose: consentconstants.Purpose(2), + bidder: openrtb_ext.BidderAppnexus, + wantType: &BasicEnforcement{}, + }, + } + + for _, tt := range tests { + cfg := fakeTCF2ConfigReader{ + enforceAlgo: tt.enforceAlgo, + enforcePurpose: tt.enforcePurpose, + enforceVendors: tt.enforceVendors, + basicEnforcementVendorsMap: tt.basicVendorsMap, + vendorExceptionMap: tt.vendorExceptionMap, + } + + builder := NewPurposeEnforcerBuilder(&cfg) + + enforcer1 := builder(tt.purpose, tt.bidder) + enforcer2 := builder(tt.purpose, tt.bidder) + + // assert that enforcer1 and enforcer2 are same objects; enforcer2 pulled from cache + assert.Same(t, enforcer1, enforcer2, tt.description) + assert.IsType(t, tt.wantType, enforcer1, tt.description) + + // assert enforcer 1 config values are properly set + switch enforcer1.(type) { + case *FullEnforcement: + { + fullEnforcer := enforcer1.(*FullEnforcement) + assert.Equal(t, fullEnforcer.cfg.PurposeID, tt.purpose, tt.description) + assert.Equal(t, fullEnforcer.cfg.EnforceAlgo, tt.enforceAlgo, tt.description) + assert.Equal(t, fullEnforcer.cfg.EnforcePurpose, tt.enforcePurpose, tt.description) + assert.Equal(t, fullEnforcer.cfg.EnforceVendors, tt.enforceVendors, tt.description) + assert.Equal(t, fullEnforcer.cfg.BasicEnforcementVendorsMap, tt.basicVendorsMap, tt.description) + assert.Equal(t, fullEnforcer.cfg.VendorExceptionMap, tt.vendorExceptionMap, tt.description) + } + case PurposeEnforcer: + { + basicEnforcer := enforcer1.(*BasicEnforcement) + assert.Equal(t, basicEnforcer.cfg.PurposeID, tt.purpose, tt.description) + assert.Equal(t, basicEnforcer.cfg.EnforceAlgo, tt.enforceAlgo, tt.description) + assert.Equal(t, basicEnforcer.cfg.EnforcePurpose, tt.enforcePurpose, tt.description) + assert.Equal(t, basicEnforcer.cfg.EnforceVendors, tt.enforceVendors, tt.description) + assert.Equal(t, basicEnforcer.cfg.BasicEnforcementVendorsMap, tt.basicVendorsMap, tt.description) + assert.Equal(t, basicEnforcer.cfg.VendorExceptionMap, tt.vendorExceptionMap, tt.description) + } + } + } +} + +func TestNewPurposeEnforcerBuilderCaching(t *testing.T) { + + bidder1 := openrtb_ext.BidderAppnexus + bidder1Enforcers := make([]PurposeEnforcer, 11) + bidder2 := openrtb_ext.BidderIx + bidder2Enforcers := make([]PurposeEnforcer, 11) + bidder3 := openrtb_ext.BidderPubmatic + bidder3Enforcers := make([]PurposeEnforcer, 11) + bidder4 := openrtb_ext.BidderRubicon + bidder4Enforcers := make([]PurposeEnforcer, 11) + + cfg := fakeTCF2ConfigReader{ + enforceAlgo: config.TCF2FullEnforcement, + basicEnforcementVendorsMap: map[string]struct{}{ + string(bidder3): {}, + string(bidder4): {}, + }, + } + builder := NewPurposeEnforcerBuilder(&cfg) + + for i := 1; i <= 10; i++ { + bidder1Enforcers[i] = builder(consentconstants.Purpose(i), bidder1) + bidder2Enforcers[i] = builder(consentconstants.Purpose(i), bidder2) + bidder3Enforcers[i] = builder(consentconstants.Purpose(i), bidder3) + bidder4Enforcers[i] = builder(consentconstants.Purpose(i), bidder4) + } + + for i := 1; i <= 10; i++ { + if i == 1 { + assert.IsType(t, bidder1Enforcers[i], &FullEnforcement{}, "purpose 1 bidder 1 enforcer is full") + assert.IsType(t, bidder3Enforcers[i], &FullEnforcement{}, "purpose 1 bidder 3 enforcer is full") + + // verify cross-bidder enforcer objects for a given purpose are the same + assert.Same(t, bidder1Enforcers[i], bidder2Enforcers[i], "purpose 1 compare bidder 1 & 2 enforcers") + assert.Same(t, bidder2Enforcers[i], bidder3Enforcers[i], "purpose 1 compare bidder 2 & 3 enforcers") + assert.Same(t, bidder3Enforcers[i], bidder4Enforcers[i], "purpose 1 compare bidder 3 & 4 enforcers") + + // verify cross-purpose enforcer objects are different + assert.Equal(t, bidder1Enforcers[i].(*FullEnforcement).cfg.PurposeID, consentconstants.Purpose(i), "purpose 1 bidder 1 enforcer purpose check") + assert.Equal(t, bidder2Enforcers[i].(*FullEnforcement).cfg.PurposeID, consentconstants.Purpose(i), "purpose 1 bidder 2 enforcer purpose check") + assert.Equal(t, bidder3Enforcers[i].(*FullEnforcement).cfg.PurposeID, consentconstants.Purpose(i), "purpose 1 bidder 3 enforcer purpose check") + assert.Equal(t, bidder4Enforcers[i].(*FullEnforcement).cfg.PurposeID, consentconstants.Purpose(i), "purpose 1 bidder 4 enforcer purpose check") + } else { + assert.IsType(t, bidder1Enforcers[i], &FullEnforcement{}, "purpose %d bidder 1 enforcer is full", i) + assert.IsType(t, bidder3Enforcers[i], &BasicEnforcement{}, "purpose %d bidder 3 enforcer is basic", i) + + // verify some cross-bidder enforcer objects for a given purpose are the same and some are different + assert.Same(t, bidder1Enforcers[i], bidder2Enforcers[i], "purpose %d compare bidder 1 & 2 enforcers", i) + assert.NotSame(t, bidder2Enforcers[i], bidder3Enforcers[i], "purpose %d compare bidder 2 & 3 enforcers", i) + assert.Same(t, bidder3Enforcers[i], bidder4Enforcers[i], "purpose %d compare bidder 3 & 4 enforcers", i) + + // verify cross-purpose enforcer objects are different + assert.Equal(t, bidder1Enforcers[i].(*FullEnforcement).cfg.PurposeID, consentconstants.Purpose(i), "purpose %d bidder 1 enforcer purpose check", i) + assert.Equal(t, bidder2Enforcers[i].(*FullEnforcement).cfg.PurposeID, consentconstants.Purpose(i), "purpose %d bidder 2 enforcer purpose check", i) + assert.Equal(t, bidder3Enforcers[i].(*BasicEnforcement).cfg.PurposeID, consentconstants.Purpose(i), "purpose %d bidder 3 enforcer purpose check", i) + assert.Equal(t, bidder4Enforcers[i].(*BasicEnforcement).cfg.PurposeID, consentconstants.Purpose(i), "purpose %d bidder 4 enforcer purpose check", i) + } + } +} + +type fakeTCF2ConfigReader struct { + enforceAlgo config.TCF2EnforcementAlgo + enforcePurpose bool + enforceVendors bool + vendorExceptionMap map[openrtb_ext.BidderName]struct{} + basicEnforcementVendorsMap map[string]struct{} +} + +func (fcr *fakeTCF2ConfigReader) BasicEnforcementVendors() map[string]struct{} { + return fcr.basicEnforcementVendorsMap +} +func (fcr *fakeTCF2ConfigReader) FeatureOneEnforced() bool { + return false +} +func (fcr *fakeTCF2ConfigReader) FeatureOneVendorException(openrtb_ext.BidderName) bool { + return false +} +func (fcr *fakeTCF2ConfigReader) ChannelEnabled(config.ChannelType) bool { + return false +} +func (fcr *fakeTCF2ConfigReader) IsEnabled() bool { + return false +} +func (fcr *fakeTCF2ConfigReader) PurposeEnforced(purpose consentconstants.Purpose) bool { + return fcr.enforcePurpose +} +func (fcr *fakeTCF2ConfigReader) PurposeEnforcementAlgo(purpose consentconstants.Purpose) config.TCF2EnforcementAlgo { + return fcr.enforceAlgo +} +func (fcr *fakeTCF2ConfigReader) PurposeEnforcingVendors(purpose consentconstants.Purpose) bool { + return fcr.enforceVendors +} +func (fcr *fakeTCF2ConfigReader) PurposeVendorExceptions(purpose consentconstants.Purpose) map[openrtb_ext.BidderName]struct{} { + return fcr.vendorExceptionMap +} +func (fcr *fakeTCF2ConfigReader) PurposeOneTreatmentEnabled() bool { + return false +} +func (fcr *fakeTCF2ConfigReader) PurposeOneTreatmentAccessAllowed() bool { + return false +} diff --git a/gdpr/vendorlist-fetching.go b/gdpr/vendorlist-fetching.go index 51c41d227f0..864cf00f856 100644 --- a/gdpr/vendorlist-fetching.go +++ b/gdpr/vendorlist-fetching.go @@ -3,7 +3,7 @@ package gdpr import ( "context" "fmt" - "io/ioutil" + "io" "net/http" "strconv" "sync" @@ -117,7 +117,7 @@ func saveOne(ctx context.Context, client *http.Client, url string, saver saveVen } defer resp.Body.Close() - respBody, err := ioutil.ReadAll(resp.Body) + respBody, err := io.ReadAll(resp.Body) if err != nil { glog.Errorf("Error reading response body from GET %s. Cookie syncs may be affected: %v", url, err) return 0 diff --git a/go.mod b/go.mod index fe4519d88e1..40217a5043f 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/prebid/prebid-server -go 1.16 +go 1.19 // Magic comment that determines which Go version Heroku uses. // +heroku goVersion go1.16 @@ -9,34 +9,74 @@ replace github.com/prebid-server/adapters/playwire_ortb => ./adapters/playwire_o require ( github.com/DATA-DOG/go-sqlmock v1.5.0 + github.com/IABTechLab/adscert v0.34.0 github.com/NYTimes/gziphandler v1.1.1 github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d + github.com/benbjohnson/clock v1.3.0 github.com/buger/jsonparser v1.1.1 github.com/chasex/glog v0.0.0-20160217080310-c62392af379c - github.com/coocood/freecache v1.2.0 + github.com/coocood/freecache v1.2.1 github.com/docker/go-units v0.4.0 + github.com/go-sql-driver/mysql v1.6.0 github.com/gofrs/uuid v4.2.0+incompatible github.com/golang/glog v1.0.0 github.com/julienschmidt/httprouter v1.3.0 github.com/lib/pq v1.10.4 github.com/mitchellh/copystructure v1.2.0 github.com/mxmCherry/openrtb/v15 v15.0.1 + github.com/prebid-server/adapters/playwire_ortb v0.0.0-00010101000000-000000000000 github.com/prebid/go-gdpr v1.11.0 + github.com/prebid/go-gpp v0.1.1 + github.com/prebid/openrtb/v17 v17.1.0 github.com/prometheus/client_golang v1.12.1 github.com/prometheus/client_model v0.2.0 - github.com/prebid-server/adapters/playwire_ortb v0.0.0-00010101000000-000000000000 github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 github.com/rs/cors v1.8.2 - github.com/sergi/go-diff v1.2.0 // indirect - github.com/spf13/viper v1.11.0 - github.com/stretchr/testify v1.7.1 + github.com/spf13/viper v1.12.0 + github.com/stretchr/testify v1.8.1 github.com/vrischmann/go-metrics-influxdb v0.1.1 github.com/xeipuuv/gojsonschema v1.2.0 github.com/yudai/gojsondiff v1.0.0 - 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-20220420153159-1850ba15e1be + golang.org/x/net v0.0.0-20220909164309-bea034e7d591 golang.org/x/text v0.3.7 + google.golang.org/grpc v1.46.2 gopkg.in/evanphx/json-patch.v4 v4.12.0 - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/fsnotify/fsnotify v1.5.4 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d // indirect + github.com/magiconair/properties v1.8.6 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect + github.com/pelletier/go-toml/v2 v2.0.1 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/common v0.32.1 // indirect + github.com/prometheus/procfs v0.7.3 // indirect + github.com/sergi/go-diff v1.2.0 // indirect + github.com/spf13/afero v1.8.2 // indirect + github.com/spf13/cast v1.5.0 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/objx v0.5.0 // indirect + github.com/subosito/gotenv v1.3.0 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect + github.com/yudai/pp v2.0.1+incompatible // indirect + golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect + golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect + google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd // indirect + google.golang.org/protobuf v1.28.0 // indirect + gopkg.in/ini.v1 v1.66.4 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 368f9f50fa1..56926b400ac 100644 --- a/go.sum +++ b/go.sum @@ -27,6 +27,7 @@ cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aD cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM= cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= @@ -38,6 +39,8 @@ cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM7 cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= +cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= +cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= 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.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= @@ -57,9 +60,10 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/IABTechLab/adscert v0.34.0 h1:UNM2gMfRPGUbv3KDiLJmy2ajaVCfF3jWqgVKkz8wBu8= +github.com/IABTechLab/adscert v0.34.0/go.mod h1:pCLd3Up1kfTrH6kYFUGGeavxIc1f6Tvvj8yJeFRb7mA= 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.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 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= @@ -74,6 +78,9 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/aws/aws-sdk-go v1.36.29/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 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= @@ -82,6 +89,7 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB 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/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -102,16 +110,21 @@ github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XP github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/coocood/freecache v1.2.0 h1:p8RhjN6Y4DRBIMzdRlm1y+M7h7YJxye3lGW8/VvzCz0= -github.com/coocood/freecache v1.2.0/go.mod h1:OKrEjkGVoxZhyWAJoeFi5BMLUJm2Tit0kpGkIr7NGYY= +github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/coocood/freecache v1.2.1 h1:/v1CqMq45NFH9mp/Pt142reundeBM0dVUD3osQBeu/U= +github.com/coocood/freecache v1.2.1/go.mod h1:RBUWa/Cy+OHdfTGFEhEuE1pMCMX51Ncizj7rthiQ3vk= 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/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 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= @@ -120,14 +133,21 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= 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= @@ -135,11 +155,15 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2 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-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= 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-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 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/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= @@ -179,6 +203,7 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS 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/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 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= @@ -194,8 +219,9 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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= @@ -217,6 +243,7 @@ github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/tink/go v1.6.1/go.mod h1:IGW53kTgag+st5yPhKKwJ6u2l+SSp5/v9XF7spovjlY= 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= @@ -224,42 +251,64 @@ github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pf github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= +github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= +github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1/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-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= 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-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 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/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 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.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= +github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= +github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q= +github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= 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/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d h1:/WZQPMZNsjZ7IlCpsLGdQBINg5bxKQ1K1sh6awxLtkA= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 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.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -278,13 +327,17 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv 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/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 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/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -302,17 +355,24 @@ github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 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/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= 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.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -327,6 +387,7 @@ github.com/mxmCherry/openrtb/v15 v15.0.1/go.mod h1:TVgncsz6MOzbL7lhun1lNuUBzVBlV 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/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/onsi/ginkgo v1.6.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= @@ -337,14 +398,17 @@ github.com/onsi/gomega v1.11.0 h1:+CqWgvj0OZycCaqclBD1pxKHAU+tOkHmQIWvDHq2aug= github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.0.0-beta.8 h1:dy81yyLYJDwMTifq24Oi/IslOslRrDSb3jwDggjz3Z0= -github.com/pelletier/go-toml/v2 v2.0.0-beta.8/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU= +github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= 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 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 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/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -352,11 +416,16 @@ github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndr github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/prebid/go-gdpr v1.11.0 h1:QbMjscuw3Ul0mDVWeMy5tP0Kii6lmTSSVhV6fm8rY9s= github.com/prebid/go-gdpr v1.11.0/go.mod h1:mPZAdkRxn+iuSjaUuJAi9+0SppBOdM1PCzv/55UH3pY= +github.com/prebid/go-gpp v0.1.1 h1:uTMJ+eHmKWL9WvDuxFT4LDoOeJW1yOsfWITqi49ZuY0= +github.com/prebid/go-gpp v0.1.1/go.mod h1:b0TLoVln+HXFD9L9xeimxIH3FN8WDKPJ42auslxEkow= +github.com/prebid/openrtb/v17 v17.1.0 h1:sFdufdVv9zuoDLuo2/I863lSP9QlEqtZZQyDz5OXPhY= +github.com/prebid/openrtb/v17 v17.1.0/go.mod h1:nMj7j6aTIopCG91Wv3nuzcFTc7YRSOzuzdPxal+FY50= 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.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= @@ -382,41 +451,56 @@ github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5X github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/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/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sagikazarmark/crypt v0.5.0/go.mod h1:l+nzl7KWh51rpzp2h7t4MZWyiEWdhNpOAnclKvg+mdA= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= +github.com/sagikazarmark/crypt v0.6.0/go.mod h1:U8+INwJo3nBv1m6A/8OBXAq7Jnpspk5AxSgDyEQcea8= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 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/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= -github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= 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.11.0 h1:7OX/1FS6n7jHD1zGrZTM7WtY13ZELRyosK4k93oPr44= -github.com/spf13/viper v1.11.0/go.mod h1:djo0X/bA5+tYVoCn+C7cAYJGcVn/qYLFTG8gdUsX7Zk= +github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= +github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= +github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= 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/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 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/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI= +github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/vrischmann/go-metrics-influxdb v0.1.1 h1:xneKFRjsS4BiVYvAKaM/rOlXYd1pGHksnES0ECCJLgo= github.com/vrischmann/go-metrics-influxdb v0.1.1/go.mod h1:q7YC8bFETCYopXRMtUvQQdLaoVhpsEwvQS2zZEYCqg8= @@ -437,9 +521,13 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de 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.2/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= -go.etcd.io/etcd/client/pkg/v3 v3.5.2/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/v2 v2.305.2/go.mod h1:2D7ZejHVMIfog1221iLSYlQRzrtECw3kz4I4VAQm3qI= +go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= +go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs= +go.etcd.io/etcd/client/v2 v2.305.4/go.mod h1:Ud+VUwIi9/uQHOMA+4ekToJ12lTxlv0zB/+DHwTGEbU= +go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY= 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= @@ -452,14 +540,20 @@ 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= 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-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= 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/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 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= @@ -496,9 +590,11 @@ 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/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= 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-20181114220301-adae6a3d119a/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= @@ -540,13 +636,16 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220420153159-1850ba15e1be h1:yx80W7nvY5ySWpaU8UWaj5o9e23YgO9BRhQol7Lc+JI= -golang.org/x/net v0.0.0-20220420153159-1850ba15e1be/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220909164309-bea034e7d591 h1:D0B/7al0LLrVC8aWF4+oxpv/m8bc7ViFfVS8/gXGdqI= +golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 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= @@ -578,14 +677,18 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ 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/sync v0.0.0-20220513210516-0976fa681c29/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-20190129075346-302c3dd5f1cc/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-20190403152447-81d4e9dc473e/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= @@ -647,11 +750,13 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -659,13 +764,17 @@ golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 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.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 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= @@ -736,8 +845,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T 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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U= golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= 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= @@ -754,6 +863,7 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M 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.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= 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= @@ -769,11 +879,15 @@ google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqiv google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= +google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= +google.golang.org/api v0.81.0/go.mod h1:FA6Mb/bZxj706H2j+j2d6mHEEaHBmbbWnkfvmorOCko= 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= @@ -783,6 +897,7 @@ google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID 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-20190404172233-64821d5d2107/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= @@ -842,6 +957,8 @@ google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= @@ -853,9 +970,18 @@ google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2 google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd h1:e0TwkXOdbnH/1x5rc5MZ/VYyiZ4v+RdVfrGMqEwT68I= +google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= 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.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 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= @@ -879,8 +1005,12 @@ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.46.2 h1:u+MLGgVf7vRdjEYZ8wDFhAVNmhkbJ5hmrA1LMWK1CAQ= +google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= 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= @@ -898,6 +1028,7 @@ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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= @@ -906,8 +1037,10 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= 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= @@ -920,8 +1053,10 @@ 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/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= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/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= @@ -932,3 +1067,4 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 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= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/hooks/empty_plan.go b/hooks/empty_plan.go new file mode 100644 index 00000000000..01e01843324 --- /dev/null +++ b/hooks/empty_plan.go @@ -0,0 +1,38 @@ +package hooks + +import ( + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/hooks/hookstage" +) + +// EmptyPlanBuilder implements the ExecutionPlanBuilder interface +// and used as the stub when the hooks' functionality is disabled. +type EmptyPlanBuilder struct{} + +func (e EmptyPlanBuilder) PlanForEntrypointStage(endpoint string) Plan[hookstage.Entrypoint] { + return nil +} + +func (e EmptyPlanBuilder) PlanForRawAuctionStage(endpoint string, account *config.Account) Plan[hookstage.RawAuctionRequest] { + return nil +} + +func (e EmptyPlanBuilder) PlanForProcessedAuctionStage(endpoint string, account *config.Account) Plan[hookstage.ProcessedAuctionRequest] { + return nil +} + +func (e EmptyPlanBuilder) PlanForBidderRequestStage(endpoint string, account *config.Account) Plan[hookstage.BidderRequest] { + return nil +} + +func (e EmptyPlanBuilder) PlanForRawBidderResponseStage(endpoint string, account *config.Account) Plan[hookstage.RawBidderResponse] { + return nil +} + +func (e EmptyPlanBuilder) PlanForAllProcessedBidResponsesStage(endpoint string, account *config.Account) Plan[hookstage.AllProcessedBidResponses] { + return nil +} + +func (e EmptyPlanBuilder) PlanForAuctionResponseStage(endpoint string, account *config.Account) Plan[hookstage.AuctionResponse] { + return nil +} diff --git a/hooks/empty_plan_test.go b/hooks/empty_plan_test.go new file mode 100644 index 00000000000..4a512a9d0db --- /dev/null +++ b/hooks/empty_plan_test.go @@ -0,0 +1,21 @@ +package hooks + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEmptyPlanBuilder(t *testing.T) { + planBuilder := EmptyPlanBuilder{} + endpoint := "/openrtb2/auction" + message := "Empty plan builder should always return empty hook execution plan for %s stage." + + assert.Len(t, planBuilder.PlanForEntrypointStage(endpoint), 0, message, StageEntrypoint) + assert.Len(t, planBuilder.PlanForRawAuctionStage(endpoint, nil), 0, message, StageRawAuctionRequest) + assert.Len(t, planBuilder.PlanForProcessedAuctionStage(endpoint, nil), 0, message, StageProcessedAuctionRequest) + assert.Len(t, planBuilder.PlanForBidderRequestStage(endpoint, nil), 0, message, StageBidderRequest) + assert.Len(t, planBuilder.PlanForRawBidderResponseStage(endpoint, nil), 0, message, StageRawBidderResponse) + assert.Len(t, planBuilder.PlanForAllProcessedBidResponsesStage(endpoint, nil), 0, message, StageAllProcessedBidResponses) + assert.Len(t, planBuilder.PlanForAuctionResponseStage(endpoint, nil), 0, message, StageAuctionResponse) +} diff --git a/hooks/hookanalytics/analytics.go b/hooks/hookanalytics/analytics.go new file mode 100644 index 00000000000..f55141fb497 --- /dev/null +++ b/hooks/hookanalytics/analytics.go @@ -0,0 +1,45 @@ +// Package hookanalytics provides basic primitives for use by the hook modules. +// +// Structures of the package allow modules to provide information +// about what activity has been performed against the hook payload. +package hookanalytics + +type Analytics struct { + Activities []Activity `json:"activities"` +} + +type Activity struct { + Name string `json:"name"` + Status ActivityStatus `json:"status"` + Results []Result `json:"results,omitempty"` +} + +type ActivityStatus string + +const ( + ActivityStatusSuccess ActivityStatus = "success" + ActivityStatusError ActivityStatus = "error" +) + +type Result struct { + Status ResultStatus `json:"status,omitempty"` + Values map[string]interface{} `json:"values,omitempty"` + AppliedTo AppliedTo `json:"appliedto,omitempty"` +} + +type AppliedTo struct { + Bidders []string `json:"bidders,omitempty"` + BidIds []string `json:"bidids,omitempty"` + ImpIds []string `json:"impids,omitempty"` + Request bool `json:"request,omitempty"` + Response bool `json:"response,omitempty"` +} + +type ResultStatus string + +const ( + ResultStatusAllow ResultStatus = "success-allow" + ResultStatusBlock ResultStatus = "success-block" + ResultStatusModify ResultStatus = "success-modify" + ResultStatusError ResultStatus = "error" +) diff --git a/hooks/hookanalytics/analytics_test.go b/hooks/hookanalytics/analytics_test.go new file mode 100644 index 00000000000..177a9335da9 --- /dev/null +++ b/hooks/hookanalytics/analytics_test.go @@ -0,0 +1,56 @@ +package hookanalytics + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAnalytics(t *testing.T) { + expectedAnalytics := []byte(` +{ + "activities": [ + { + "name": "device-id", + "status": "success", + "results": [ + { + "status": "success-allow", + "values": { + "foo": "bar" + }, + "appliedto": { + "impids": [ + "impId1" + ], + "request": true + } + } + ] + }, + { + "name": "define-blocks", + "status": "error" + } + ] +} +`) + + result := Result{Status: ResultStatusAllow, Values: map[string]interface{}{"foo": "bar"}} + result.AppliedTo = AppliedTo{ImpIds: []string{"impId1"}, Request: true} + + activity := Activity{Name: "device-id", Status: ActivityStatusSuccess} + activity.Results = append(activity.Results, result) + + analytics := Analytics{} + analytics.Activities = append( + analytics.Activities, + activity, + Activity{Name: "define-blocks", Status: ActivityStatusError}, + ) + + gotAnalytics, err := json.Marshal(analytics) + assert.NoError(t, err, "Failed to marshal analytics: %s", err) + assert.JSONEq(t, string(expectedAnalytics), string(gotAnalytics)) +} diff --git a/hooks/hookexecution/context.go b/hooks/hookexecution/context.go new file mode 100644 index 00000000000..5f7cc3ab188 --- /dev/null +++ b/hooks/hookexecution/context.go @@ -0,0 +1,72 @@ +package hookexecution + +import ( + "sync" + + "github.com/golang/glog" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/hooks/hookstage" +) + +// executionContext holds information passed to module's hook during hook execution. +type executionContext struct { + endpoint string + stage string + accountId string + account *config.Account + moduleContexts *moduleContexts +} + +func (ctx executionContext) getModuleContext(moduleName string) hookstage.ModuleInvocationContext { + moduleInvocationCtx := hookstage.ModuleInvocationContext{Endpoint: ctx.endpoint} + if ctx.moduleContexts != nil { + if mc, ok := ctx.moduleContexts.get(moduleName); ok { + moduleInvocationCtx.ModuleContext = mc + } + } + + if ctx.account != nil { + cfg, err := ctx.account.Hooks.Modules.ModuleConfig(moduleName) + if err != nil { + glog.Warningf("Failed to get account config for %s module: %s", moduleName, err) + } + + moduleInvocationCtx.AccountConfig = cfg + } + + return moduleInvocationCtx +} + +// moduleContexts preserves data the module wants to pass to itself from earlier stages to later stages. +type moduleContexts struct { + sync.RWMutex + ctxs map[string]hookstage.ModuleContext // format: {"module_name": hookstage.ModuleContext} +} + +func (mc *moduleContexts) put(moduleName string, mCtx hookstage.ModuleContext) { + mc.Lock() + defer mc.Unlock() + + newCtx := mCtx + if existingCtx, ok := mc.ctxs[moduleName]; ok && existingCtx != nil { + for k, v := range mCtx { + existingCtx[k] = v + } + newCtx = existingCtx + } + mc.ctxs[moduleName] = newCtx +} + +func (mc *moduleContexts) get(moduleName string) (hookstage.ModuleContext, bool) { + mc.RLock() + defer mc.RUnlock() + mCtx, ok := mc.ctxs[moduleName] + + return mCtx, ok +} + +type stageModuleContext struct { + groupCtx []groupModuleContext +} + +type groupModuleContext map[string]hookstage.ModuleContext diff --git a/hooks/hookexecution/errors.go b/hooks/hookexecution/errors.go new file mode 100644 index 00000000000..62c6fb715b6 --- /dev/null +++ b/hooks/hookexecution/errors.go @@ -0,0 +1,65 @@ +package hookexecution + +import ( + "fmt" + + "github.com/prebid/prebid-server/errortypes" +) + +// TimeoutError indicates exceeding of the max execution time allotted for hook. +type TimeoutError struct{} + +func (e TimeoutError) Error() string { + return "Hook execution timeout" +} + +// FailureError indicates expected error occurred during hook execution on the module-side. +// A moduleFailed metric will be sent in such case. +type FailureError struct { + Message string +} + +func (e FailureError) Error() string { + return fmt.Sprintf("hook execution failed: %s", e.Message) +} + +// RejectError indicates stage rejection requested by specific hook. +// Implements errortypes.Coder interface for compatibility only, +// so as not to be recognized as a fatal error +type RejectError struct { + NBR int + Hook HookID + Stage string +} + +func (e RejectError) Code() int { + return errortypes.ModuleRejectionErrorCode +} + +func (e RejectError) Severity() errortypes.Severity { + return errortypes.SeverityWarning +} + +func (e RejectError) Error() string { + return fmt.Sprintf( + `Module %s (hook: %s) rejected request with code %d at %s stage`, + e.Hook.ModuleCode, + e.Hook.HookImplCode, + e.NBR, + e.Stage, + ) +} + +func FindFirstRejectOrNil(errors []error) *RejectError { + for _, err := range errors { + if rejectErr, ok := CastRejectErr(err); ok { + return rejectErr + } + } + return nil +} + +func CastRejectErr(err error) (*RejectError, bool) { + rejectErr, ok := err.(*RejectError) + return rejectErr, ok +} diff --git a/hooks/hookexecution/errors_test.go b/hooks/hookexecution/errors_test.go new file mode 100644 index 00000000000..444cad12167 --- /dev/null +++ b/hooks/hookexecution/errors_test.go @@ -0,0 +1,84 @@ +package hookexecution + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFindFirstRejectOrNil(t *testing.T) { + customError := errors.New("error message") + rejectError := &RejectError{NBR: 123} + + testCases := []struct { + description string + errs []error + expectedErr *RejectError + }{ + { + description: "Returns reject error", + errs: []error{rejectError}, + expectedErr: rejectError, + }, + { + description: "Finds reject error in slice of errors and returns it", + errs: []error{customError, rejectError}, + expectedErr: rejectError, + }, + { + description: "No reject error if there are no errors", + errs: []error{}, + expectedErr: nil, + }, + { + description: "No reject error if it not found", + errs: []error{customError}, + expectedErr: nil, + }, + } + + for _, test := range testCases { + t.Run(test.description, func(t *testing.T) { + result := FindFirstRejectOrNil(test.errs) + assert.Equal(t, test.expectedErr, result) + }) + } +} + +func TestCastRejectErr(t *testing.T) { + rejectError := &RejectError{NBR: 123} + testCases := []struct { + description string + err error + expectedErr *RejectError + expectedResult bool + }{ + { + description: "Returns reject error and true if reject error provided", + err: rejectError, + expectedErr: rejectError, + expectedResult: true, + }, + { + description: "Returns nil and false if no error provided", + err: nil, + expectedErr: nil, + expectedResult: false, + }, + { + description: "Returns nil and false if custom error type provided", + err: errors.New("error message"), + expectedErr: nil, + expectedResult: false, + }, + } + + for _, test := range testCases { + t.Run(test.description, func(t *testing.T) { + rejectErr, isRejectErr := CastRejectErr(test.err) + assert.Equal(t, test.expectedErr, rejectErr, "Invalid error returned.") + assert.Equal(t, test.expectedResult, isRejectErr, "Invalid casting result.") + }) + } +} diff --git a/hooks/hookexecution/execution.go b/hooks/hookexecution/execution.go new file mode 100644 index 00000000000..52790501b20 --- /dev/null +++ b/hooks/hookexecution/execution.go @@ -0,0 +1,312 @@ +package hookexecution + +import ( + "context" + "fmt" + "strings" + "sync" + "time" + + "github.com/prebid/prebid-server/hooks" + "github.com/prebid/prebid-server/hooks/hookstage" + "github.com/prebid/prebid-server/metrics" +) + +type hookResponse[T any] struct { + Err error + ExecutionTime time.Duration + HookID HookID + Result hookstage.HookResult[T] +} + +type hookHandler[H any, P any] func( + context.Context, + hookstage.ModuleInvocationContext, + H, + P, +) (hookstage.HookResult[P], error) + +func executeStage[H any, P any]( + executionCtx executionContext, + plan hooks.Plan[H], + payload P, + hookHandler hookHandler[H, P], + metricEngine metrics.MetricsEngine, +) (StageOutcome, P, stageModuleContext, *RejectError) { + stageOutcome := StageOutcome{} + stageOutcome.Groups = make([]GroupOutcome, 0, len(plan)) + stageModuleCtx := stageModuleContext{} + stageModuleCtx.groupCtx = make([]groupModuleContext, 0, len(plan)) + + for _, group := range plan { + groupOutcome, newPayload, moduleContexts, rejectErr := executeGroup(executionCtx, group, payload, hookHandler, metricEngine) + stageOutcome.ExecutionTimeMillis += groupOutcome.ExecutionTimeMillis + stageOutcome.Groups = append(stageOutcome.Groups, groupOutcome) + stageModuleCtx.groupCtx = append(stageModuleCtx.groupCtx, moduleContexts) + if rejectErr != nil { + return stageOutcome, payload, stageModuleCtx, rejectErr + } + + payload = newPayload + } + + return stageOutcome, payload, stageModuleCtx, nil +} + +func executeGroup[H any, P any]( + executionCtx executionContext, + group hooks.Group[H], + payload P, + hookHandler hookHandler[H, P], + metricEngine metrics.MetricsEngine, +) (GroupOutcome, P, groupModuleContext, *RejectError) { + var wg sync.WaitGroup + rejected := make(chan struct{}) + resp := make(chan hookResponse[P]) + + for _, hook := range group.Hooks { + mCtx := executionCtx.getModuleContext(hook.Module) + wg.Add(1) + go func(hw hooks.HookWrapper[H], moduleCtx hookstage.ModuleInvocationContext) { + defer wg.Done() + executeHook(moduleCtx, hw, payload, hookHandler, group.Timeout, resp, rejected) + }(hook, mCtx) + } + + go func() { + wg.Wait() + close(resp) + }() + + hookResponses := collectHookResponses(resp, rejected) + + return handleHookResponses(executionCtx, hookResponses, payload, metricEngine) +} + +func executeHook[H any, P any]( + moduleCtx hookstage.ModuleInvocationContext, + hw hooks.HookWrapper[H], + payload P, + hookHandler hookHandler[H, P], + timeout time.Duration, + resp chan<- hookResponse[P], + rejected <-chan struct{}, +) { + hookRespCh := make(chan hookResponse[P], 1) + startTime := time.Now() + hookId := HookID{ModuleCode: hw.Module, HookImplCode: hw.Code} + + go func() { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + result, err := hookHandler(ctx, moduleCtx, hw.Hook, payload) + hookRespCh <- hookResponse[P]{ + Result: result, + Err: err, + } + }() + + select { + case res := <-hookRespCh: + res.HookID = hookId + res.ExecutionTime = time.Since(startTime) + resp <- res + case <-time.After(timeout): + resp <- hookResponse[P]{ + Err: TimeoutError{}, + ExecutionTime: time.Since(startTime), + HookID: hookId, + Result: hookstage.HookResult[P]{}, + } + case <-rejected: + return + } +} + +func collectHookResponses[P any](resp <-chan hookResponse[P], rejected chan<- struct{}) []hookResponse[P] { + hookResponses := make([]hookResponse[P], 0) + for r := range resp { + hookResponses = append(hookResponses, r) + if r.Result.Reject { + close(rejected) + break + } + } + + return hookResponses +} + +func handleHookResponses[P any]( + executionCtx executionContext, + hookResponses []hookResponse[P], + payload P, + metricEngine metrics.MetricsEngine, +) (GroupOutcome, P, groupModuleContext, *RejectError) { + groupOutcome := GroupOutcome{} + groupOutcome.InvocationResults = make([]HookOutcome, 0, len(hookResponses)) + groupModuleCtx := make(groupModuleContext, len(hookResponses)) + + for _, r := range hookResponses { + groupModuleCtx[r.HookID.ModuleCode] = r.Result.ModuleContext + if r.ExecutionTime > groupOutcome.ExecutionTimeMillis { + groupOutcome.ExecutionTimeMillis = r.ExecutionTime + } + + updatedPayload, hookOutcome, rejectErr := handleHookResponse(executionCtx, payload, r, metricEngine) + groupOutcome.InvocationResults = append(groupOutcome.InvocationResults, hookOutcome) + payload = updatedPayload + + if rejectErr != nil { + return groupOutcome, payload, groupModuleCtx, rejectErr + } + } + + return groupOutcome, payload, groupModuleCtx, nil +} + +// handleHookResponse is a strategy function that selects and applies +// one of the available algorithms to handle hook response. +func handleHookResponse[P any]( + ctx executionContext, + payload P, + hr hookResponse[P], + metricEngine metrics.MetricsEngine, +) (P, HookOutcome, *RejectError) { + var rejectErr *RejectError + labels := metrics.ModuleLabels{Module: hr.HookID.ModuleCode, Stage: ctx.stage, AccountID: ctx.accountId} + metricEngine.RecordModuleCalled(labels, hr.ExecutionTime) + + hookOutcome := HookOutcome{ + Status: StatusSuccess, + HookID: hr.HookID, + Message: hr.Result.Message, + Errors: hr.Result.Errors, + Warnings: hr.Result.Warnings, + DebugMessages: hr.Result.DebugMessages, + AnalyticsTags: hr.Result.AnalyticsTags, + ExecutionTime: ExecutionTime{ExecutionTimeMillis: hr.ExecutionTime}, + } + + switch true { + case hr.Err != nil: + handleHookError(hr, &hookOutcome, metricEngine, labels) + case hr.Result.Reject: + rejectErr = handleHookReject(ctx, hr, &hookOutcome, metricEngine, labels) + default: + payload = handleHookMutations(payload, hr, &hookOutcome, metricEngine, labels) + } + + return payload, hookOutcome, rejectErr +} + +// handleHookError sets an appropriate status to HookOutcome depending on the type of hook execution error. +func handleHookError[P any]( + hr hookResponse[P], + hookOutcome *HookOutcome, + metricEngine metrics.MetricsEngine, + labels metrics.ModuleLabels, +) { + if hr.Err == nil { + return + } + + hookOutcome.Errors = append(hookOutcome.Errors, hr.Err.Error()) + switch hr.Err.(type) { + case TimeoutError: + metricEngine.RecordModuleTimeout(labels) + hookOutcome.Status = StatusTimeout + case FailureError: + metricEngine.RecordModuleFailed(labels) + hookOutcome.Status = StatusFailure + default: + metricEngine.RecordModuleExecutionError(labels) + hookOutcome.Status = StatusExecutionFailure + } +} + +// handleHookReject rejects execution at the current stage. +// In case the stage does not support rejection, hook execution marked as failed. +func handleHookReject[P any]( + ctx executionContext, + hr hookResponse[P], + hookOutcome *HookOutcome, + metricEngine metrics.MetricsEngine, + labels metrics.ModuleLabels, +) *RejectError { + if !hr.Result.Reject { + return nil + } + + stage := hooks.Stage(ctx.stage) + if !stage.IsRejectable() { + metricEngine.RecordModuleExecutionError(labels) + hookOutcome.Status = StatusExecutionFailure + hookOutcome.Errors = append( + hookOutcome.Errors, + fmt.Sprintf( + "Module (name: %s, hook code: %s) tried to reject request on the %s stage that does not support rejection", + hr.HookID.ModuleCode, + hr.HookID.HookImplCode, + ctx.stage, + ), + ) + return nil + } + + rejectErr := &RejectError{NBR: hr.Result.NbrCode, Hook: hr.HookID, Stage: ctx.stage} + hookOutcome.Action = ActionReject + hookOutcome.Errors = append(hookOutcome.Errors, rejectErr.Error()) + metricEngine.RecordModuleSuccessRejected(labels) + + return rejectErr +} + +// handleHookMutations applies mutations returned by hook to provided payload. +func handleHookMutations[P any]( + payload P, + hr hookResponse[P], + hookOutcome *HookOutcome, + metricEngine metrics.MetricsEngine, + labels metrics.ModuleLabels, +) P { + if hr.Result.ChangeSet == nil || len(hr.Result.ChangeSet.Mutations()) == 0 { + metricEngine.RecordModuleSuccessNooped(labels) + hookOutcome.Action = ActionNone + return payload + } + + hookOutcome.Action = ActionUpdate + successfulMutations := 0 + for _, mut := range hr.Result.ChangeSet.Mutations() { + p, err := mut.Apply(payload) + if err != nil { + hookOutcome.Warnings = append( + hookOutcome.Warnings, + fmt.Sprintf("failed to apply hook mutation: %s", err), + ) + continue + } + + payload = p + hookOutcome.DebugMessages = append( + hookOutcome.DebugMessages, + fmt.Sprintf( + "Hook mutation successfully applied, affected key: %s, mutation type: %s", + strings.Join(mut.Key(), "."), + mut.Type(), + ), + ) + successfulMutations++ + } + + // if at least one mutation from a given module was successfully applied + // we consider that the module was processed successfully + if successfulMutations > 0 { + metricEngine.RecordModuleSuccessUpdated(labels) + } else { + hookOutcome.Status = StatusExecutionFailure + metricEngine.RecordModuleExecutionError(labels) + } + + return payload +} diff --git a/hooks/hookexecution/executor.go b/hooks/hookexecution/executor.go new file mode 100644 index 00000000000..bdc99c3d808 --- /dev/null +++ b/hooks/hookexecution/executor.go @@ -0,0 +1,206 @@ +package hookexecution + +import ( + "context" + "net/http" + "sync" + + "github.com/prebid/openrtb/v17/openrtb2" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/hooks" + "github.com/prebid/prebid-server/hooks/hookstage" + "github.com/prebid/prebid-server/metrics" +) + +const ( + EndpointAuction = "/openrtb2/auction" + EndpointAmp = "/openrtb2/amp" +) + +// An entity specifies the type of object that was processed during the execution of the stage. +type entity string + +const ( + entityHttpRequest entity = "http-request" + entityAuctionRequest entity = "auction-request" + entityAuctionResponse entity = "auction_response" + entityAllProcessedBidResponses entity = "all_processed_bid_responses" +) + +type StageExecutor interface { + ExecuteEntrypointStage(req *http.Request, body []byte) ([]byte, *RejectError) + ExecuteRawAuctionStage(body []byte) ([]byte, *RejectError) + ExecuteProcessedAuctionStage(req *openrtb2.BidRequest) *RejectError +} + +type HookStageExecutor interface { + StageExecutor + SetAccount(account *config.Account) + GetOutcomes() []StageOutcome +} + +type hookExecutor struct { + account *config.Account + accountID string + endpoint string + planBuilder hooks.ExecutionPlanBuilder + stageOutcomes []StageOutcome + moduleContexts *moduleContexts + metricEngine metrics.MetricsEngine + // Mutex needed for BidderRequest and RawBidderResponse Stages as they are run in several goroutines + sync.Mutex +} + +func NewHookExecutor(builder hooks.ExecutionPlanBuilder, endpoint string, me metrics.MetricsEngine) *hookExecutor { + return &hookExecutor{ + endpoint: endpoint, + planBuilder: builder, + stageOutcomes: []StageOutcome{}, + moduleContexts: &moduleContexts{ctxs: make(map[string]hookstage.ModuleContext)}, + metricEngine: me, + } +} + +func (e *hookExecutor) SetAccount(account *config.Account) { + if account == nil { + return + } + + e.account = account + e.accountID = account.ID +} + +func (e *hookExecutor) GetOutcomes() []StageOutcome { + return e.stageOutcomes +} + +func (e *hookExecutor) ExecuteEntrypointStage(req *http.Request, body []byte) ([]byte, *RejectError) { + plan := e.planBuilder.PlanForEntrypointStage(e.endpoint) + if len(plan) == 0 { + return body, nil + } + + handler := func( + ctx context.Context, + moduleCtx hookstage.ModuleInvocationContext, + hook hookstage.Entrypoint, + payload hookstage.EntrypointPayload, + ) (hookstage.HookResult[hookstage.EntrypointPayload], error) { + return hook.HandleEntrypointHook(ctx, moduleCtx, payload) + } + + stageName := hooks.StageEntrypoint.String() + executionCtx := e.newContext(stageName) + payload := hookstage.EntrypointPayload{Request: req, Body: body} + + outcome, payload, contexts, rejectErr := executeStage(executionCtx, plan, payload, handler, e.metricEngine) + outcome.Entity = entityHttpRequest + outcome.Stage = stageName + + e.saveModuleContexts(contexts) + e.pushStageOutcome(outcome) + + return payload.Body, rejectErr +} + +func (e *hookExecutor) ExecuteRawAuctionStage(requestBody []byte) ([]byte, *RejectError) { + plan := e.planBuilder.PlanForRawAuctionStage(e.endpoint, e.account) + if len(plan) == 0 { + return requestBody, nil + } + + handler := func( + ctx context.Context, + moduleCtx hookstage.ModuleInvocationContext, + hook hookstage.RawAuctionRequest, + payload hookstage.RawAuctionRequestPayload, + ) (hookstage.HookResult[hookstage.RawAuctionRequestPayload], error) { + return hook.HandleRawAuctionHook(ctx, moduleCtx, payload) + } + + stageName := hooks.StageRawAuctionRequest.String() + executionCtx := e.newContext(stageName) + payload := hookstage.RawAuctionRequestPayload(requestBody) + + outcome, payload, contexts, reject := executeStage(executionCtx, plan, payload, handler, e.metricEngine) + outcome.Entity = entityAuctionRequest + outcome.Stage = stageName + + e.saveModuleContexts(contexts) + e.pushStageOutcome(outcome) + + return payload, reject +} + +func (e *hookExecutor) ExecuteProcessedAuctionStage(request *openrtb2.BidRequest) *RejectError { + plan := e.planBuilder.PlanForProcessedAuctionStage(e.endpoint, e.account) + if len(plan) == 0 { + return nil + } + + handler := func( + ctx context.Context, + moduleCtx hookstage.ModuleInvocationContext, + hook hookstage.ProcessedAuctionRequest, + payload hookstage.ProcessedAuctionRequestPayload, + ) (hookstage.HookResult[hookstage.ProcessedAuctionRequestPayload], error) { + return hook.HandleProcessedAuctionHook(ctx, moduleCtx, payload) + } + + stageName := hooks.StageProcessedAuctionRequest.String() + executionCtx := e.newContext(stageName) + payload := hookstage.ProcessedAuctionRequestPayload{BidRequest: request} + + outcome, _, contexts, reject := executeStage(executionCtx, plan, payload, handler, e.metricEngine) + outcome.Entity = entityAuctionRequest + outcome.Stage = stageName + + e.saveModuleContexts(contexts) + e.pushStageOutcome(outcome) + + return reject +} + +func (e *hookExecutor) newContext(stage string) executionContext { + return executionContext{ + account: e.account, + accountId: e.accountID, + endpoint: e.endpoint, + moduleContexts: e.moduleContexts, + stage: stage, + } +} + +func (e *hookExecutor) saveModuleContexts(ctxs stageModuleContext) { + for _, moduleCtxs := range ctxs.groupCtx { + for moduleName, moduleCtx := range moduleCtxs { + e.moduleContexts.put(moduleName, moduleCtx) + } + } +} + +func (e *hookExecutor) pushStageOutcome(outcome StageOutcome) { + e.Lock() + defer e.Unlock() + e.stageOutcomes = append(e.stageOutcomes, outcome) +} + +type EmptyHookExecutor struct{} + +func (executor *EmptyHookExecutor) SetAccount(_ *config.Account) {} + +func (executor *EmptyHookExecutor) GetOutcomes() []StageOutcome { + return []StageOutcome{} +} + +func (executor *EmptyHookExecutor) ExecuteEntrypointStage(_ *http.Request, body []byte) ([]byte, *RejectError) { + return body, nil +} + +func (executor *EmptyHookExecutor) ExecuteRawAuctionStage(body []byte) ([]byte, *RejectError) { + return body, nil +} + +func (executor *EmptyHookExecutor) ExecuteProcessedAuctionStage(_ *openrtb2.BidRequest) *RejectError { + return nil +} diff --git a/hooks/hookexecution/executor_test.go b/hooks/hookexecution/executor_test.go new file mode 100644 index 00000000000..a60e320f1a0 --- /dev/null +++ b/hooks/hookexecution/executor_test.go @@ -0,0 +1,1255 @@ +package hookexecution + +import ( + "bytes" + "fmt" + "net/http" + "net/url" + "testing" + "time" + + "github.com/prebid/openrtb/v17/openrtb2" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/hooks" + "github.com/prebid/prebid-server/hooks/hookanalytics" + "github.com/prebid/prebid-server/hooks/hookstage" + "github.com/prebid/prebid-server/metrics" + metricsConfig "github.com/prebid/prebid-server/metrics/config" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestEmptyHookExecutor(t *testing.T) { + executor := EmptyHookExecutor{} + executor.SetAccount(&config.Account{}) + + body := []byte(`{"foo": "bar"}`) + reader := bytes.NewReader(body) + req, err := http.NewRequest(http.MethodPost, "https://prebid.com/openrtb2/auction", reader) + assert.NoError(t, err, "Failed to create http request.") + + entrypointBody, entrypointRejectErr := executor.ExecuteEntrypointStage(req, body) + rawAuctionBody, rawAuctionRejectErr := executor.ExecuteRawAuctionStage(body) + processedAuctionRejectErr := executor.ExecuteProcessedAuctionStage(&openrtb2.BidRequest{}) + + outcomes := executor.GetOutcomes() + assert.Equal(t, EmptyHookExecutor{}, executor, "EmptyHookExecutor shouldn't be changed.") + assert.Empty(t, outcomes, "EmptyHookExecutor shouldn't return stage outcomes.") + + assert.Nil(t, entrypointRejectErr, "EmptyHookExecutor shouldn't return reject error at entrypoint stage.") + assert.Equal(t, body, entrypointBody, "EmptyHookExecutor shouldn't change body at entrypoint stage.") + + assert.Nil(t, rawAuctionRejectErr, "EmptyHookExecutor shouldn't return reject error at raw-auction stage.") + assert.Equal(t, body, rawAuctionBody, "EmptyHookExecutor shouldn't change body at raw-auction stage.") + + assert.Nil(t, processedAuctionRejectErr, "EmptyHookExecutor shouldn't return reject error at processed-auction stage.") +} + +func TestExecuteEntrypointStage(t *testing.T) { + const body string = `{"name": "John", "last_name": "Doe"}` + const urlString string = "https://prebid.com/openrtb2/auction" + + foobarModuleCtx := &moduleContexts{ctxs: map[string]hookstage.ModuleContext{"foobar": nil}} + + testCases := []struct { + description string + givenBody string + givenUrl string + givenPlanBuilder hooks.ExecutionPlanBuilder + expectedBody string + expectedHeader http.Header + expectedQuery url.Values + expectedReject *RejectError + expectedModuleContexts *moduleContexts + expectedStageOutcomes []StageOutcome + }{ + { + description: "Payload not changed if hook execution plan empty", + givenBody: body, + givenUrl: urlString, + givenPlanBuilder: hooks.EmptyPlanBuilder{}, + expectedBody: body, + expectedHeader: http.Header{}, + expectedQuery: url.Values{}, + expectedReject: nil, + expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{}}, + expectedStageOutcomes: []StageOutcome{}, + }, + { + description: "Payload changed if hooks return mutations", + givenBody: body, + givenUrl: urlString, + givenPlanBuilder: TestApplyHookMutationsBuilder{}, + expectedBody: `{"last_name": "Doe", "foo": "bar"}`, + expectedHeader: http.Header{"Foo": []string{"bar"}}, + expectedQuery: url.Values{"foo": []string{"baz"}}, + expectedReject: nil, + expectedModuleContexts: foobarModuleCtx, + expectedStageOutcomes: []StageOutcome{ + { + Entity: entityHttpRequest, + Stage: hooks.StageEntrypoint.String(), + Groups: []GroupOutcome{ + { + InvocationResults: []HookOutcome{ + { + AnalyticsTags: hookanalytics.Analytics{}, + HookID: HookID{ModuleCode: "foobar", HookImplCode: "foo"}, + Status: StatusSuccess, + Action: ActionUpdate, + Message: "", + DebugMessages: []string{fmt.Sprintf("Hook mutation successfully applied, affected key: header.foo, mutation type: %s", hookstage.MutationUpdate)}, + Errors: nil, + Warnings: nil, + }, + { + AnalyticsTags: hookanalytics.Analytics{}, + HookID: HookID{ModuleCode: "foobar", HookImplCode: "foobaz"}, + Status: StatusExecutionFailure, + Action: ActionUpdate, + Message: "", + DebugMessages: nil, + Errors: nil, + Warnings: []string{"failed to apply hook mutation: key not found"}, + }, + { + AnalyticsTags: hookanalytics.Analytics{}, + HookID: HookID{ModuleCode: "foobar", HookImplCode: "bar"}, + Status: StatusSuccess, + Action: ActionUpdate, + Message: "", + DebugMessages: []string{fmt.Sprintf("Hook mutation successfully applied, affected key: param.foo, mutation type: %s", hookstage.MutationUpdate)}, + Errors: nil, + Warnings: nil, + }, + }, + }, + { + InvocationResults: []HookOutcome{ + { + AnalyticsTags: hookanalytics.Analytics{}, + HookID: HookID{ModuleCode: "foobar", HookImplCode: "baz"}, + Status: StatusSuccess, + Action: ActionUpdate, + Message: "", + DebugMessages: []string{ + fmt.Sprintf("Hook mutation successfully applied, affected key: body.foo, mutation type: %s", hookstage.MutationUpdate), + fmt.Sprintf("Hook mutation successfully applied, affected key: body.name, mutation type: %s", hookstage.MutationDelete), + }, + Errors: nil, + Warnings: nil, + }, + { + AnalyticsTags: hookanalytics.Analytics{}, + HookID: HookID{ModuleCode: "foobar", HookImplCode: "foo"}, + Status: StatusFailure, + Action: "", + Message: "", + DebugMessages: nil, + Errors: []string{"hook execution failed: attribute not found"}, + Warnings: nil, + }, + }, + }, + }, + }, + }, + }, + { + description: "Stage execution can be rejected - and later hooks rejected", + givenBody: body, + givenUrl: urlString, + givenPlanBuilder: TestRejectPlanBuilder{}, + expectedBody: body, + expectedHeader: http.Header{"Foo": []string{"bar"}}, + expectedQuery: url.Values{}, + expectedReject: &RejectError{0, HookID{ModuleCode: "foobar", HookImplCode: "bar"}, hooks.StageEntrypoint.String()}, + expectedModuleContexts: foobarModuleCtx, + expectedStageOutcomes: []StageOutcome{ + { + Entity: entityHttpRequest, + Stage: hooks.StageEntrypoint.String(), + Groups: []GroupOutcome{ + { + InvocationResults: []HookOutcome{ + { + AnalyticsTags: hookanalytics.Analytics{}, + HookID: HookID{ModuleCode: "foobar", HookImplCode: "foo"}, + Status: StatusSuccess, + Action: ActionUpdate, + Message: "", + DebugMessages: []string{ + fmt.Sprintf("Hook mutation successfully applied, affected key: header.foo, mutation type: %s", hookstage.MutationUpdate), + }, + Errors: nil, + Warnings: nil, + }, + { + AnalyticsTags: hookanalytics.Analytics{}, + HookID: HookID{ModuleCode: "foobar", HookImplCode: "baz"}, + Status: StatusExecutionFailure, + Action: "", + Message: "", + DebugMessages: nil, + Errors: []string{"unexpected error"}, + Warnings: nil, + }, + }, + }, + { + InvocationResults: []HookOutcome{ + { + AnalyticsTags: hookanalytics.Analytics{}, + HookID: HookID{ModuleCode: "foobar", HookImplCode: "bar"}, + Status: StatusSuccess, + Action: ActionReject, + Message: "", + DebugMessages: nil, + Errors: []string{ + `Module foobar (hook: bar) rejected request with code 0 at entrypoint stage`, + }, + Warnings: nil, + }, + }, + }, + }, + }, + }, + }, + { + description: "Stage execution can be timed out", + givenBody: body, + givenUrl: urlString, + givenPlanBuilder: TestWithTimeoutPlanBuilder{}, + expectedBody: `{"foo":"bar", "last_name":"Doe"}`, + expectedHeader: http.Header{"Foo": []string{"bar"}}, + expectedQuery: url.Values{}, + expectedReject: nil, + expectedModuleContexts: foobarModuleCtx, + expectedStageOutcomes: []StageOutcome{ + { + Entity: entityHttpRequest, + Stage: hooks.StageEntrypoint.String(), + Groups: []GroupOutcome{ + { + InvocationResults: []HookOutcome{ + { + AnalyticsTags: hookanalytics.Analytics{}, + HookID: HookID{ModuleCode: "foobar", HookImplCode: "foo"}, + Status: StatusSuccess, + Action: ActionUpdate, + Message: "", + DebugMessages: []string{ + fmt.Sprintf("Hook mutation successfully applied, affected key: header.foo, mutation type: %s", hookstage.MutationUpdate), + }, + Errors: nil, + Warnings: nil, + }, + { + AnalyticsTags: hookanalytics.Analytics{}, + HookID: HookID{ModuleCode: "foobar", HookImplCode: "bar"}, + Status: StatusTimeout, + Action: "", + Message: "", + DebugMessages: nil, + Errors: []string{"Hook execution timeout"}, + Warnings: nil, + }, + }, + }, + { + InvocationResults: []HookOutcome{ + { + AnalyticsTags: hookanalytics.Analytics{}, + HookID: HookID{ModuleCode: "foobar", HookImplCode: "baz"}, + Status: StatusSuccess, + Action: ActionUpdate, + Message: "", + DebugMessages: []string{ + fmt.Sprintf("Hook mutation successfully applied, affected key: body.foo, mutation type: %s", hookstage.MutationUpdate), + fmt.Sprintf("Hook mutation successfully applied, affected key: body.name, mutation type: %s", hookstage.MutationDelete), + }, + Errors: nil, + Warnings: nil, + }, + }, + }, + }, + }, + }, + }, + { + description: "Modules contexts are preserved and correct", + givenBody: body, + givenUrl: urlString, + givenPlanBuilder: TestWithModuleContextsPlanBuilder{}, + expectedBody: body, + expectedHeader: http.Header{}, + expectedQuery: url.Values{}, + expectedReject: nil, + expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{ + "module-1": {"entrypoint-ctx-1": "some-ctx-1", "entrypoint-ctx-3": "some-ctx-3"}, + "module-2": {"entrypoint-ctx-2": "some-ctx-2"}, + }}, + expectedStageOutcomes: []StageOutcome{ + { + Entity: entityHttpRequest, + Stage: hooks.StageEntrypoint.String(), + Groups: []GroupOutcome{ + { + InvocationResults: []HookOutcome{ + { + AnalyticsTags: hookanalytics.Analytics{}, + HookID: HookID{ModuleCode: "module-1", HookImplCode: "foo"}, + Status: StatusSuccess, + Action: ActionNone, + Message: "", + DebugMessages: nil, + Errors: nil, + Warnings: nil, + }, + }, + }, + { + InvocationResults: []HookOutcome{ + { + AnalyticsTags: hookanalytics.Analytics{}, + HookID: HookID{ModuleCode: "module-2", HookImplCode: "bar"}, + Status: StatusSuccess, + Action: ActionNone, + Message: "", + DebugMessages: nil, + Errors: nil, + Warnings: nil, + }, + { + AnalyticsTags: hookanalytics.Analytics{}, + HookID: HookID{ModuleCode: "module-1", HookImplCode: "baz"}, + Status: StatusSuccess, + Action: ActionNone, + Message: "", + DebugMessages: nil, + Errors: nil, + Warnings: nil, + }, + }, + }, + }, + }, + }, + }, + } + + for _, test := range testCases { + t.Run(test.description, func(t *testing.T) { + body := []byte(test.givenBody) + reader := bytes.NewReader(body) + req, err := http.NewRequest(http.MethodPost, test.givenUrl, reader) + assert.NoError(t, err) + + exec := NewHookExecutor(test.givenPlanBuilder, EndpointAuction, &metricsConfig.NilMetricsEngine{}) + newBody, reject := exec.ExecuteEntrypointStage(req, body) + + assert.Equal(t, test.expectedReject, reject, "Unexpected stage reject.") + assert.JSONEq(t, test.expectedBody, string(newBody), "Incorrect request body.") + assert.Equal(t, test.expectedHeader, req.Header, "Incorrect request header.") + assert.Equal(t, test.expectedQuery, req.URL.Query(), "Incorrect request query.") + assert.Equal(t, test.expectedModuleContexts, exec.moduleContexts, "Incorrect module contexts") + + stageOutcomes := exec.GetOutcomes() + if len(test.expectedStageOutcomes) == 0 { + assert.Empty(t, stageOutcomes, "Incorrect stage outcomes.") + } else { + assertEqualStageOutcomes(t, test.expectedStageOutcomes[0], stageOutcomes[0]) + } + }) + } +} + +func TestMetricsAreGatheredDuringHookExecution(t *testing.T) { + reader := bytes.NewReader(nil) + req, err := http.NewRequest(http.MethodPost, "https://prebid.com/openrtb2/auction", reader) + assert.NoError(t, err) + + metricEngine := &metrics.MetricsEngineMock{} + builder := TestAllHookResultsBuilder{} + exec := NewHookExecutor(TestAllHookResultsBuilder{}, "/openrtb2/auction", metricEngine) + moduleLabels := metrics.ModuleLabels{ + Module: "module-1", + Stage: "entrypoint", + } + rTime := func(dur time.Duration) bool { return dur.Nanoseconds() > 0 } + plan := builder.PlanForEntrypointStage("") + hooksCalledDuringStage := 0 + for _, group := range plan { + for range group.Hooks { + hooksCalledDuringStage++ + } + } + metricEngine.On("RecordModuleCalled", moduleLabels, mock.MatchedBy(rTime)).Times(hooksCalledDuringStage) + metricEngine.On("RecordModuleSuccessUpdated", moduleLabels).Once() + metricEngine.On("RecordModuleSuccessRejected", moduleLabels).Once() + metricEngine.On("RecordModuleTimeout", moduleLabels).Once() + metricEngine.On("RecordModuleExecutionError", moduleLabels).Twice() + metricEngine.On("RecordModuleFailed", moduleLabels).Once() + metricEngine.On("RecordModuleSuccessNooped", moduleLabels).Once() + + _, _ = exec.ExecuteEntrypointStage(req, nil) + + // Assert that all module metrics funcs were called with the parameters we expected + metricEngine.AssertExpectations(t) +} + +func TestExecuteRawAuctionStage(t *testing.T) { + const body string = `{"name": "John", "last_name": "Doe"}` + const bodyUpdated string = `{"last_name": "Doe", "foo": "bar"}` + const urlString string = "https://prebid.com/openrtb2/auction" + + foobarModuleCtx := &moduleContexts{ctxs: map[string]hookstage.ModuleContext{"foobar": nil}} + account := &config.Account{} + + testCases := []struct { + description string + givenBody string + givenUrl string + givenPlanBuilder hooks.ExecutionPlanBuilder + givenAccount *config.Account + expectedBody string + expectedReject *RejectError + expectedModuleContexts *moduleContexts + expectedStageOutcomes []StageOutcome + }{ + { + description: "Payload not changed if hook execution plan empty", + givenBody: body, + givenUrl: urlString, + givenPlanBuilder: hooks.EmptyPlanBuilder{}, + givenAccount: account, + expectedBody: body, + expectedReject: nil, + expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{}}, + expectedStageOutcomes: []StageOutcome{}, + }, + { + description: "Payload changed if hooks return mutations", + givenBody: body, + givenUrl: urlString, + givenPlanBuilder: TestApplyHookMutationsBuilder{}, + givenAccount: account, + expectedBody: bodyUpdated, + expectedReject: nil, + expectedModuleContexts: foobarModuleCtx, + expectedStageOutcomes: []StageOutcome{ + { + Entity: entityAuctionRequest, + Stage: hooks.StageRawAuctionRequest.String(), + Groups: []GroupOutcome{ + { + InvocationResults: []HookOutcome{ + { + AnalyticsTags: hookanalytics.Analytics{}, + HookID: HookID{ModuleCode: "foobar", HookImplCode: "foo"}, + Status: StatusSuccess, + Action: ActionUpdate, + Message: "", + DebugMessages: []string{ + fmt.Sprintf("Hook mutation successfully applied, affected key: body.foo, mutation type: %s", hookstage.MutationUpdate), + fmt.Sprintf("Hook mutation successfully applied, affected key: body.name, mutation type: %s", hookstage.MutationDelete), + }, + Errors: nil, + Warnings: nil, + }, + { + AnalyticsTags: hookanalytics.Analytics{}, + HookID: HookID{ModuleCode: "foobar", HookImplCode: "bar"}, + Status: StatusExecutionFailure, + Action: ActionUpdate, + Message: "", + DebugMessages: nil, + Errors: nil, + Warnings: []string{"failed to apply hook mutation: key not found"}, + }, + }, + }, + { + InvocationResults: []HookOutcome{ + { + AnalyticsTags: hookanalytics.Analytics{}, + HookID: HookID{ModuleCode: "foobar", HookImplCode: "baz"}, + Status: StatusFailure, + Action: "", + Message: "", + DebugMessages: nil, + Errors: []string{"hook execution failed: attribute not found"}, + Warnings: nil, + }, + }, + }, + }, + }, + }, + }, + { + description: "Stage execution can be rejected - and later hooks rejected", + givenBody: body, + givenUrl: urlString, + givenPlanBuilder: TestRejectPlanBuilder{}, + givenAccount: nil, + expectedBody: bodyUpdated, + expectedReject: &RejectError{0, HookID{ModuleCode: "foobar", HookImplCode: "bar"}, hooks.StageRawAuctionRequest.String()}, + expectedModuleContexts: foobarModuleCtx, + expectedStageOutcomes: []StageOutcome{ + { + ExecutionTime: ExecutionTime{}, + Entity: entityAuctionRequest, + Stage: hooks.StageRawAuctionRequest.String(), + Groups: []GroupOutcome{ + { + ExecutionTime: ExecutionTime{}, + InvocationResults: []HookOutcome{ + { + ExecutionTime: ExecutionTime{}, + AnalyticsTags: hookanalytics.Analytics{}, + HookID: HookID{ModuleCode: "foobar", HookImplCode: "foo"}, + Status: StatusSuccess, + Action: ActionUpdate, + Message: "", + DebugMessages: []string{ + fmt.Sprintf("Hook mutation successfully applied, affected key: body.foo, mutation type: %s", hookstage.MutationUpdate), + fmt.Sprintf("Hook mutation successfully applied, affected key: body.name, mutation type: %s", hookstage.MutationDelete), + }, + Errors: nil, + Warnings: nil, + }, + { + ExecutionTime: ExecutionTime{}, + AnalyticsTags: hookanalytics.Analytics{}, + HookID: HookID{ModuleCode: "foobar", HookImplCode: "baz"}, + Status: StatusExecutionFailure, + Action: "", + Message: "", + DebugMessages: nil, + Errors: []string{"unexpected error"}, + Warnings: nil, + }, + }, + }, + { + ExecutionTime: ExecutionTime{}, + InvocationResults: []HookOutcome{ + { + ExecutionTime: ExecutionTime{}, + AnalyticsTags: hookanalytics.Analytics{}, + HookID: HookID{ModuleCode: "foobar", HookImplCode: "bar"}, + Status: StatusSuccess, + Action: ActionReject, + Message: "", + DebugMessages: nil, + Errors: []string{ + `Module foobar (hook: bar) rejected request with code 0 at raw_auction_request stage`, + }, + Warnings: nil, + }, + }, + }, + }, + }, + }, + }, + { + description: "Stage execution can be timed out", + givenBody: body, + givenUrl: urlString, + givenPlanBuilder: TestWithTimeoutPlanBuilder{}, + givenAccount: account, + expectedBody: bodyUpdated, + expectedReject: nil, + expectedModuleContexts: foobarModuleCtx, + expectedStageOutcomes: []StageOutcome{ + { + ExecutionTime: ExecutionTime{}, + Entity: entityAuctionRequest, + Stage: hooks.StageRawAuctionRequest.String(), + Groups: []GroupOutcome{ + { + ExecutionTime: ExecutionTime{}, + InvocationResults: []HookOutcome{ + { + ExecutionTime: ExecutionTime{}, + AnalyticsTags: hookanalytics.Analytics{}, + HookID: HookID{ModuleCode: "foobar", HookImplCode: "foo"}, + Status: StatusSuccess, + Action: ActionUpdate, + Message: "", + DebugMessages: []string{ + fmt.Sprintf("Hook mutation successfully applied, affected key: body.foo, mutation type: %s", hookstage.MutationUpdate), + fmt.Sprintf("Hook mutation successfully applied, affected key: body.name, mutation type: %s", hookstage.MutationDelete), + }, + Errors: nil, + Warnings: nil, + }, + }, + }, + { + ExecutionTime: ExecutionTime{}, + InvocationResults: []HookOutcome{ + { + ExecutionTime: ExecutionTime{}, + AnalyticsTags: hookanalytics.Analytics{}, + HookID: HookID{ModuleCode: "foobar", HookImplCode: "bar"}, + Status: StatusTimeout, + Action: "", + Message: "", + DebugMessages: nil, + Errors: []string{"Hook execution timeout"}, + Warnings: nil, + }, + }, + }, + }, + }, + }, + }, + { + description: "Modules contexts are preserved and correct", + givenBody: body, + givenUrl: urlString, + givenPlanBuilder: TestWithModuleContextsPlanBuilder{}, + givenAccount: account, + expectedBody: body, + expectedReject: nil, + expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{ + "module-1": {"raw-auction-ctx-1": "some-ctx-1", "raw-auction-ctx-3": "some-ctx-3"}, + "module-2": {"raw-auction-ctx-2": "some-ctx-2"}, + }}, + expectedStageOutcomes: []StageOutcome{ + { + ExecutionTime: ExecutionTime{}, + Entity: entityAuctionRequest, + Stage: hooks.StageRawAuctionRequest.String(), + Groups: []GroupOutcome{ + { + ExecutionTime: ExecutionTime{}, + InvocationResults: []HookOutcome{ + { + ExecutionTime: ExecutionTime{}, + AnalyticsTags: hookanalytics.Analytics{}, + HookID: HookID{ModuleCode: "module-1", HookImplCode: "foo"}, + Status: StatusSuccess, + Action: ActionNone, + Message: "", + DebugMessages: nil, + Errors: nil, + Warnings: nil, + }, + { + ExecutionTime: ExecutionTime{}, + AnalyticsTags: hookanalytics.Analytics{}, + HookID: HookID{ModuleCode: "module-2", HookImplCode: "baz"}, + Status: StatusSuccess, + Action: ActionNone, + Message: "", + DebugMessages: nil, + Errors: nil, + Warnings: nil, + }, + }, + }, + { + ExecutionTime: ExecutionTime{}, + InvocationResults: []HookOutcome{ + { + ExecutionTime: ExecutionTime{}, + AnalyticsTags: hookanalytics.Analytics{}, + HookID: HookID{ModuleCode: "module-1", HookImplCode: "bar"}, + Status: StatusSuccess, + Action: ActionNone, + Message: "", + DebugMessages: nil, + Errors: nil, + Warnings: nil, + }, + }, + }, + }, + }, + }, + }, + } + + for _, test := range testCases { + t.Run(test.description, func(t *testing.T) { + exec := NewHookExecutor(test.givenPlanBuilder, EndpointAuction, &metricsConfig.NilMetricsEngine{}) + exec.SetAccount(test.givenAccount) + + newBody, reject := exec.ExecuteRawAuctionStage([]byte(test.givenBody)) + + assert.Equal(t, test.expectedReject, reject, "Unexpected stage reject.") + assert.JSONEq(t, test.expectedBody, string(newBody), "Incorrect request body.") + assert.Equal(t, test.expectedModuleContexts, exec.moduleContexts, "Incorrect module contexts") + + stageOutcomes := exec.GetOutcomes() + if len(test.expectedStageOutcomes) == 0 { + assert.Empty(t, stageOutcomes, "Incorrect stage outcomes.") + } else { + assertEqualStageOutcomes(t, test.expectedStageOutcomes[0], stageOutcomes[0]) + } + }) + } +} + +func TestExecuteProcessedAuctionStage(t *testing.T) { + foobarModuleCtx := &moduleContexts{ctxs: map[string]hookstage.ModuleContext{"foobar": nil}} + account := &config.Account{} + req := openrtb2.BidRequest{ID: "some-id", User: &openrtb2.User{ID: "user-id"}} + reqUpdated := openrtb2.BidRequest{ID: "some-id", User: &openrtb2.User{ID: "user-id", Yob: 2000, Consent: "true"}} + + testCases := []struct { + description string + givenPlanBuilder hooks.ExecutionPlanBuilder + givenAccount *config.Account + givenRequest openrtb2.BidRequest + expectedRequest openrtb2.BidRequest + expectedReject *RejectError + expectedModuleContexts *moduleContexts + expectedStageOutcomes []StageOutcome + }{ + { + description: "Request not changed if hook execution plan empty", + givenPlanBuilder: hooks.EmptyPlanBuilder{}, + givenAccount: account, + givenRequest: req, + expectedRequest: req, + expectedReject: nil, + expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{}}, + expectedStageOutcomes: []StageOutcome{}, + }, + { + description: "Request changed if hooks return mutations", + givenPlanBuilder: TestApplyHookMutationsBuilder{}, + givenAccount: account, + givenRequest: req, + expectedRequest: reqUpdated, + expectedReject: nil, + expectedModuleContexts: foobarModuleCtx, + expectedStageOutcomes: []StageOutcome{ + { + Entity: entityAuctionRequest, + Stage: hooks.StageProcessedAuctionRequest.String(), + Groups: []GroupOutcome{ + { + InvocationResults: []HookOutcome{ + { + AnalyticsTags: hookanalytics.Analytics{}, + HookID: HookID{ModuleCode: "foobar", HookImplCode: "foo"}, + Status: StatusSuccess, + Action: ActionUpdate, + Message: "", + DebugMessages: []string{ + fmt.Sprintf("Hook mutation successfully applied, affected key: bidRequest.user.yob, mutation type: %s", hookstage.MutationUpdate), + fmt.Sprintf("Hook mutation successfully applied, affected key: bidRequest.user.consent, mutation type: %s", hookstage.MutationUpdate), + }, + Errors: nil, + Warnings: nil, + }, + }, + }, + }, + }, + }, + }, + { + description: "Stage execution can be rejected - and later hooks rejected", + givenPlanBuilder: TestRejectPlanBuilder{}, + givenAccount: nil, + givenRequest: req, + expectedRequest: req, + expectedReject: &RejectError{0, HookID{ModuleCode: "foobar", HookImplCode: "foo"}, hooks.StageProcessedAuctionRequest.String()}, + expectedModuleContexts: foobarModuleCtx, + expectedStageOutcomes: []StageOutcome{ + { + Entity: entityAuctionRequest, + Stage: hooks.StageProcessedAuctionRequest.String(), + Groups: []GroupOutcome{ + { + InvocationResults: []HookOutcome{ + { + AnalyticsTags: hookanalytics.Analytics{}, + HookID: HookID{ModuleCode: "foobar", HookImplCode: "foo"}, + Status: StatusSuccess, + Action: ActionReject, + Message: "", + DebugMessages: nil, + Errors: []string{ + `Module foobar (hook: foo) rejected request with code 0 at processed_auction_request stage`, + }, + Warnings: nil, + }, + }, + }, + }, + }, + }, + }, + { + description: "Request can be changed when a hook times out", + givenPlanBuilder: TestWithTimeoutPlanBuilder{}, + givenAccount: account, + givenRequest: req, + expectedRequest: reqUpdated, + expectedReject: nil, + expectedModuleContexts: foobarModuleCtx, + expectedStageOutcomes: []StageOutcome{ + { + Entity: entityAuctionRequest, + Stage: hooks.StageProcessedAuctionRequest.String(), + Groups: []GroupOutcome{ + { + InvocationResults: []HookOutcome{ + { + AnalyticsTags: hookanalytics.Analytics{}, + HookID: HookID{ModuleCode: "foobar", HookImplCode: "foo"}, + Status: StatusTimeout, + Action: "", + Message: "", + DebugMessages: nil, + Errors: []string{"Hook execution timeout"}, + Warnings: nil, + }, + }, + }, + { + InvocationResults: []HookOutcome{ + { + AnalyticsTags: hookanalytics.Analytics{}, + HookID: HookID{ModuleCode: "foobar", HookImplCode: "bar"}, + Status: StatusSuccess, + Action: ActionUpdate, + Message: "", + DebugMessages: []string{ + fmt.Sprintf("Hook mutation successfully applied, affected key: bidRequest.user.yob, mutation type: %s", hookstage.MutationUpdate), + fmt.Sprintf("Hook mutation successfully applied, affected key: bidRequest.user.consent, mutation type: %s", hookstage.MutationUpdate), + }, + Errors: nil, + Warnings: nil, + }, + }, + }, + }, + }, + }, + }, + { + description: "Modules contexts are preserved and correct", + givenPlanBuilder: TestWithModuleContextsPlanBuilder{}, + givenAccount: account, + givenRequest: req, + expectedRequest: req, + expectedReject: nil, + expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{ + "module-1": {"processed-auction-ctx-1": "some-ctx-1", "processed-auction-ctx-3": "some-ctx-3"}, + "module-2": {"processed-auction-ctx-2": "some-ctx-2"}, + }}, + expectedStageOutcomes: []StageOutcome{ + { + Entity: entityAuctionRequest, + Stage: hooks.StageProcessedAuctionRequest.String(), + Groups: []GroupOutcome{ + { + InvocationResults: []HookOutcome{ + { + AnalyticsTags: hookanalytics.Analytics{}, + HookID: HookID{ModuleCode: "module-1", HookImplCode: "foo"}, + Status: StatusSuccess, + Action: ActionNone, + Message: "", + DebugMessages: nil, + Errors: nil, + Warnings: nil, + }, + }, + }, + { + InvocationResults: []HookOutcome{ + { + AnalyticsTags: hookanalytics.Analytics{}, + HookID: HookID{ModuleCode: "module-2", HookImplCode: "bar"}, + Status: StatusSuccess, + Action: ActionNone, + Message: "", + DebugMessages: nil, + Errors: nil, + Warnings: nil, + }, + { + AnalyticsTags: hookanalytics.Analytics{}, + HookID: HookID{ModuleCode: "module-1", HookImplCode: "baz"}, + Status: StatusSuccess, + Action: ActionNone, + Message: "", + DebugMessages: nil, + Errors: nil, + Warnings: nil, + }, + }, + }, + }, + }, + }, + }, + } + + for _, test := range testCases { + t.Run(test.description, func(ti *testing.T) { + exec := NewHookExecutor(test.givenPlanBuilder, EndpointAuction, &metricsConfig.NilMetricsEngine{}) + exec.SetAccount(test.givenAccount) + + reject := exec.ExecuteProcessedAuctionStage(&test.givenRequest) + + assert.Equal(ti, test.expectedReject, reject, "Unexpected stage reject.") + assert.Equal(ti, test.expectedRequest, test.givenRequest, "Incorrect request update.") + assert.Equal(ti, test.expectedModuleContexts, exec.moduleContexts, "Incorrect module contexts") + + stageOutcomes := exec.GetOutcomes() + if len(test.expectedStageOutcomes) == 0 { + assert.Empty(ti, stageOutcomes, "Incorrect stage outcomes.") + } else { + assertEqualStageOutcomes(ti, test.expectedStageOutcomes[0], stageOutcomes[0]) + } + }) + } +} + +func TestInterStageContextCommunication(t *testing.T) { + body := []byte(`{"foo": "bar"}`) + reader := bytes.NewReader(body) + exec := NewHookExecutor(TestWithModuleContextsPlanBuilder{}, EndpointAuction, &metricsConfig.NilMetricsEngine{}) + req, err := http.NewRequest(http.MethodPost, "https://prebid.com/openrtb2/auction", reader) + assert.NoError(t, err) + + // test that context added at the entrypoint stage + _, reject := exec.ExecuteEntrypointStage(req, body) + assert.Nil(t, reject, "Unexpected reject from entrypoint stage.") + assert.Equal( + t, + &moduleContexts{ctxs: map[string]hookstage.ModuleContext{ + "module-1": { + "entrypoint-ctx-1": "some-ctx-1", + "entrypoint-ctx-3": "some-ctx-3", + }, + "module-2": {"entrypoint-ctx-2": "some-ctx-2"}, + }}, + exec.moduleContexts, + "Wrong module contexts after executing entrypoint hook.", + ) + + // test that context added at the raw-auction stage merged with existing module contexts + _, reject = exec.ExecuteRawAuctionStage(body) + assert.Nil(t, reject, "Unexpected reject from raw-auction stage.") + assert.Equal(t, &moduleContexts{ctxs: map[string]hookstage.ModuleContext{ + "module-1": { + "entrypoint-ctx-1": "some-ctx-1", + "entrypoint-ctx-3": "some-ctx-3", + "raw-auction-ctx-1": "some-ctx-1", + "raw-auction-ctx-3": "some-ctx-3", + }, + "module-2": { + "entrypoint-ctx-2": "some-ctx-2", + "raw-auction-ctx-2": "some-ctx-2", + }, + }}, exec.moduleContexts, "Wrong module contexts after executing raw-auction hook.") + + // test that context added at the processed-auction stage merged with existing module contexts + reject = exec.ExecuteProcessedAuctionStage(&openrtb2.BidRequest{}) + assert.Nil(t, reject, "Unexpected reject from processed-auction stage.") + assert.Equal(t, &moduleContexts{ctxs: map[string]hookstage.ModuleContext{ + "module-1": { + "entrypoint-ctx-1": "some-ctx-1", + "entrypoint-ctx-3": "some-ctx-3", + "raw-auction-ctx-1": "some-ctx-1", + "raw-auction-ctx-3": "some-ctx-3", + "processed-auction-ctx-1": "some-ctx-1", + "processed-auction-ctx-3": "some-ctx-3", + }, + "module-2": { + "entrypoint-ctx-2": "some-ctx-2", + "raw-auction-ctx-2": "some-ctx-2", + "processed-auction-ctx-2": "some-ctx-2", + }, + }}, exec.moduleContexts, "Wrong module contexts after executing processed-auction hook.") +} + +type TestApplyHookMutationsBuilder struct { + hooks.EmptyPlanBuilder +} + +func (e TestApplyHookMutationsBuilder) PlanForEntrypointStage(_ string) hooks.Plan[hookstage.Entrypoint] { + return hooks.Plan[hookstage.Entrypoint]{ + hooks.Group[hookstage.Entrypoint]{ + Timeout: 1 * time.Millisecond, + Hooks: []hooks.HookWrapper[hookstage.Entrypoint]{ + {Module: "foobar", Code: "foo", Hook: mockUpdateHeaderEntrypointHook{}}, + {Module: "foobar", Code: "foobaz", Hook: mockFailedMutationHook{}}, + {Module: "foobar", Code: "bar", Hook: mockUpdateQueryEntrypointHook{}}, + }, + }, + hooks.Group[hookstage.Entrypoint]{ + Timeout: 1 * time.Millisecond, + Hooks: []hooks.HookWrapper[hookstage.Entrypoint]{ + {Module: "foobar", Code: "baz", Hook: mockUpdateBodyHook{}}, + {Module: "foobar", Code: "foo", Hook: mockFailureHook{}}, + }, + }, + } +} + +func (e TestApplyHookMutationsBuilder) PlanForRawAuctionStage(_ string, _ *config.Account) hooks.Plan[hookstage.RawAuctionRequest] { + return hooks.Plan[hookstage.RawAuctionRequest]{ + hooks.Group[hookstage.RawAuctionRequest]{ + Timeout: 1 * time.Millisecond, + Hooks: []hooks.HookWrapper[hookstage.RawAuctionRequest]{ + {Module: "foobar", Code: "foo", Hook: mockUpdateBodyHook{}}, + {Module: "foobar", Code: "bar", Hook: mockFailedMutationHook{}}, + }, + }, + hooks.Group[hookstage.RawAuctionRequest]{ + Timeout: 1 * time.Millisecond, + Hooks: []hooks.HookWrapper[hookstage.RawAuctionRequest]{ + {Module: "foobar", Code: "baz", Hook: mockFailureHook{}}, + }, + }, + } +} + +func (e TestApplyHookMutationsBuilder) PlanForProcessedAuctionStage(_ string, _ *config.Account) hooks.Plan[hookstage.ProcessedAuctionRequest] { + return hooks.Plan[hookstage.ProcessedAuctionRequest]{ + hooks.Group[hookstage.ProcessedAuctionRequest]{ + Timeout: 1 * time.Millisecond, + Hooks: []hooks.HookWrapper[hookstage.ProcessedAuctionRequest]{ + {Module: "foobar", Code: "foo", Hook: mockUpdateBidRequestHook{}}, + }, + }, + } +} + +type TestRejectPlanBuilder struct { + hooks.EmptyPlanBuilder +} + +func (e TestRejectPlanBuilder) PlanForEntrypointStage(_ string) hooks.Plan[hookstage.Entrypoint] { + return hooks.Plan[hookstage.Entrypoint]{ + hooks.Group[hookstage.Entrypoint]{ + Timeout: 1 * time.Millisecond, + Hooks: []hooks.HookWrapper[hookstage.Entrypoint]{ + {Module: "foobar", Code: "foo", Hook: mockUpdateHeaderEntrypointHook{}}, + {Module: "foobar", Code: "baz", Hook: mockErrorHook{}}, + }, + }, + hooks.Group[hookstage.Entrypoint]{ + Timeout: 5 * time.Millisecond, + Hooks: []hooks.HookWrapper[hookstage.Entrypoint]{ + // reject stage + {Module: "foobar", Code: "bar", Hook: mockRejectHook{}}, + // next hook rejected: we use timeout hook to make sure + // that it runs longer than previous one, so it won't be executed earlier + {Module: "foobar", Code: "baz", Hook: mockTimeoutHook{}}, + }, + }, + // group of hooks rejected + hooks.Group[hookstage.Entrypoint]{ + Timeout: 1 * time.Millisecond, + Hooks: []hooks.HookWrapper[hookstage.Entrypoint]{ + {Module: "foobar", Code: "foo", Hook: mockUpdateHeaderEntrypointHook{}}, + {Module: "foobar", Code: "baz", Hook: mockErrorHook{}}, + }, + }, + } +} + +func (e TestRejectPlanBuilder) PlanForRawAuctionStage(_ string, _ *config.Account) hooks.Plan[hookstage.RawAuctionRequest] { + return hooks.Plan[hookstage.RawAuctionRequest]{ + hooks.Group[hookstage.RawAuctionRequest]{ + Timeout: 1 * time.Millisecond, + Hooks: []hooks.HookWrapper[hookstage.RawAuctionRequest]{ + {Module: "foobar", Code: "foo", Hook: mockUpdateBodyHook{}}, + {Module: "foobar", Code: "baz", Hook: mockErrorHook{}}, + }, + }, + hooks.Group[hookstage.RawAuctionRequest]{ + Timeout: 1 * time.Millisecond, + Hooks: []hooks.HookWrapper[hookstage.RawAuctionRequest]{ + {Module: "foobar", Code: "bar", Hook: mockRejectHook{}}, + // next hook rejected: we use timeout hook to make sure + // that it runs longer than previous one, so it won't be executed earlier + {Module: "foobar", Code: "baz", Hook: mockTimeoutHook{}}, + }, + }, + // group of hooks rejected + hooks.Group[hookstage.RawAuctionRequest]{ + Timeout: 1 * time.Millisecond, + Hooks: []hooks.HookWrapper[hookstage.RawAuctionRequest]{ + {Module: "foobar", Code: "foo", Hook: mockUpdateBodyHook{}}, + {Module: "foobar", Code: "baz", Hook: mockErrorHook{}}, + }, + }, + } +} + +func (e TestRejectPlanBuilder) PlanForProcessedAuctionStage(_ string, _ *config.Account) hooks.Plan[hookstage.ProcessedAuctionRequest] { + return hooks.Plan[hookstage.ProcessedAuctionRequest]{ + hooks.Group[hookstage.ProcessedAuctionRequest]{ + Timeout: 1 * time.Millisecond, + Hooks: []hooks.HookWrapper[hookstage.ProcessedAuctionRequest]{ + {Module: "foobar", Code: "foo", Hook: mockRejectHook{}}, + }, + }, + hooks.Group[hookstage.ProcessedAuctionRequest]{ + Timeout: 1 * time.Millisecond, + Hooks: []hooks.HookWrapper[hookstage.ProcessedAuctionRequest]{ + {Module: "foobar", Code: "bar", Hook: mockUpdateBidRequestHook{}}, + }, + }, + } +} + +type TestWithTimeoutPlanBuilder struct { + hooks.EmptyPlanBuilder +} + +func (e TestWithTimeoutPlanBuilder) PlanForEntrypointStage(_ string) hooks.Plan[hookstage.Entrypoint] { + return hooks.Plan[hookstage.Entrypoint]{ + hooks.Group[hookstage.Entrypoint]{ + Timeout: 1 * time.Millisecond, + Hooks: []hooks.HookWrapper[hookstage.Entrypoint]{ + {Module: "foobar", Code: "foo", Hook: mockUpdateHeaderEntrypointHook{}}, + {Module: "foobar", Code: "bar", Hook: mockTimeoutHook{}}, + }, + }, + hooks.Group[hookstage.Entrypoint]{ + Timeout: 1 * time.Millisecond, + Hooks: []hooks.HookWrapper[hookstage.Entrypoint]{ + {Module: "foobar", Code: "baz", Hook: mockUpdateBodyHook{}}, + }, + }, + } +} + +func (e TestWithTimeoutPlanBuilder) PlanForRawAuctionStage(_ string, _ *config.Account) hooks.Plan[hookstage.RawAuctionRequest] { + return hooks.Plan[hookstage.RawAuctionRequest]{ + hooks.Group[hookstage.RawAuctionRequest]{ + Timeout: 1 * time.Millisecond, + Hooks: []hooks.HookWrapper[hookstage.RawAuctionRequest]{ + {Module: "foobar", Code: "foo", Hook: mockUpdateBodyHook{}}, + }, + }, + hooks.Group[hookstage.RawAuctionRequest]{ + Timeout: 1 * time.Millisecond, + Hooks: []hooks.HookWrapper[hookstage.RawAuctionRequest]{ + {Module: "foobar", Code: "bar", Hook: mockTimeoutHook{}}, + }, + }, + } +} + +func (e TestWithTimeoutPlanBuilder) PlanForProcessedAuctionStage(_ string, _ *config.Account) hooks.Plan[hookstage.ProcessedAuctionRequest] { + return hooks.Plan[hookstage.ProcessedAuctionRequest]{ + hooks.Group[hookstage.ProcessedAuctionRequest]{ + Timeout: 1 * time.Millisecond, + Hooks: []hooks.HookWrapper[hookstage.ProcessedAuctionRequest]{ + {Module: "foobar", Code: "foo", Hook: mockTimeoutHook{}}, + }, + }, + hooks.Group[hookstage.ProcessedAuctionRequest]{ + Timeout: 1 * time.Millisecond, + Hooks: []hooks.HookWrapper[hookstage.ProcessedAuctionRequest]{ + {Module: "foobar", Code: "bar", Hook: mockUpdateBidRequestHook{}}, + }, + }, + } +} + +type TestWithModuleContextsPlanBuilder struct { + hooks.EmptyPlanBuilder +} + +func (e TestWithModuleContextsPlanBuilder) PlanForEntrypointStage(_ string) hooks.Plan[hookstage.Entrypoint] { + return hooks.Plan[hookstage.Entrypoint]{ + hooks.Group[hookstage.Entrypoint]{ + Timeout: 1 * time.Millisecond, + Hooks: []hooks.HookWrapper[hookstage.Entrypoint]{ + {Module: "module-1", Code: "foo", Hook: mockModuleContextHook{key: "entrypoint-ctx-1", val: "some-ctx-1"}}, + }, + }, + hooks.Group[hookstage.Entrypoint]{ + Timeout: 1 * time.Millisecond, + Hooks: []hooks.HookWrapper[hookstage.Entrypoint]{ + {Module: "module-2", Code: "bar", Hook: mockModuleContextHook{key: "entrypoint-ctx-2", val: "some-ctx-2"}}, + {Module: "module-1", Code: "baz", Hook: mockModuleContextHook{key: "entrypoint-ctx-3", val: "some-ctx-3"}}, + }, + }, + } +} + +func (e TestWithModuleContextsPlanBuilder) PlanForRawAuctionStage(_ string, _ *config.Account) hooks.Plan[hookstage.RawAuctionRequest] { + return hooks.Plan[hookstage.RawAuctionRequest]{ + hooks.Group[hookstage.RawAuctionRequest]{ + Timeout: 1 * time.Millisecond, + Hooks: []hooks.HookWrapper[hookstage.RawAuctionRequest]{ + {Module: "module-1", Code: "foo", Hook: mockModuleContextHook{key: "raw-auction-ctx-1", val: "some-ctx-1"}}, + {Module: "module-2", Code: "baz", Hook: mockModuleContextHook{key: "raw-auction-ctx-2", val: "some-ctx-2"}}, + }, + }, + hooks.Group[hookstage.RawAuctionRequest]{ + Timeout: 1 * time.Millisecond, + Hooks: []hooks.HookWrapper[hookstage.RawAuctionRequest]{ + {Module: "module-1", Code: "bar", Hook: mockModuleContextHook{key: "raw-auction-ctx-3", val: "some-ctx-3"}}, + }, + }, + } +} + +func (e TestWithModuleContextsPlanBuilder) PlanForProcessedAuctionStage(_ string, _ *config.Account) hooks.Plan[hookstage.ProcessedAuctionRequest] { + return hooks.Plan[hookstage.ProcessedAuctionRequest]{ + hooks.Group[hookstage.ProcessedAuctionRequest]{ + Timeout: 1 * time.Millisecond, + Hooks: []hooks.HookWrapper[hookstage.ProcessedAuctionRequest]{ + {Module: "module-1", Code: "foo", Hook: mockModuleContextHook{key: "processed-auction-ctx-1", val: "some-ctx-1"}}, + }, + }, + hooks.Group[hookstage.ProcessedAuctionRequest]{ + Timeout: 1 * time.Millisecond, + Hooks: []hooks.HookWrapper[hookstage.ProcessedAuctionRequest]{ + {Module: "module-2", Code: "bar", Hook: mockModuleContextHook{key: "processed-auction-ctx-2", val: "some-ctx-2"}}, + {Module: "module-1", Code: "baz", Hook: mockModuleContextHook{key: "processed-auction-ctx-3", val: "some-ctx-3"}}, + }, + }, + } +} + +type TestAllHookResultsBuilder struct { + hooks.EmptyPlanBuilder +} + +func (e TestAllHookResultsBuilder) PlanForEntrypointStage(_ string) hooks.Plan[hookstage.Entrypoint] { + return hooks.Plan[hookstage.Entrypoint]{ + hooks.Group[hookstage.Entrypoint]{ + Timeout: 1 * time.Millisecond, + Hooks: []hooks.HookWrapper[hookstage.Entrypoint]{ + {Module: "module-1", Code: "code-1", Hook: mockUpdateHeaderEntrypointHook{}}, + {Module: "module-1", Code: "code-3", Hook: mockTimeoutHook{}}, + {Module: "module-1", Code: "code-4", Hook: mockFailureHook{}}, + {Module: "module-1", Code: "code-5", Hook: mockErrorHook{}}, + {Module: "module-1", Code: "code-6", Hook: mockFailedMutationHook{}}, + {Module: "module-1", Code: "code-7", Hook: mockModuleContextHook{key: "key", val: "val"}}, + }, + }, + // place the reject hook in a separate group because it rejects the stage completely + // thus we can not make accurate mock calls if it is processed in parallel with others + hooks.Group[hookstage.Entrypoint]{ + Timeout: 10 * time.Second, + Hooks: []hooks.HookWrapper[hookstage.Entrypoint]{ + {Module: "module-1", Code: "code-2", Hook: mockRejectHook{}}, + }, + }, + } +} diff --git a/hooks/hookexecution/mocks_test.go b/hooks/hookexecution/mocks_test.go new file mode 100644 index 00000000000..a11063ef0bb --- /dev/null +++ b/hooks/hookexecution/mocks_test.go @@ -0,0 +1,200 @@ +package hookexecution + +import ( + "context" + "errors" + "time" + + "github.com/prebid/prebid-server/hooks/hookstage" +) + +type mockUpdateHeaderEntrypointHook struct{} + +func (e mockUpdateHeaderEntrypointHook) HandleEntrypointHook(_ context.Context, _ hookstage.ModuleInvocationContext, _ hookstage.EntrypointPayload) (hookstage.HookResult[hookstage.EntrypointPayload], error) { + c := &hookstage.ChangeSet[hookstage.EntrypointPayload]{} + c.AddMutation(func(payload hookstage.EntrypointPayload) (hookstage.EntrypointPayload, error) { + payload.Request.Header.Add("foo", "bar") + return payload, nil + }, hookstage.MutationUpdate, "header", "foo") + + return hookstage.HookResult[hookstage.EntrypointPayload]{ChangeSet: c}, nil +} + +type mockUpdateQueryEntrypointHook struct{} + +func (e mockUpdateQueryEntrypointHook) HandleEntrypointHook(_ context.Context, _ hookstage.ModuleInvocationContext, _ hookstage.EntrypointPayload) (hookstage.HookResult[hookstage.EntrypointPayload], error) { + c := &hookstage.ChangeSet[hookstage.EntrypointPayload]{} + c.AddMutation(func(payload hookstage.EntrypointPayload) (hookstage.EntrypointPayload, error) { + params := payload.Request.URL.Query() + params.Add("foo", "baz") + payload.Request.URL.RawQuery = params.Encode() + return payload, nil + }, hookstage.MutationUpdate, "param", "foo") + + return hookstage.HookResult[hookstage.EntrypointPayload]{ChangeSet: c}, nil +} + +type mockUpdateBodyHook struct{} + +func (e mockUpdateBodyHook) HandleEntrypointHook(_ context.Context, _ hookstage.ModuleInvocationContext, _ hookstage.EntrypointPayload) (hookstage.HookResult[hookstage.EntrypointPayload], error) { + c := &hookstage.ChangeSet[hookstage.EntrypointPayload]{} + c.AddMutation( + func(payload hookstage.EntrypointPayload) (hookstage.EntrypointPayload, error) { + payload.Body = []byte(`{"name": "John", "last_name": "Doe", "foo": "bar"}`) + return payload, nil + }, hookstage.MutationUpdate, "body", "foo", + ).AddMutation( + func(payload hookstage.EntrypointPayload) (hookstage.EntrypointPayload, error) { + payload.Body = []byte(`{"last_name": "Doe", "foo": "bar"}`) + return payload, nil + }, hookstage.MutationDelete, "body", "name", + ) + + return hookstage.HookResult[hookstage.EntrypointPayload]{ChangeSet: c}, nil +} + +func (e mockUpdateBodyHook) HandleRawAuctionHook(_ context.Context, _ hookstage.ModuleInvocationContext, _ hookstage.RawAuctionRequestPayload) (hookstage.HookResult[hookstage.RawAuctionRequestPayload], error) { + c := &hookstage.ChangeSet[hookstage.RawAuctionRequestPayload]{} + c.AddMutation( + func(payload hookstage.RawAuctionRequestPayload) (hookstage.RawAuctionRequestPayload, error) { + payload = []byte(`{"name": "John", "last_name": "Doe", "foo": "bar"}`) + return payload, nil + }, hookstage.MutationUpdate, "body", "foo", + ).AddMutation( + func(payload hookstage.RawAuctionRequestPayload) (hookstage.RawAuctionRequestPayload, error) { + payload = []byte(`{"last_name": "Doe", "foo": "bar"}`) + return payload, nil + }, hookstage.MutationDelete, "body", "name", + ) + + return hookstage.HookResult[hookstage.RawAuctionRequestPayload]{ChangeSet: c}, nil +} + +type mockRejectHook struct{} + +func (e mockRejectHook) HandleEntrypointHook(_ context.Context, _ hookstage.ModuleInvocationContext, _ hookstage.EntrypointPayload) (hookstage.HookResult[hookstage.EntrypointPayload], error) { + return hookstage.HookResult[hookstage.EntrypointPayload]{Reject: true}, nil +} + +func (e mockRejectHook) HandleRawAuctionHook(_ context.Context, _ hookstage.ModuleInvocationContext, _ hookstage.RawAuctionRequestPayload) (hookstage.HookResult[hookstage.RawAuctionRequestPayload], error) { + return hookstage.HookResult[hookstage.RawAuctionRequestPayload]{Reject: true}, nil +} + +func (e mockRejectHook) HandleProcessedAuctionHook(_ context.Context, _ hookstage.ModuleInvocationContext, _ hookstage.ProcessedAuctionRequestPayload) (hookstage.HookResult[hookstage.ProcessedAuctionRequestPayload], error) { + return hookstage.HookResult[hookstage.ProcessedAuctionRequestPayload]{Reject: true}, nil +} + +type mockTimeoutHook struct{} + +func (e mockTimeoutHook) HandleEntrypointHook(_ context.Context, _ hookstage.ModuleInvocationContext, _ hookstage.EntrypointPayload) (hookstage.HookResult[hookstage.EntrypointPayload], error) { + time.Sleep(2 * time.Millisecond) + c := &hookstage.ChangeSet[hookstage.EntrypointPayload]{} + c.AddMutation(func(payload hookstage.EntrypointPayload) (hookstage.EntrypointPayload, error) { + params := payload.Request.URL.Query() + params.Add("bar", "foo") + payload.Request.URL.RawQuery = params.Encode() + return payload, nil + }, hookstage.MutationUpdate, "param", "bar") + + return hookstage.HookResult[hookstage.EntrypointPayload]{ChangeSet: c}, nil +} + +func (e mockTimeoutHook) HandleRawAuctionHook(_ context.Context, _ hookstage.ModuleInvocationContext, _ hookstage.RawAuctionRequestPayload) (hookstage.HookResult[hookstage.RawAuctionRequestPayload], error) { + time.Sleep(2 * time.Millisecond) + c := &hookstage.ChangeSet[hookstage.RawAuctionRequestPayload]{} + c.AddMutation(func(payload hookstage.RawAuctionRequestPayload) (hookstage.RawAuctionRequestPayload, error) { + payload = []byte(`{"last_name": "Doe", "foo": "bar", "address": "A st."}`) + return payload, nil + }, hookstage.MutationUpdate, "param", "address") + + return hookstage.HookResult[hookstage.RawAuctionRequestPayload]{ChangeSet: c}, nil +} + +func (e mockTimeoutHook) HandleProcessedAuctionHook(_ context.Context, _ hookstage.ModuleInvocationContext, _ hookstage.ProcessedAuctionRequestPayload) (hookstage.HookResult[hookstage.ProcessedAuctionRequestPayload], error) { + time.Sleep(2 * time.Millisecond) + c := &hookstage.ChangeSet[hookstage.ProcessedAuctionRequestPayload]{} + c.AddMutation(func(payload hookstage.ProcessedAuctionRequestPayload) (hookstage.ProcessedAuctionRequestPayload, error) { + payload.BidRequest.User.CustomData = "some-custom-data" + return payload, nil + }, hookstage.MutationUpdate, "bidRequest", "user.customData") + + return hookstage.HookResult[hookstage.ProcessedAuctionRequestPayload]{ChangeSet: c}, nil +} + +type mockModuleContextHook struct { + key, val string +} + +func (e mockModuleContextHook) HandleEntrypointHook(_ context.Context, miCtx hookstage.ModuleInvocationContext, _ hookstage.EntrypointPayload) (hookstage.HookResult[hookstage.EntrypointPayload], error) { + miCtx.ModuleContext = map[string]interface{}{e.key: e.val} + return hookstage.HookResult[hookstage.EntrypointPayload]{ModuleContext: miCtx.ModuleContext}, nil +} + +func (e mockModuleContextHook) HandleRawAuctionHook(_ context.Context, miCtx hookstage.ModuleInvocationContext, _ hookstage.RawAuctionRequestPayload) (hookstage.HookResult[hookstage.RawAuctionRequestPayload], error) { + miCtx.ModuleContext = map[string]interface{}{e.key: e.val} + return hookstage.HookResult[hookstage.RawAuctionRequestPayload]{ModuleContext: miCtx.ModuleContext}, nil +} + +func (e mockModuleContextHook) HandleProcessedAuctionHook(_ context.Context, miCtx hookstage.ModuleInvocationContext, _ hookstage.ProcessedAuctionRequestPayload) (hookstage.HookResult[hookstage.ProcessedAuctionRequestPayload], error) { + miCtx.ModuleContext = map[string]interface{}{e.key: e.val} + return hookstage.HookResult[hookstage.ProcessedAuctionRequestPayload]{ModuleContext: miCtx.ModuleContext}, nil +} + +type mockFailureHook struct{} + +func (h mockFailureHook) HandleEntrypointHook(_ context.Context, _ hookstage.ModuleInvocationContext, _ hookstage.EntrypointPayload) (hookstage.HookResult[hookstage.EntrypointPayload], error) { + return hookstage.HookResult[hookstage.EntrypointPayload]{}, FailureError{Message: "attribute not found"} +} + +func (h mockFailureHook) HandleRawAuctionHook(_ context.Context, miCtx hookstage.ModuleInvocationContext, _ hookstage.RawAuctionRequestPayload) (hookstage.HookResult[hookstage.RawAuctionRequestPayload], error) { + return hookstage.HookResult[hookstage.RawAuctionRequestPayload]{}, FailureError{Message: "attribute not found"} +} + +type mockErrorHook struct{} + +func (h mockErrorHook) HandleEntrypointHook(_ context.Context, _ hookstage.ModuleInvocationContext, _ hookstage.EntrypointPayload) (hookstage.HookResult[hookstage.EntrypointPayload], error) { + return hookstage.HookResult[hookstage.EntrypointPayload]{}, errors.New("unexpected error") +} + +func (h mockErrorHook) HandleRawAuctionHook(_ context.Context, miCtx hookstage.ModuleInvocationContext, _ hookstage.RawAuctionRequestPayload) (hookstage.HookResult[hookstage.RawAuctionRequestPayload], error) { + return hookstage.HookResult[hookstage.RawAuctionRequestPayload]{}, errors.New("unexpected error") +} + +type mockFailedMutationHook struct{} + +func (h mockFailedMutationHook) HandleEntrypointHook(_ context.Context, _ hookstage.ModuleInvocationContext, _ hookstage.EntrypointPayload) (hookstage.HookResult[hookstage.EntrypointPayload], error) { + changeSet := &hookstage.ChangeSet[hookstage.EntrypointPayload]{} + changeSet.AddMutation(func(payload hookstage.EntrypointPayload) (hookstage.EntrypointPayload, error) { + return payload, errors.New("key not found") + }, hookstage.MutationUpdate, "header", "foo") + + return hookstage.HookResult[hookstage.EntrypointPayload]{ChangeSet: changeSet}, nil +} + +func (h mockFailedMutationHook) HandleRawAuctionHook(_ context.Context, miCtx hookstage.ModuleInvocationContext, _ hookstage.RawAuctionRequestPayload) (hookstage.HookResult[hookstage.RawAuctionRequestPayload], error) { + changeSet := &hookstage.ChangeSet[hookstage.RawAuctionRequestPayload]{} + changeSet.AddMutation(func(payload hookstage.RawAuctionRequestPayload) (hookstage.RawAuctionRequestPayload, error) { + return payload, errors.New("key not found") + }, hookstage.MutationUpdate, "header", "foo") + + return hookstage.HookResult[hookstage.RawAuctionRequestPayload]{ChangeSet: changeSet}, nil +} + +type mockUpdateBidRequestHook struct{} + +func (e mockUpdateBidRequestHook) HandleProcessedAuctionHook(_ context.Context, _ hookstage.ModuleInvocationContext, _ hookstage.ProcessedAuctionRequestPayload) (hookstage.HookResult[hookstage.ProcessedAuctionRequestPayload], error) { + c := &hookstage.ChangeSet[hookstage.ProcessedAuctionRequestPayload]{} + c.AddMutation( + func(payload hookstage.ProcessedAuctionRequestPayload) (hookstage.ProcessedAuctionRequestPayload, error) { + payload.BidRequest.User.Yob = 2000 + return payload, nil + }, hookstage.MutationUpdate, "bidRequest", "user.yob", + ).AddMutation( + func(payload hookstage.ProcessedAuctionRequestPayload) (hookstage.ProcessedAuctionRequestPayload, error) { + payload.BidRequest.User.Consent = "true" + return payload, nil + }, hookstage.MutationUpdate, "bidRequest", "user.consent", + ) + + return hookstage.HookResult[hookstage.ProcessedAuctionRequestPayload]{ChangeSet: c}, nil +} diff --git a/hooks/hookexecution/outcome.go b/hooks/hookexecution/outcome.go new file mode 100644 index 00000000000..d02bed9af22 --- /dev/null +++ b/hooks/hookexecution/outcome.go @@ -0,0 +1,67 @@ +package hookexecution + +import ( + "time" + + "github.com/prebid/prebid-server/hooks/hookanalytics" +) + +// Status indicates the result of hook execution. +type Status string + +const ( + StatusSuccess Status = "success" // successful hook execution + StatusTimeout Status = "timeout" // hook was not completed in the allotted time + StatusFailure Status = "failure" // expected module-side failure occurred during hook execution + StatusExecutionFailure Status = "execution_failure" // unexpected failure occurred during hook execution +) + +// Action indicates the type of taken behaviour after the successful hook execution. +type Action string + +const ( + ActionUpdate Action = "update" // the hook returned mutations that were successfully applied + ActionReject Action = "reject" // the hook decided to reject the stage + ActionNone Action = "no_action" // the hook does not want to take any action +) + +// StageOutcome represents the result of executing specific stage. +type StageOutcome struct { + // ExecutionTime is the sum of ExecutionTime of all its groups + ExecutionTime + // An Entity specifies the type of object that was processed during the execution of the stage. + Entity entity `json:"entity"` + Groups []GroupOutcome `json:"groups"` + Stage string `json:"-"` +} + +// GroupOutcome represents the result of executing specific group of hooks. +type GroupOutcome struct { + // ExecutionTime is set to the longest ExecutionTime of its children. + ExecutionTime + InvocationResults []HookOutcome `json:"invocation_results"` +} + +// HookOutcome represents the result of executing specific hook. +type HookOutcome struct { + // ExecutionTime is the execution time of a specific hook without applying its result. + ExecutionTime + AnalyticsTags hookanalytics.Analytics `json:"analytics_tags"` + HookID HookID `json:"hook_id"` + Status Status `json:"status"` + Action Action `json:"action"` + Message string `json:"message"` // arbitrary string value returned from hook execution + DebugMessages []string `json:"debug_messages"` + Errors []string `json:"-"` + Warnings []string `json:"-"` +} + +// HookID points to the specific hook defined by the hook execution plan. +type HookID struct { + ModuleCode string `json:"module_code"` + HookImplCode string `json:"hook_impl_code"` +} + +type ExecutionTime struct { + ExecutionTimeMillis time.Duration `json:"execution_time_millis"` +} diff --git a/hooks/hookexecution/outcome_test.go b/hooks/hookexecution/outcome_test.go new file mode 100644 index 00000000000..de81d7b0854 --- /dev/null +++ b/hooks/hookexecution/outcome_test.go @@ -0,0 +1,51 @@ +package hookexecution + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func assertEqualStageOutcomes(t *testing.T, expected StageOutcome, actual StageOutcome) { + t.Helper() + + assert.Equal(t, len(actual.Groups), len(expected.Groups), "Stage outcomes contain different number of groups") + + // calculate expected timings from actual outcome + for i, group := range actual.Groups { + expected.ExecutionTimeMillis += group.ExecutionTimeMillis + for _, hook := range group.InvocationResults { + if hook.ExecutionTimeMillis > expected.Groups[i].ExecutionTimeMillis { + expected.Groups[i].ExecutionTimeMillis = hook.ExecutionTimeMillis + } + } + } + + assert.Equal(t, expected.ExecutionTimeMillis, actual.ExecutionTimeMillis, "Incorrect stage execution time") + assert.Equal(t, expected.Stage, actual.Stage, "Incorrect stage name") + assert.Equal(t, expected.Entity, actual.Entity, "Incorrect stage entity name") + + for i, expGroup := range expected.Groups { + gotGroup := actual.Groups[i] + assert.Equal(t, len(expGroup.InvocationResults), len(gotGroup.InvocationResults), "Group outcomes #%d contain different number of invocation results", i) + assert.Equal(t, expGroup.ExecutionTimeMillis, gotGroup.ExecutionTimeMillis, "Incorrect group #%d execution time", i) + + for _, expHook := range expGroup.InvocationResults { + gotHook := findCorrespondingHookResult(expHook.HookID, gotGroup) + assert.NotNil(t, gotHook, "Expected to get hook, got nil: group #%d, hookID %v", i, expHook.HookID) + + gotHook.ExecutionTimeMillis = 0 // reset hook execution time, we cannot predict it + assert.Equal(t, expHook, *gotHook, "Incorrect hook outcome: group #%d, hookID %v", i, expHook.HookID) + } + } +} + +func findCorrespondingHookResult(hookID HookID, group GroupOutcome) *HookOutcome { + for _, hook := range group.InvocationResults { + if hook.HookID.ModuleCode == hookID.ModuleCode && + hook.HookID.HookImplCode == hookID.HookImplCode { + return &hook + } + } + return nil +} diff --git a/hooks/hookstage/allprocessedbidresponses.go b/hooks/hookstage/allprocessedbidresponses.go new file mode 100644 index 00000000000..85b3e4f9439 --- /dev/null +++ b/hooks/hookstage/allprocessedbidresponses.go @@ -0,0 +1,28 @@ +package hookstage + +import ( + "context" +) + +// AllProcessedBidResponses hooks are invoked over a list of all +// processed responses received from bidders before a winner is chosen. +// +// At this stage, account config is available, +// so it can be configured at the account-level execution plan, +// the account-level module config is passed to hooks. +// +// Rejection has no effect and is completely ignored at this stage. +type AllProcessedBidResponses interface { + HandleAllProcessedBidResponsesHook( + context.Context, + ModuleInvocationContext, + AllProcessedBidResponsesPayload, + ) (HookResult[AllProcessedBidResponsesPayload], error) +} + +// AllProcessedBidResponsesPayload consists of a list of all +// processed responses received from bidders. +// Hooks are allowed to modify payload object and discard bids using mutations. +type AllProcessedBidResponsesPayload struct { + // todo: decide what payload to use within the hook invocation task +} diff --git a/hooks/hookstage/auctionresponse.go b/hooks/hookstage/auctionresponse.go new file mode 100644 index 00000000000..2227c4b54ee --- /dev/null +++ b/hooks/hookstage/auctionresponse.go @@ -0,0 +1,30 @@ +package hookstage + +import ( + "context" + + "github.com/prebid/openrtb/v17/openrtb2" +) + +// AuctionResponse hooks are invoked at the very end of request processing. +// The hooks are invoked even if the request was rejected at earlier stages. +// +// At this stage, account config is available, +// so it can be configured at the account-level execution plan, +// the account-level module config is passed to hooks. +// +// Rejection has no effect and is completely ignored at this stage. +type AuctionResponse interface { + HandleAuctionResponseHook( + context.Context, + ModuleInvocationContext, + AuctionResponsePayload, + ) (HookResult[AuctionResponsePayload], error) +} + +// AuctionResponsePayload consists of a final openrtb2.BidResponse +// object that will be sent back to the requester. +// Hooks are allowed to modify openrtb2.BidResponse object. +type AuctionResponsePayload struct { + BidResponse *openrtb2.BidResponse +} diff --git a/hooks/hookstage/bidderrequest.go b/hooks/hookstage/bidderrequest.go new file mode 100644 index 00000000000..0da4640d9b6 --- /dev/null +++ b/hooks/hookstage/bidderrequest.go @@ -0,0 +1,29 @@ +package hookstage + +import ( + "context" + + "github.com/prebid/openrtb/v17/openrtb2" +) + +// BidderRequest hooks are invoked for each bidder participating in auction. +// +// At this stage, account config is available, +// so it can be configured at the account-level execution plan, +// the account-level module config is passed to hooks. +// +// Rejection results in skipping the bidder's request. +type BidderRequest interface { + HandleBidderRequestHook( + context.Context, + ModuleInvocationContext, + BidderRequestPayload, + ) (HookResult[BidderRequestPayload], error) +} + +// BidderRequestPayload consists of the openrtb2.BidRequest object +// distilled for the particular bidder. +// Hooks are allowed to modify openrtb2.BidRequest using mutations. +type BidderRequestPayload struct { + BidRequest *openrtb2.BidRequest +} diff --git a/hooks/hookstage/entrypoint.go b/hooks/hookstage/entrypoint.go new file mode 100644 index 00000000000..627e9af2213 --- /dev/null +++ b/hooks/hookstage/entrypoint.go @@ -0,0 +1,30 @@ +package hookstage + +import ( + "context" + "net/http" +) + +// Entrypoint hooks are invoked at the very beginning of request processing. +// +// At this stage, account config is not yet available, +// so it can only be defined as part of the host-level execution plan, +// the account-level module config is not available. +// +// Rejection results in sending an empty BidResponse +// with the NBR code indicating the rejection reason. +type Entrypoint interface { + HandleEntrypointHook( + context.Context, + ModuleInvocationContext, + EntrypointPayload, + ) (HookResult[EntrypointPayload], error) +} + +// EntrypointPayload consists of an HTTP request and a raw body of the openrtb2.BidRequest. +// For "/openrtb2/amp" endpoint the body is nil. +// Hooks are allowed to modify this data using mutations. +type EntrypointPayload struct { + Request *http.Request + Body []byte +} diff --git a/hooks/hookstage/invocation.go b/hooks/hookstage/invocation.go new file mode 100644 index 00000000000..afea4a2fd8d --- /dev/null +++ b/hooks/hookstage/invocation.go @@ -0,0 +1,34 @@ +package hookstage + +import ( + "encoding/json" + + "github.com/prebid/prebid-server/hooks/hookanalytics" +) + +// HookResult represents the result of execution the concrete hook instance. +type HookResult[T any] struct { + Reject bool // true value indicates rejection of the program execution at the specific stage + NbrCode int // hook must provide NbrCode if the field Reject set to true + Message string // holds arbitrary message added by hook + ChangeSet *ChangeSet[T] // set of changes the module wants to apply to hook payload in case of successful execution + Errors []string + Warnings []string + DebugMessages []string + AnalyticsTags hookanalytics.Analytics + ModuleContext ModuleContext // holds values that the module wants to pass to itself at later stages +} + +// ModuleInvocationContext holds data passed to the module hook during invocation. +type ModuleInvocationContext struct { + // AccountConfig represents module config rewritten at the account-level. + AccountConfig json.RawMessage + // Endpoint represents the path of the current endpoint. + Endpoint string + // ModuleContext holds values that the module passes to itself from the previous stages. + ModuleContext ModuleContext +} + +// ModuleContext holds arbitrary data passed between module hooks at different stages. +// We use interface as we do not know exactly how the modules will use their inner context. +type ModuleContext map[string]interface{} diff --git a/hooks/hookstage/mutation.go b/hooks/hookstage/mutation.go new file mode 100644 index 00000000000..53d6333667c --- /dev/null +++ b/hooks/hookstage/mutation.go @@ -0,0 +1,54 @@ +package hookstage + +type MutationType int + +const ( + MutationAdd MutationType = iota + MutationUpdate + MutationDelete +) + +func (mt MutationType) String() string { + if v, ok := map[MutationType]string{ + MutationAdd: "add", + MutationUpdate: "update", + MutationDelete: "delete", + }[mt]; ok { + return v + } + + return "unknown" +} + +type mutationFunc[T any] func(T) (T, error) + +type Mutation[T any] struct { + mutType MutationType + key []string // key indicates path to the modified field + fn mutationFunc[T] // fn actual function that makes changes to payload +} + +func (m Mutation[T]) Type() MutationType { + return m.mutType +} + +func (m Mutation[T]) Key() []string { + return m.key +} + +func (m Mutation[T]) Apply(p T) (T, error) { + return m.fn(p) +} + +type ChangeSet[T any] struct { + muts []Mutation[T] +} + +func (c *ChangeSet[T]) Mutations() []Mutation[T] { + return c.muts +} + +func (c *ChangeSet[T]) AddMutation(fn mutationFunc[T], t MutationType, k ...string) *ChangeSet[T] { + c.muts = append(c.muts, Mutation[T]{fn: fn, mutType: t, key: k}) + return c +} diff --git a/hooks/hookstage/processedauctionrequest.go b/hooks/hookstage/processedauctionrequest.go new file mode 100644 index 00000000000..80c7f086401 --- /dev/null +++ b/hooks/hookstage/processedauctionrequest.go @@ -0,0 +1,30 @@ +package hookstage + +import ( + "context" + + "github.com/prebid/openrtb/v17/openrtb2" +) + +// ProcessedAuctionRequest hooks are invoked after the request is parsed +// and enriched with additional data. +// +// At this stage, account config is available, +// so it can be configured at the account-level execution plan, +// the account-level module config is passed to hooks. +// +// Rejection results in sending an empty BidResponse +// with the NBR code indicating the rejection reason. +type ProcessedAuctionRequest interface { + HandleProcessedAuctionHook( + context.Context, + ModuleInvocationContext, + ProcessedAuctionRequestPayload, + ) (HookResult[ProcessedAuctionRequestPayload], error) +} + +// ProcessedAuctionRequestPayload consists of the openrtb2.BidRequest object. +// Hooks are allowed to modify openrtb2.BidRequest using mutations. +type ProcessedAuctionRequestPayload struct { + BidRequest *openrtb2.BidRequest +} diff --git a/hooks/hookstage/rawauctionrequest.go b/hooks/hookstage/rawauctionrequest.go new file mode 100644 index 00000000000..1462e3fb755 --- /dev/null +++ b/hooks/hookstage/rawauctionrequest.go @@ -0,0 +1,27 @@ +package hookstage + +import ( + "context" +) + +// RawAuctionRequest hooks are invoked only for "/openrtb2/auction" +// endpoint after retrieving the account config, +// but before the request is parsed and any additions are made. +// +// At this stage, account config is available, +// so it can be configured at the account-level execution plan, +// the account-level module config is passed to hooks. +// +// Rejection results in sending an empty BidResponse +// with the NBR code indicating the rejection reason. +type RawAuctionRequest interface { + HandleRawAuctionHook( + context.Context, + ModuleInvocationContext, + RawAuctionRequestPayload, + ) (HookResult[RawAuctionRequestPayload], error) +} + +// RawAuctionRequestPayload represents a raw body of the openrtb2.BidRequest. +// Hooks are allowed to modify body using mutations. +type RawAuctionRequestPayload []byte diff --git a/hooks/hookstage/rawbidderresponse.go b/hooks/hookstage/rawbidderresponse.go new file mode 100644 index 00000000000..5a66e0a901b --- /dev/null +++ b/hooks/hookstage/rawbidderresponse.go @@ -0,0 +1,29 @@ +package hookstage + +import ( + "context" + + "github.com/prebid/prebid-server/adapters" +) + +// RawBidderResponse hooks are invoked for each bidder participating in auction. +// +// At this stage, account config is available, +// so it can be configured at the account-level execution plan, +// the account-level module config is passed to hooks. +// +// Rejection results in ignoring the bidder's response. +type RawBidderResponse interface { + HandleRawBidderResponseHook( + context.Context, + ModuleInvocationContext, + RawBidderResponsePayload, + ) (HookResult[RawBidderResponsePayload], error) +} + +// RawBidderResponsePayload consists of a list of adapters.TypedBid +// objects representing bids returned by a particular bidder. +// Hooks are allowed to modify bids using mutations. +type RawBidderResponsePayload struct { + Bids []*adapters.TypedBid +} diff --git a/hooks/plan.go b/hooks/plan.go new file mode 100644 index 00000000000..c6fda959762 --- /dev/null +++ b/hooks/plan.go @@ -0,0 +1,206 @@ +package hooks + +import ( + "time" + + "github.com/golang/glog" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/hooks/hookstage" +) + +type Stage string + +// Names of the available stages. +const ( + StageEntrypoint Stage = "entrypoint" + StageRawAuctionRequest Stage = "raw_auction_request" + StageProcessedAuctionRequest Stage = "processed_auction_request" + StageBidderRequest Stage = "bidder_request" + StageRawBidderResponse Stage = "raw_bidder_response" + StageAllProcessedBidResponses Stage = "all_processed_bid_responses" + StageAuctionResponse Stage = "auction_response" +) + +func (s Stage) String() string { + return string(s) +} + +func (s Stage) IsRejectable() bool { + return s != StageAllProcessedBidResponses && + s != StageAuctionResponse +} + +// ExecutionPlanBuilder is the interface that provides methods +// for retrieving hooks grouped and sorted in the established order +// according to the hook execution plan intended for run at a certain stage. +type ExecutionPlanBuilder interface { + PlanForEntrypointStage(endpoint string) Plan[hookstage.Entrypoint] + PlanForRawAuctionStage(endpoint string, account *config.Account) Plan[hookstage.RawAuctionRequest] + PlanForProcessedAuctionStage(endpoint string, account *config.Account) Plan[hookstage.ProcessedAuctionRequest] + PlanForBidderRequestStage(endpoint string, account *config.Account) Plan[hookstage.BidderRequest] + PlanForRawBidderResponseStage(endpoint string, account *config.Account) Plan[hookstage.RawBidderResponse] + PlanForAllProcessedBidResponsesStage(endpoint string, account *config.Account) Plan[hookstage.AllProcessedBidResponses] + PlanForAuctionResponseStage(endpoint string, account *config.Account) Plan[hookstage.AuctionResponse] +} + +// Plan represents a slice of groups of hooks of a specific type grouped in the established order. +type Plan[T any] []Group[T] + +// Group represents a slice of hooks sorted in the established order. +type Group[T any] struct { + // Timeout specifies the max duration in milliseconds that a group of hooks is allowed to run. + Timeout time.Duration + // Hooks holds a slice of HookWrapper of a specific type. + Hooks []HookWrapper[T] +} + +// HookWrapper wraps Hook representing specific hook interface +// and holds additional meta information, such as Module name and hook Code. +type HookWrapper[T any] struct { + // Module holds a name of the module that provides the Hook. + // Specified in the format "vendor.module_name". + Module string + // Code is an arbitrary value assigned to hook via the hook execution plan + // and is used when sending metrics, logging debug information, etc. + Code string + // Hook is an instance of the specific hook interface. + Hook T +} + +// NewExecutionPlanBuilder returns a new instance of the ExecutionPlanBuilder interface. +// Depending on the hooks' status, method returns a real PlanBuilder or the EmptyPlanBuilder. +func NewExecutionPlanBuilder(hooks config.Hooks, repo HookRepository) ExecutionPlanBuilder { + if hooks.Enabled { + return PlanBuilder{ + hooks: hooks, + repo: repo, + } + } + return EmptyPlanBuilder{} +} + +// PlanBuilder is a concrete implementation of the ExecutionPlanBuilder interface. +// Which returns hook execution plans for specific stage defined by the hook config. +type PlanBuilder struct { + hooks config.Hooks + repo HookRepository +} + +func (p PlanBuilder) PlanForEntrypointStage(endpoint string) Plan[hookstage.Entrypoint] { + return getMergedPlan( + p.hooks, + nil, + endpoint, + StageEntrypoint, + p.repo.GetEntrypointHook, + ) +} + +func (p PlanBuilder) PlanForRawAuctionStage(endpoint string, account *config.Account) Plan[hookstage.RawAuctionRequest] { + return getMergedPlan( + p.hooks, + account, + endpoint, + StageRawAuctionRequest, + p.repo.GetRawAuctionHook, + ) +} + +func (p PlanBuilder) PlanForProcessedAuctionStage(endpoint string, account *config.Account) Plan[hookstage.ProcessedAuctionRequest] { + return getMergedPlan( + p.hooks, + account, + endpoint, + StageProcessedAuctionRequest, + p.repo.GetProcessedAuctionHook, + ) +} + +func (p PlanBuilder) PlanForBidderRequestStage(endpoint string, account *config.Account) Plan[hookstage.BidderRequest] { + return getMergedPlan( + p.hooks, + account, + endpoint, + StageBidderRequest, + p.repo.GetBidderRequestHook, + ) +} + +func (p PlanBuilder) PlanForRawBidderResponseStage(endpoint string, account *config.Account) Plan[hookstage.RawBidderResponse] { + return getMergedPlan( + p.hooks, + account, + endpoint, + StageRawBidderResponse, + p.repo.GetRawBidderResponseHook, + ) +} + +func (p PlanBuilder) PlanForAllProcessedBidResponsesStage(endpoint string, account *config.Account) Plan[hookstage.AllProcessedBidResponses] { + return getMergedPlan( + p.hooks, + account, + endpoint, + StageAllProcessedBidResponses, + p.repo.GetAllProcessedBidResponsesHook, + ) +} + +func (p PlanBuilder) PlanForAuctionResponseStage(endpoint string, account *config.Account) Plan[hookstage.AuctionResponse] { + return getMergedPlan( + p.hooks, + account, + endpoint, + StageAuctionResponse, + p.repo.GetAuctionResponseHook, + ) +} + +type hookFn[T any] func(moduleName string) (T, bool) + +func getMergedPlan[T any]( + cfg config.Hooks, + account *config.Account, + endpoint string, + stage Stage, + getHookFn hookFn[T], +) Plan[T] { + accountPlan := cfg.DefaultAccountExecutionPlan + if account != nil && account.Hooks.ExecutionPlan.Endpoints != nil { + accountPlan = account.Hooks.ExecutionPlan + } + + plan := getPlan(getHookFn, cfg.HostExecutionPlan, endpoint, stage) + plan = append(plan, getPlan(getHookFn, accountPlan, endpoint, stage)...) + + return plan +} + +func getPlan[T any](getHookFn hookFn[T], cfg config.HookExecutionPlan, endpoint string, stage Stage) Plan[T] { + plan := make(Plan[T], 0, len(cfg.Endpoints[endpoint].Stages[stage.String()].Groups)) + for _, groupCfg := range cfg.Endpoints[endpoint].Stages[stage.String()].Groups { + group := getGroup(getHookFn, groupCfg) + if len(group.Hooks) > 0 { + plan = append(plan, group) + } + } + + return plan +} + +func getGroup[T any](getHookFn hookFn[T], cfg config.HookExecutionGroup) Group[T] { + group := Group[T]{ + Timeout: time.Duration(cfg.Timeout) * time.Millisecond, + Hooks: make([]HookWrapper[T], 0, len(cfg.HookSequence)), + } + + for _, hookCfg := range cfg.HookSequence { + if h, ok := getHookFn(hookCfg.ModuleCode); ok { + group.Hooks = append(group.Hooks, HookWrapper[T]{Module: hookCfg.ModuleCode, Code: hookCfg.HookImplCode, Hook: h}) + } else { + glog.Warningf("Not found hook while building hook execution plan: %s %s", hookCfg.ModuleCode, hookCfg.HookImplCode) + } + } + + return group +} diff --git a/hooks/plan_test.go b/hooks/plan_test.go new file mode 100644 index 00000000000..5d2a504f0d1 --- /dev/null +++ b/hooks/plan_test.go @@ -0,0 +1,872 @@ +package hooks + +import ( + "context" + "encoding/json" + "testing" + "time" + + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/hooks/hookstage" + "github.com/stretchr/testify/assert" +) + +func TestNewExecutionPlanBuilder(t *testing.T) { + enabledConfig := config.Hooks{Enabled: true} + testCases := map[string]struct { + givenConfig config.Hooks + expectedPlanBuilder ExecutionPlanBuilder + }{ + "Real plan builder returned when hooks enabled": { + givenConfig: enabledConfig, + expectedPlanBuilder: PlanBuilder{hooks: enabledConfig}, + }, + "Empty plan builder returned when hooks disabled": { + givenConfig: config.Hooks{Enabled: false}, + expectedPlanBuilder: EmptyPlanBuilder{}, + }, + } + + for name, test := range testCases { + t.Run(name, func(t *testing.T) { + gotPlanBuilder := NewExecutionPlanBuilder(test.givenConfig, nil) + assert.Equal(t, test.expectedPlanBuilder, gotPlanBuilder) + }) + } +} + +func TestPlanForEntrypointStage(t *testing.T) { + const group1 string = `{"timeout": 5, "hook_sequence": [{"module_code": "foobar", "hook_impl_code": "foo"}]}` + const group2 string = `{"timeout": 10, "hook_sequence": [{"module_code": "foobar", "hook_impl_code": "bar"}, {"module_code": "ortb2blocking", "hook_impl_code": "block_request"}]}` + const planData1 string = `{"endpoints": {"/openrtb2/auction": {"stages": {"entrypoint": {"groups": [` + group1 + `]}}}}}` + const planData2 string = `{"endpoints": {"/openrtb2/auction": {"stages": {"entrypoint": {"groups": [` + group2 + `,` + group1 + `]}}}, "/openrtb2/amp": {"stages": {"entrypoint": {"groups": [` + group1 + `]}}}}}` + + testCases := map[string]struct { + givenEndpoint string + givenHostPlanData []byte + givenDefaultAccountPlanData []byte + givenHooks map[string]interface{} + expectedPlan Plan[hookstage.Entrypoint] + }{ + "Host and default-account execution plans successfully merged": { + givenEndpoint: "/openrtb2/auction", + givenHostPlanData: []byte(planData1), + givenDefaultAccountPlanData: []byte(planData2), + givenHooks: map[string]interface{}{ + "foobar": fakeEntrypointHook{}, + "ortb2blocking": fakeEntrypointHook{}, + }, + expectedPlan: Plan[hookstage.Entrypoint]{ + // first group from host-level plan + Group[hookstage.Entrypoint]{ + Timeout: 5 * time.Millisecond, + Hooks: []HookWrapper[hookstage.Entrypoint]{ + {Module: "foobar", Code: "foo", Hook: fakeEntrypointHook{}}, + }, + }, + // then groups from the account-level plan + Group[hookstage.Entrypoint]{ + Timeout: 10 * time.Millisecond, + Hooks: []HookWrapper[hookstage.Entrypoint]{ + {Module: "foobar", Code: "bar", Hook: fakeEntrypointHook{}}, + {Module: "ortb2blocking", Code: "block_request", Hook: fakeEntrypointHook{}}, + }, + }, + Group[hookstage.Entrypoint]{ + Timeout: 5 * time.Millisecond, + Hooks: []HookWrapper[hookstage.Entrypoint]{ + {Module: "foobar", Code: "foo", Hook: fakeEntrypointHook{}}, + }, + }, + }, + }, + "Works with empty default-account-execution_plan": { + givenEndpoint: "/openrtb2/auction", + givenHostPlanData: []byte(planData1), + givenDefaultAccountPlanData: []byte(`{}`), + givenHooks: map[string]interface{}{"foobar": fakeEntrypointHook{}}, + expectedPlan: Plan[hookstage.Entrypoint]{ + Group[hookstage.Entrypoint]{ + Timeout: 5 * time.Millisecond, + Hooks: []HookWrapper[hookstage.Entrypoint]{ + {Module: "foobar", Code: "foo", Hook: fakeEntrypointHook{}}, + }, + }, + }, + }, + "Works with empty host-execution_plan": { + givenEndpoint: "/openrtb2/auction", + givenHostPlanData: []byte(`{}`), + givenDefaultAccountPlanData: []byte(planData1), + givenHooks: map[string]interface{}{"foobar": fakeEntrypointHook{}}, + expectedPlan: Plan[hookstage.Entrypoint]{ + Group[hookstage.Entrypoint]{ + Timeout: 5 * time.Millisecond, + Hooks: []HookWrapper[hookstage.Entrypoint]{ + {Module: "foobar", Code: "foo", Hook: fakeEntrypointHook{}}, + }, + }, + }, + }, + "Empty plan if hooks config not defined": { + givenEndpoint: "/openrtb2/auction", + givenHostPlanData: []byte(`{}`), + givenDefaultAccountPlanData: []byte(`{}`), + givenHooks: map[string]interface{}{"foobar": fakeEntrypointHook{}}, + expectedPlan: Plan[hookstage.Entrypoint]{}, + }, + "Empty plan if hook repository empty": { + givenEndpoint: "/openrtb2/auction", + givenHostPlanData: []byte(planData1), + givenDefaultAccountPlanData: []byte(`{}`), + givenHooks: nil, + expectedPlan: Plan[hookstage.Entrypoint]{}, + }, + } + + for name, test := range testCases { + t.Run(name, func(t *testing.T) { + planBuilder, err := getPlanBuilder(test.givenHooks, test.givenHostPlanData, test.givenDefaultAccountPlanData) + if assert.NoError(t, err, "Failed to init hook execution plan builder") { + assert.Equal(t, test.expectedPlan, planBuilder.PlanForEntrypointStage(test.givenEndpoint)) + } + }) + } +} + +func TestPlanForRawAuctionStage(t *testing.T) { + const group1 string = `{"timeout": 5, "hook_sequence": [{"module_code": "foobar", "hook_impl_code": "foo"}]}` + const group2 string = `{"timeout": 10, "hook_sequence": [{"module_code": "foobar", "hook_impl_code": "bar"}, {"module_code": "ortb2blocking", "hook_impl_code": "block_request"}]}` + const group3 string = `{"timeout": 15, "hook_sequence": [{"module_code": "prebid", "hook_impl_code": "baz"}]}` + const hostPlanData string = `{"endpoints": {"/openrtb2/auction": {"stages": {"raw_auction_request": {"groups": [` + group1 + `]}}}}}` + const defaultAccountPlanData string = `{"endpoints": {"/openrtb2/auction": {"stages": {"raw_auction_request": {"groups": [` + group2 + `,` + group1 + `]}}}, "/openrtb2/amp": {"stages": {"entrypoint": {"groups": [` + group1 + `]}}}}}` + const accountPlanData string = `{"execution_plan": {"endpoints": {"/openrtb2/auction": {"stages": {"raw_auction_request": {"groups": [` + group3 + `]}}}}}}` + + hooks := map[string]interface{}{ + "foobar": fakeRawAuctionHook{}, + "ortb2blocking": fakeRawAuctionHook{}, + "prebid": fakeRawAuctionHook{}, + } + + testCases := map[string]struct { + givenEndpoint string + givenHostPlanData []byte + givenDefaultAccountPlanData []byte + giveAccountPlanData []byte + givenHooks map[string]interface{} + expectedPlan Plan[hookstage.RawAuctionRequest] + }{ + "Account-specific execution plan rewrites default-account execution plan": { + givenEndpoint: "/openrtb2/auction", + givenHostPlanData: []byte(hostPlanData), + givenDefaultAccountPlanData: []byte(defaultAccountPlanData), + giveAccountPlanData: []byte(accountPlanData), + givenHooks: hooks, + expectedPlan: Plan[hookstage.RawAuctionRequest]{ + // first group from host-level plan + Group[hookstage.RawAuctionRequest]{ + Timeout: 5 * time.Millisecond, + Hooks: []HookWrapper[hookstage.RawAuctionRequest]{ + {Module: "foobar", Code: "foo", Hook: fakeRawAuctionHook{}}, + }, + }, + // then come groups from account-level plan (default-account-level plan ignored) + Group[hookstage.RawAuctionRequest]{ + Timeout: 15 * time.Millisecond, + Hooks: []HookWrapper[hookstage.RawAuctionRequest]{ + {Module: "prebid", Code: "baz", Hook: fakeRawAuctionHook{}}, + }, + }, + }, + }, + "Works with only account-specific plan": { + givenEndpoint: "/openrtb2/auction", + givenHostPlanData: []byte(`{}`), + givenDefaultAccountPlanData: []byte(`{}`), + giveAccountPlanData: []byte(accountPlanData), + givenHooks: hooks, + expectedPlan: Plan[hookstage.RawAuctionRequest]{ + Group[hookstage.RawAuctionRequest]{ + Timeout: 15 * time.Millisecond, + Hooks: []HookWrapper[hookstage.RawAuctionRequest]{ + {Module: "prebid", Code: "baz", Hook: fakeRawAuctionHook{}}, + }, + }, + }, + }, + "Works with empty account-specific execution plan": { + givenEndpoint: "/openrtb2/auction", + givenHostPlanData: []byte(hostPlanData), + givenDefaultAccountPlanData: []byte(defaultAccountPlanData), + giveAccountPlanData: []byte(`{}`), + givenHooks: hooks, + expectedPlan: Plan[hookstage.RawAuctionRequest]{ + Group[hookstage.RawAuctionRequest]{ + Timeout: 5 * time.Millisecond, + Hooks: []HookWrapper[hookstage.RawAuctionRequest]{ + {Module: "foobar", Code: "foo", Hook: fakeRawAuctionHook{}}, + }, + }, + Group[hookstage.RawAuctionRequest]{ + Timeout: 10 * time.Millisecond, + Hooks: []HookWrapper[hookstage.RawAuctionRequest]{ + {Module: "foobar", Code: "bar", Hook: fakeRawAuctionHook{}}, + {Module: "ortb2blocking", Code: "block_request", Hook: fakeRawAuctionHook{}}, + }, + }, + Group[hookstage.RawAuctionRequest]{ + Timeout: 5 * time.Millisecond, + Hooks: []HookWrapper[hookstage.RawAuctionRequest]{ + {Module: "foobar", Code: "foo", Hook: fakeRawAuctionHook{}}, + }, + }, + }, + }, + } + + for name, test := range testCases { + t.Run(name, func(t *testing.T) { + account := new(config.Account) + if err := json.Unmarshal(test.giveAccountPlanData, &account.Hooks); err != nil { + t.Fatal(err) + } + + planBuilder, err := getPlanBuilder(test.givenHooks, test.givenHostPlanData, test.givenDefaultAccountPlanData) + if assert.NoError(t, err, "Failed to init hook execution plan builder") { + plan := planBuilder.PlanForRawAuctionStage(test.givenEndpoint, account) + assert.Equal(t, test.expectedPlan, plan) + } + }) + } +} + +func TestPlanForProcessedAuctionStage(t *testing.T) { + const group1 string = `{"timeout": 5, "hook_sequence": [{"module_code": "foobar", "hook_impl_code": "foo"}]}` + const group2 string = `{"timeout": 10, "hook_sequence": [{"module_code": "foobar", "hook_impl_code": "bar"}, {"module_code": "ortb2blocking", "hook_impl_code": "block_request"}]}` + const group3 string = `{"timeout": 15, "hook_sequence": [{"module_code": "prebid", "hook_impl_code": "baz"}]}` + const hostPlanData string = `{"endpoints": {"/openrtb2/auction": {"stages": {"processed_auction_request": {"groups": [` + group1 + `]}}}}}` + const defaultAccountPlanData string = `{"endpoints": {"/openrtb2/auction": {"stages": {"processed_auction_request": {"groups": [` + group2 + `,` + group1 + `]}}}, "/openrtb2/amp": {"stages": {"entrypoint": {"groups": [` + group1 + `]}}}}}` + const accountPlanData string = `{"execution_plan": {"endpoints": {"/openrtb2/auction": {"stages": {"processed_auction_request": {"groups": [` + group3 + `]}}}}}}` + + hooks := map[string]interface{}{ + "foobar": fakeProcessedAuctionHook{}, + "ortb2blocking": fakeProcessedAuctionHook{}, + "prebid": fakeProcessedAuctionHook{}, + } + + testCases := map[string]struct { + givenEndpoint string + givenHostPlanData []byte + givenDefaultAccountPlanData []byte + giveAccountPlanData []byte + givenHooks map[string]interface{} + expectedPlan Plan[hookstage.ProcessedAuctionRequest] + }{ + "Account-specific execution plan rewrites default-account execution plan": { + givenEndpoint: "/openrtb2/auction", + givenHostPlanData: []byte(hostPlanData), + givenDefaultAccountPlanData: []byte(defaultAccountPlanData), + giveAccountPlanData: []byte(accountPlanData), + givenHooks: hooks, + expectedPlan: Plan[hookstage.ProcessedAuctionRequest]{ + // first group from host-level plan + Group[hookstage.ProcessedAuctionRequest]{ + Timeout: 5 * time.Millisecond, + Hooks: []HookWrapper[hookstage.ProcessedAuctionRequest]{ + {Module: "foobar", Code: "foo", Hook: fakeProcessedAuctionHook{}}, + }, + }, + // then come groups from account-level plan (default-account-level plan ignored) + Group[hookstage.ProcessedAuctionRequest]{ + Timeout: 15 * time.Millisecond, + Hooks: []HookWrapper[hookstage.ProcessedAuctionRequest]{ + {Module: "prebid", Code: "baz", Hook: fakeProcessedAuctionHook{}}, + }, + }, + }, + }, + "Works with only account-specific plan": { + givenEndpoint: "/openrtb2/auction", + givenHostPlanData: []byte(`{}`), + givenDefaultAccountPlanData: []byte(`{}`), + giveAccountPlanData: []byte(accountPlanData), + givenHooks: hooks, + expectedPlan: Plan[hookstage.ProcessedAuctionRequest]{ + Group[hookstage.ProcessedAuctionRequest]{ + Timeout: 15 * time.Millisecond, + Hooks: []HookWrapper[hookstage.ProcessedAuctionRequest]{ + {Module: "prebid", Code: "baz", Hook: fakeProcessedAuctionHook{}}, + }, + }, + }, + }, + "Works with empty account-specific execution plan": { + givenEndpoint: "/openrtb2/auction", + givenHostPlanData: []byte(hostPlanData), + givenDefaultAccountPlanData: []byte(defaultAccountPlanData), + giveAccountPlanData: []byte(`{}`), + givenHooks: hooks, + expectedPlan: Plan[hookstage.ProcessedAuctionRequest]{ + Group[hookstage.ProcessedAuctionRequest]{ + Timeout: 5 * time.Millisecond, + Hooks: []HookWrapper[hookstage.ProcessedAuctionRequest]{ + {Module: "foobar", Code: "foo", Hook: fakeProcessedAuctionHook{}}, + }, + }, + Group[hookstage.ProcessedAuctionRequest]{ + Timeout: 10 * time.Millisecond, + Hooks: []HookWrapper[hookstage.ProcessedAuctionRequest]{ + {Module: "foobar", Code: "bar", Hook: fakeProcessedAuctionHook{}}, + {Module: "ortb2blocking", Code: "block_request", Hook: fakeProcessedAuctionHook{}}, + }, + }, + Group[hookstage.ProcessedAuctionRequest]{ + Timeout: 5 * time.Millisecond, + Hooks: []HookWrapper[hookstage.ProcessedAuctionRequest]{ + {Module: "foobar", Code: "foo", Hook: fakeProcessedAuctionHook{}}, + }, + }, + }, + }, + } + + for name, test := range testCases { + t.Run(name, func(t *testing.T) { + account := new(config.Account) + if err := json.Unmarshal(test.giveAccountPlanData, &account.Hooks); err != nil { + t.Fatal(err) + } + + planBuilder, err := getPlanBuilder(test.givenHooks, test.givenHostPlanData, test.givenDefaultAccountPlanData) + if assert.NoError(t, err, "Failed to init hook execution plan builder") { + plan := planBuilder.PlanForProcessedAuctionStage(test.givenEndpoint, account) + assert.Equal(t, test.expectedPlan, plan) + } + }) + } +} + +func TestPlanForBidderRequestStage(t *testing.T) { + const group1 string = `{"timeout": 5, "hook_sequence": [{"module_code": "foobar", "hook_impl_code": "foo"}]}` + const group2 string = `{"timeout": 10, "hook_sequence": [{"module_code": "foobar", "hook_impl_code": "bar"}, {"module_code": "ortb2blocking", "hook_impl_code": "block_request"}]}` + const group3 string = `{"timeout": 15, "hook_sequence": [{"module_code": "prebid", "hook_impl_code": "baz"}]}` + const hostPlanData string = `{"endpoints": {"/openrtb2/auction": {"stages": {"bidder_request": {"groups": [` + group1 + `]}}}}}` + const defaultAccountPlanData string = `{"endpoints": {"/openrtb2/auction": {"stages": {"bidder_request": {"groups": [` + group2 + `,` + group1 + `]}}}, "/openrtb2/amp": {"stages": {"entrypoint": {"groups": [` + group1 + `]}}}}}` + const accountPlanData string = `{"execution_plan": {"endpoints": {"/openrtb2/auction": {"stages": {"bidder_request": {"groups": [` + group3 + `]}}}}}}` + + hooks := map[string]interface{}{ + "foobar": fakeBidderRequestHook{}, + "ortb2blocking": fakeBidderRequestHook{}, + "prebid": fakeBidderRequestHook{}, + } + + testCases := map[string]struct { + givenEndpoint string + givenHostPlanData []byte + givenDefaultAccountPlanData []byte + giveAccountPlanData []byte + givenHooks map[string]interface{} + expectedPlan Plan[hookstage.BidderRequest] + }{ + "Account-specific execution plan rewrites default-account execution plan": { + givenEndpoint: "/openrtb2/auction", + givenHostPlanData: []byte(hostPlanData), + givenDefaultAccountPlanData: []byte(defaultAccountPlanData), + giveAccountPlanData: []byte(accountPlanData), + givenHooks: hooks, + expectedPlan: Plan[hookstage.BidderRequest]{ + // first group from host-level plan + Group[hookstage.BidderRequest]{ + Timeout: 5 * time.Millisecond, + Hooks: []HookWrapper[hookstage.BidderRequest]{ + {Module: "foobar", Code: "foo", Hook: fakeBidderRequestHook{}}, + }, + }, + // then come groups from account-level plan (default-account-level plan ignored) + Group[hookstage.BidderRequest]{ + Timeout: 15 * time.Millisecond, + Hooks: []HookWrapper[hookstage.BidderRequest]{ + {Module: "prebid", Code: "baz", Hook: fakeBidderRequestHook{}}, + }, + }, + }, + }, + "Works with only account-specific plan": { + givenEndpoint: "/openrtb2/auction", + givenHostPlanData: []byte(`{}`), + givenDefaultAccountPlanData: []byte(`{}`), + giveAccountPlanData: []byte(accountPlanData), + givenHooks: hooks, + expectedPlan: Plan[hookstage.BidderRequest]{ + Group[hookstage.BidderRequest]{ + Timeout: 15 * time.Millisecond, + Hooks: []HookWrapper[hookstage.BidderRequest]{ + {Module: "prebid", Code: "baz", Hook: fakeBidderRequestHook{}}, + }, + }, + }, + }, + "Works with empty account-specific execution plan": { + givenEndpoint: "/openrtb2/auction", + givenHostPlanData: []byte(hostPlanData), + givenDefaultAccountPlanData: []byte(defaultAccountPlanData), + giveAccountPlanData: []byte(`{}`), + givenHooks: hooks, + expectedPlan: Plan[hookstage.BidderRequest]{ + Group[hookstage.BidderRequest]{ + Timeout: 5 * time.Millisecond, + Hooks: []HookWrapper[hookstage.BidderRequest]{ + {Module: "foobar", Code: "foo", Hook: fakeBidderRequestHook{}}, + }, + }, + Group[hookstage.BidderRequest]{ + Timeout: 10 * time.Millisecond, + Hooks: []HookWrapper[hookstage.BidderRequest]{ + {Module: "foobar", Code: "bar", Hook: fakeBidderRequestHook{}}, + {Module: "ortb2blocking", Code: "block_request", Hook: fakeBidderRequestHook{}}, + }, + }, + Group[hookstage.BidderRequest]{ + Timeout: 5 * time.Millisecond, + Hooks: []HookWrapper[hookstage.BidderRequest]{ + {Module: "foobar", Code: "foo", Hook: fakeBidderRequestHook{}}, + }, + }, + }, + }, + } + + for name, test := range testCases { + t.Run(name, func(t *testing.T) { + account := new(config.Account) + if err := json.Unmarshal(test.giveAccountPlanData, &account.Hooks); err != nil { + t.Fatal(err) + } + + planBuilder, err := getPlanBuilder(test.givenHooks, test.givenHostPlanData, test.givenDefaultAccountPlanData) + if assert.NoError(t, err, "Failed to init hook execution plan builder") { + plan := planBuilder.PlanForBidderRequestStage(test.givenEndpoint, account) + assert.Equal(t, test.expectedPlan, plan) + } + }) + } +} + +func TestPlanForRawBidderResponseStage(t *testing.T) { + const group1 string = `{"timeout": 5, "hook_sequence": [{"module_code": "foobar", "hook_impl_code": "foo"}]}` + const group2 string = `{"timeout": 10, "hook_sequence": [{"module_code": "foobar", "hook_impl_code": "bar"}, {"module_code": "ortb2blocking", "hook_impl_code": "block_request"}]}` + const group3 string = `{"timeout": 15, "hook_sequence": [{"module_code": "prebid", "hook_impl_code": "baz"}]}` + const hostPlanData string = `{"endpoints": {"/openrtb2/auction": {"stages": {"raw_bidder_response": {"groups": [` + group1 + `]}}}}}` + const defaultAccountPlanData string = `{"endpoints": {"/openrtb2/auction": {"stages": {"raw_bidder_response": {"groups": [` + group2 + `,` + group1 + `]}}}, "/openrtb2/amp": {"stages": {"entrypoint": {"groups": [` + group1 + `]}}}}}` + const accountPlanData string = `{"execution_plan": {"endpoints": {"/openrtb2/auction": {"stages": {"raw_bidder_response": {"groups": [` + group3 + `]}}}}}}` + + hooks := map[string]interface{}{ + "foobar": fakeRawBidderResponseHook{}, + "ortb2blocking": fakeRawBidderResponseHook{}, + "prebid": fakeRawBidderResponseHook{}, + } + + testCases := map[string]struct { + givenEndpoint string + givenHostPlanData []byte + givenDefaultAccountPlanData []byte + giveAccountPlanData []byte + givenHooks map[string]interface{} + expectedPlan Plan[hookstage.RawBidderResponse] + }{ + "Account-specific execution plan rewrites default-account execution plan": { + givenEndpoint: "/openrtb2/auction", + givenHostPlanData: []byte(hostPlanData), + givenDefaultAccountPlanData: []byte(defaultAccountPlanData), + giveAccountPlanData: []byte(accountPlanData), + givenHooks: hooks, + expectedPlan: Plan[hookstage.RawBidderResponse]{ + // first group from host-level plan + Group[hookstage.RawBidderResponse]{ + Timeout: 5 * time.Millisecond, + Hooks: []HookWrapper[hookstage.RawBidderResponse]{ + {Module: "foobar", Code: "foo", Hook: fakeRawBidderResponseHook{}}, + }, + }, + // then come groups from account-level plan (default-account-level plan ignored) + Group[hookstage.RawBidderResponse]{ + Timeout: 15 * time.Millisecond, + Hooks: []HookWrapper[hookstage.RawBidderResponse]{ + {Module: "prebid", Code: "baz", Hook: fakeRawBidderResponseHook{}}, + }, + }, + }, + }, + "Works with only account-specific plan": { + givenEndpoint: "/openrtb2/auction", + givenHostPlanData: []byte(`{}`), + givenDefaultAccountPlanData: []byte(`{}`), + giveAccountPlanData: []byte(accountPlanData), + givenHooks: hooks, + expectedPlan: Plan[hookstage.RawBidderResponse]{ + Group[hookstage.RawBidderResponse]{ + Timeout: 15 * time.Millisecond, + Hooks: []HookWrapper[hookstage.RawBidderResponse]{ + {Module: "prebid", Code: "baz", Hook: fakeRawBidderResponseHook{}}, + }, + }, + }, + }, + "Works with empty account-specific execution plan": { + givenEndpoint: "/openrtb2/auction", + givenHostPlanData: []byte(hostPlanData), + givenDefaultAccountPlanData: []byte(defaultAccountPlanData), + giveAccountPlanData: []byte(`{}`), + givenHooks: hooks, + expectedPlan: Plan[hookstage.RawBidderResponse]{ + Group[hookstage.RawBidderResponse]{ + Timeout: 5 * time.Millisecond, + Hooks: []HookWrapper[hookstage.RawBidderResponse]{ + {Module: "foobar", Code: "foo", Hook: fakeRawBidderResponseHook{}}, + }, + }, + Group[hookstage.RawBidderResponse]{ + Timeout: 10 * time.Millisecond, + Hooks: []HookWrapper[hookstage.RawBidderResponse]{ + {Module: "foobar", Code: "bar", Hook: fakeRawBidderResponseHook{}}, + {Module: "ortb2blocking", Code: "block_request", Hook: fakeRawBidderResponseHook{}}, + }, + }, + Group[hookstage.RawBidderResponse]{ + Timeout: 5 * time.Millisecond, + Hooks: []HookWrapper[hookstage.RawBidderResponse]{ + {Module: "foobar", Code: "foo", Hook: fakeRawBidderResponseHook{}}, + }, + }, + }, + }, + } + + for name, test := range testCases { + t.Run(name, func(t *testing.T) { + account := new(config.Account) + if err := json.Unmarshal(test.giveAccountPlanData, &account.Hooks); err != nil { + t.Fatal(err) + } + + planBuilder, err := getPlanBuilder(test.givenHooks, test.givenHostPlanData, test.givenDefaultAccountPlanData) + if assert.NoError(t, err, "Failed to init hook execution plan builder") { + plan := planBuilder.PlanForRawBidderResponseStage(test.givenEndpoint, account) + assert.Equal(t, test.expectedPlan, plan) + } + }) + } +} + +func TestPlanForAllProcessedBidResponsesStage(t *testing.T) { + const group1 string = `{"timeout": 5, "hook_sequence": [{"module_code": "foobar", "hook_impl_code": "foo"}]}` + const group2 string = `{"timeout": 10, "hook_sequence": [{"module_code": "foobar", "hook_impl_code": "bar"}, {"module_code": "ortb2blocking", "hook_impl_code": "block_request"}]}` + const group3 string = `{"timeout": 15, "hook_sequence": [{"module_code": "prebid", "hook_impl_code": "baz"}]}` + const hostPlanData string = `{"endpoints": {"/openrtb2/auction": {"stages": {"all_processed_bid_responses": {"groups": [` + group1 + `]}}}}}` + const defaultAccountPlanData string = `{"endpoints": {"/openrtb2/auction": {"stages": {"all_processed_bid_responses": {"groups": [` + group2 + `,` + group1 + `]}}}, "/openrtb2/amp": {"stages": {"entrypoint": {"groups": [` + group1 + `]}}}}}` + const accountPlanData string = `{"execution_plan": {"endpoints": {"/openrtb2/auction": {"stages": {"all_processed_bid_responses": {"groups": [` + group3 + `]}}}}}}` + + hooks := map[string]interface{}{ + "foobar": fakeAllProcessedBidResponsesHook{}, + "ortb2blocking": fakeAllProcessedBidResponsesHook{}, + "prebid": fakeAllProcessedBidResponsesHook{}, + } + + testCases := map[string]struct { + givenEndpoint string + givenHostPlanData []byte + givenDefaultAccountPlanData []byte + giveAccountPlanData []byte + givenHooks map[string]interface{} + expectedPlan Plan[hookstage.AllProcessedBidResponses] + }{ + "Account-specific execution plan rewrites default-account execution plan": { + givenEndpoint: "/openrtb2/auction", + givenHostPlanData: []byte(hostPlanData), + givenDefaultAccountPlanData: []byte(defaultAccountPlanData), + giveAccountPlanData: []byte(accountPlanData), + givenHooks: hooks, + expectedPlan: Plan[hookstage.AllProcessedBidResponses]{ + // first group from host-level plan + Group[hookstage.AllProcessedBidResponses]{ + Timeout: 5 * time.Millisecond, + Hooks: []HookWrapper[hookstage.AllProcessedBidResponses]{ + {Module: "foobar", Code: "foo", Hook: fakeAllProcessedBidResponsesHook{}}, + }, + }, + // then come groups from account-level plan (default-account-level plan ignored) + Group[hookstage.AllProcessedBidResponses]{ + Timeout: 15 * time.Millisecond, + Hooks: []HookWrapper[hookstage.AllProcessedBidResponses]{ + {Module: "prebid", Code: "baz", Hook: fakeAllProcessedBidResponsesHook{}}, + }, + }, + }, + }, + "Works with only account-specific plan": { + givenEndpoint: "/openrtb2/auction", + givenHostPlanData: []byte(`{}`), + givenDefaultAccountPlanData: []byte(`{}`), + giveAccountPlanData: []byte(accountPlanData), + givenHooks: hooks, + expectedPlan: Plan[hookstage.AllProcessedBidResponses]{ + Group[hookstage.AllProcessedBidResponses]{ + Timeout: 15 * time.Millisecond, + Hooks: []HookWrapper[hookstage.AllProcessedBidResponses]{ + {Module: "prebid", Code: "baz", Hook: fakeAllProcessedBidResponsesHook{}}, + }, + }, + }, + }, + "Works with empty account-specific execution plan": { + givenEndpoint: "/openrtb2/auction", + givenHostPlanData: []byte(hostPlanData), + givenDefaultAccountPlanData: []byte(defaultAccountPlanData), + giveAccountPlanData: []byte(`{}`), + givenHooks: hooks, + expectedPlan: Plan[hookstage.AllProcessedBidResponses]{ + Group[hookstage.AllProcessedBidResponses]{ + Timeout: 5 * time.Millisecond, + Hooks: []HookWrapper[hookstage.AllProcessedBidResponses]{ + {Module: "foobar", Code: "foo", Hook: fakeAllProcessedBidResponsesHook{}}, + }, + }, + Group[hookstage.AllProcessedBidResponses]{ + Timeout: 10 * time.Millisecond, + Hooks: []HookWrapper[hookstage.AllProcessedBidResponses]{ + {Module: "foobar", Code: "bar", Hook: fakeAllProcessedBidResponsesHook{}}, + {Module: "ortb2blocking", Code: "block_request", Hook: fakeAllProcessedBidResponsesHook{}}, + }, + }, + Group[hookstage.AllProcessedBidResponses]{ + Timeout: 5 * time.Millisecond, + Hooks: []HookWrapper[hookstage.AllProcessedBidResponses]{ + {Module: "foobar", Code: "foo", Hook: fakeAllProcessedBidResponsesHook{}}, + }, + }, + }, + }, + } + + for name, test := range testCases { + t.Run(name, func(t *testing.T) { + account := new(config.Account) + if err := json.Unmarshal(test.giveAccountPlanData, &account.Hooks); err != nil { + t.Fatal(err) + } + + planBuilder, err := getPlanBuilder(test.givenHooks, test.givenHostPlanData, test.givenDefaultAccountPlanData) + if assert.NoError(t, err, "Failed to init hook execution plan builder") { + plan := planBuilder.PlanForAllProcessedBidResponsesStage(test.givenEndpoint, account) + assert.Equal(t, test.expectedPlan, plan) + } + }) + } +} + +func TestPlanForAuctionResponseStage(t *testing.T) { + const group1 string = `{"timeout": 5, "hook_sequence": [{"module_code": "foobar", "hook_impl_code": "foo"}]}` + const group2 string = `{"timeout": 10, "hook_sequence": [{"module_code": "foobar", "hook_impl_code": "bar"}, {"module_code": "ortb2blocking", "hook_impl_code": "block_request"}]}` + const group3 string = `{"timeout": 15, "hook_sequence": [{"module_code": "prebid", "hook_impl_code": "baz"}]}` + const hostPlanData string = `{"endpoints": {"/openrtb2/auction": {"stages": {"auction_response": {"groups": [` + group1 + `]}}}}}` + const defaultAccountPlanData string = `{"endpoints": {"/openrtb2/auction": {"stages": {"auction_response": {"groups": [` + group2 + `,` + group1 + `]}}}, "/openrtb2/amp": {"stages": {"entrypoint": {"groups": [` + group1 + `]}}}}}` + const accountPlanData string = `{"execution_plan": {"endpoints": {"/openrtb2/auction": {"stages": {"auction_response": {"groups": [` + group3 + `]}}}}}}` + + hooks := map[string]interface{}{ + "foobar": fakeAuctionResponseHook{}, + "ortb2blocking": fakeAuctionResponseHook{}, + "prebid": fakeAuctionResponseHook{}, + } + + testCases := map[string]struct { + givenEndpoint string + givenHostPlanData []byte + givenDefaultAccountPlanData []byte + giveAccountPlanData []byte + givenHooks map[string]interface{} + expectedPlan Plan[hookstage.AuctionResponse] + }{ + "Account-specific execution plan rewrites default-account execution plan": { + givenEndpoint: "/openrtb2/auction", + givenHostPlanData: []byte(hostPlanData), + givenDefaultAccountPlanData: []byte(defaultAccountPlanData), + giveAccountPlanData: []byte(accountPlanData), + givenHooks: hooks, + expectedPlan: Plan[hookstage.AuctionResponse]{ + // first group from host-level plan + Group[hookstage.AuctionResponse]{ + Timeout: 5 * time.Millisecond, + Hooks: []HookWrapper[hookstage.AuctionResponse]{ + {Module: "foobar", Code: "foo", Hook: fakeAuctionResponseHook{}}, + }, + }, + // then come groups from account-level plan (default-account-level plan ignored) + Group[hookstage.AuctionResponse]{ + Timeout: 15 * time.Millisecond, + Hooks: []HookWrapper[hookstage.AuctionResponse]{ + {Module: "prebid", Code: "baz", Hook: fakeAuctionResponseHook{}}, + }, + }, + }, + }, + "Works with only account-specific plan": { + givenEndpoint: "/openrtb2/auction", + givenHostPlanData: []byte(`{}`), + givenDefaultAccountPlanData: []byte(`{}`), + giveAccountPlanData: []byte(accountPlanData), + givenHooks: hooks, + expectedPlan: Plan[hookstage.AuctionResponse]{ + Group[hookstage.AuctionResponse]{ + Timeout: 15 * time.Millisecond, + Hooks: []HookWrapper[hookstage.AuctionResponse]{ + {Module: "prebid", Code: "baz", Hook: fakeAuctionResponseHook{}}, + }, + }, + }, + }, + "Works with empty account-specific execution plan": { + givenEndpoint: "/openrtb2/auction", + givenHostPlanData: []byte(hostPlanData), + givenDefaultAccountPlanData: []byte(defaultAccountPlanData), + giveAccountPlanData: []byte(`{}`), + givenHooks: hooks, + expectedPlan: Plan[hookstage.AuctionResponse]{ + Group[hookstage.AuctionResponse]{ + Timeout: 5 * time.Millisecond, + Hooks: []HookWrapper[hookstage.AuctionResponse]{ + {Module: "foobar", Code: "foo", Hook: fakeAuctionResponseHook{}}, + }, + }, + Group[hookstage.AuctionResponse]{ + Timeout: 10 * time.Millisecond, + Hooks: []HookWrapper[hookstage.AuctionResponse]{ + {Module: "foobar", Code: "bar", Hook: fakeAuctionResponseHook{}}, + {Module: "ortb2blocking", Code: "block_request", Hook: fakeAuctionResponseHook{}}, + }, + }, + Group[hookstage.AuctionResponse]{ + Timeout: 5 * time.Millisecond, + Hooks: []HookWrapper[hookstage.AuctionResponse]{ + {Module: "foobar", Code: "foo", Hook: fakeAuctionResponseHook{}}, + }, + }, + }, + }, + } + + for name, test := range testCases { + t.Run(name, func(t *testing.T) { + account := new(config.Account) + if err := json.Unmarshal(test.giveAccountPlanData, &account.Hooks); err != nil { + t.Fatal(err) + } + + planBuilder, err := getPlanBuilder(test.givenHooks, test.givenHostPlanData, test.givenDefaultAccountPlanData) + if assert.NoError(t, err, "Failed to init hook execution plan builder") { + plan := planBuilder.PlanForAuctionResponseStage(test.givenEndpoint, account) + assert.Equal(t, test.expectedPlan, plan) + } + }) + } +} + +func getPlanBuilder( + moduleHooks map[string]interface{}, + hostPlanData, accountPlanData []byte, +) (ExecutionPlanBuilder, error) { + var err error + var hooks config.Hooks + var hostPlan config.HookExecutionPlan + var defaultAccountPlan config.HookExecutionPlan + + err = json.Unmarshal(hostPlanData, &hostPlan) + if err != nil { + return nil, err + } + + err = json.Unmarshal(accountPlanData, &defaultAccountPlan) + if err != nil { + return nil, err + } + + hooks.Enabled = true + hooks.HostExecutionPlan = hostPlan + hooks.DefaultAccountExecutionPlan = defaultAccountPlan + + repo, err := NewHookRepository(moduleHooks) + if err != nil { + return nil, err + } + + return NewExecutionPlanBuilder(hooks, repo), nil +} + +type fakeEntrypointHook struct{} + +func (h fakeEntrypointHook) HandleEntrypointHook( + _ context.Context, + _ hookstage.ModuleInvocationContext, + _ hookstage.EntrypointPayload, +) (hookstage.HookResult[hookstage.EntrypointPayload], error) { + return hookstage.HookResult[hookstage.EntrypointPayload]{}, nil +} + +type fakeRawAuctionHook struct{} + +func (f fakeRawAuctionHook) HandleRawAuctionHook( + _ context.Context, + _ hookstage.ModuleInvocationContext, + _ hookstage.RawAuctionRequestPayload, +) (hookstage.HookResult[hookstage.RawAuctionRequestPayload], error) { + return hookstage.HookResult[hookstage.RawAuctionRequestPayload]{}, nil +} + +type fakeProcessedAuctionHook struct{} + +func (f fakeProcessedAuctionHook) HandleProcessedAuctionHook( + _ context.Context, + _ hookstage.ModuleInvocationContext, + _ hookstage.ProcessedAuctionRequestPayload, +) (hookstage.HookResult[hookstage.ProcessedAuctionRequestPayload], error) { + return hookstage.HookResult[hookstage.ProcessedAuctionRequestPayload]{}, nil +} + +type fakeBidderRequestHook struct{} + +func (f fakeBidderRequestHook) HandleBidderRequestHook( + _ context.Context, + _ hookstage.ModuleInvocationContext, + _ hookstage.BidderRequestPayload, +) (hookstage.HookResult[hookstage.BidderRequestPayload], error) { + return hookstage.HookResult[hookstage.BidderRequestPayload]{}, nil +} + +type fakeRawBidderResponseHook struct{} + +func (f fakeRawBidderResponseHook) HandleRawBidderResponseHook( + _ context.Context, + _ hookstage.ModuleInvocationContext, + _ hookstage.RawBidderResponsePayload, +) (hookstage.HookResult[hookstage.RawBidderResponsePayload], error) { + return hookstage.HookResult[hookstage.RawBidderResponsePayload]{}, nil +} + +type fakeAllProcessedBidResponsesHook struct{} + +func (f fakeAllProcessedBidResponsesHook) HandleAllProcessedBidResponsesHook( + _ context.Context, + _ hookstage.ModuleInvocationContext, + _ hookstage.AllProcessedBidResponsesPayload, +) (hookstage.HookResult[hookstage.AllProcessedBidResponsesPayload], error) { + return hookstage.HookResult[hookstage.AllProcessedBidResponsesPayload]{}, nil +} + +type fakeAuctionResponseHook struct{} + +func (f fakeAuctionResponseHook) HandleAuctionResponseHook( + _ context.Context, + _ hookstage.ModuleInvocationContext, + _ hookstage.AuctionResponsePayload, +) (hookstage.HookResult[hookstage.AuctionResponsePayload], error) { + return hookstage.HookResult[hookstage.AuctionResponsePayload]{}, nil +} diff --git a/hooks/repo.go b/hooks/repo.go new file mode 100644 index 00000000000..033a3f8f7a0 --- /dev/null +++ b/hooks/repo.go @@ -0,0 +1,158 @@ +package hooks + +import ( + "fmt" + "github.com/prebid/prebid-server/hooks/hookstage" +) + +// HookRepository is the interface that exposes methods +// that return instance of the certain hook interface. +// +// Each method accepts hook ID and returns hook interface +// registered under this ID and true if hook found +// otherwise nil value returned with the false, +// indicating not found hook for this ID. +type HookRepository interface { + GetEntrypointHook(id string) (hookstage.Entrypoint, bool) + GetRawAuctionHook(id string) (hookstage.RawAuctionRequest, bool) + GetProcessedAuctionHook(id string) (hookstage.ProcessedAuctionRequest, bool) + GetBidderRequestHook(id string) (hookstage.BidderRequest, bool) + GetRawBidderResponseHook(id string) (hookstage.RawBidderResponse, bool) + GetAllProcessedBidResponsesHook(id string) (hookstage.AllProcessedBidResponses, bool) + GetAuctionResponseHook(id string) (hookstage.AuctionResponse, bool) +} + +// NewHookRepository returns a new instance of the HookRepository interface. +// +// The hooks argument represents a mapping of hook IDs to types +// implementing at least one of the available hook interfaces, see [hookstage] pkg. +// +// Error returned if provided interface doesn't implement any hook interface +// or hook with same ID already exists. +func NewHookRepository(hooks map[string]interface{}) (HookRepository, error) { + repo := new(hookRepository) + for id, hook := range hooks { + if err := repo.add(id, hook); err != nil { + return nil, err + } + } + + return repo, nil +} + +type hookRepository struct { + entrypointHooks map[string]hookstage.Entrypoint + rawAuctionHooks map[string]hookstage.RawAuctionRequest + processedAuctionHooks map[string]hookstage.ProcessedAuctionRequest + bidderRequestHooks map[string]hookstage.BidderRequest + rawBidderResponseHooks map[string]hookstage.RawBidderResponse + allProcessedBidResponseHooks map[string]hookstage.AllProcessedBidResponses + auctionResponseHooks map[string]hookstage.AuctionResponse +} + +func (r *hookRepository) GetEntrypointHook(id string) (h hookstage.Entrypoint, ok bool) { + return getHook(r.entrypointHooks, id) +} + +func (r *hookRepository) GetRawAuctionHook(id string) (hookstage.RawAuctionRequest, bool) { + return getHook(r.rawAuctionHooks, id) +} + +func (r *hookRepository) GetProcessedAuctionHook(id string) (hookstage.ProcessedAuctionRequest, bool) { + return getHook(r.processedAuctionHooks, id) +} + +func (r *hookRepository) GetBidderRequestHook(id string) (hookstage.BidderRequest, bool) { + return getHook(r.bidderRequestHooks, id) +} + +func (r *hookRepository) GetRawBidderResponseHook(id string) (hookstage.RawBidderResponse, bool) { + return getHook(r.rawBidderResponseHooks, id) +} + +func (r *hookRepository) GetAllProcessedBidResponsesHook(id string) (hookstage.AllProcessedBidResponses, bool) { + return getHook(r.allProcessedBidResponseHooks, id) +} + +func (r *hookRepository) GetAuctionResponseHook(id string) (hookstage.AuctionResponse, bool) { + return getHook(r.auctionResponseHooks, id) +} + +func (r *hookRepository) add(id string, hook interface{}) error { + var hasAnyHooks bool + var err error + + if h, ok := hook.(hookstage.Entrypoint); ok { + hasAnyHooks = true + if r.entrypointHooks, err = addHook(r.entrypointHooks, h, id); err != nil { + return err + } + } + + if h, ok := hook.(hookstage.RawAuctionRequest); ok { + hasAnyHooks = true + if r.rawAuctionHooks, err = addHook(r.rawAuctionHooks, h, id); err != nil { + return err + } + } + + if h, ok := hook.(hookstage.ProcessedAuctionRequest); ok { + hasAnyHooks = true + if r.processedAuctionHooks, err = addHook(r.processedAuctionHooks, h, id); err != nil { + return err + } + } + + if h, ok := hook.(hookstage.BidderRequest); ok { + hasAnyHooks = true + if r.bidderRequestHooks, err = addHook(r.bidderRequestHooks, h, id); err != nil { + return err + } + } + + if h, ok := hook.(hookstage.RawBidderResponse); ok { + hasAnyHooks = true + if r.rawBidderResponseHooks, err = addHook(r.rawBidderResponseHooks, h, id); err != nil { + return err + } + } + + if h, ok := hook.(hookstage.AllProcessedBidResponses); ok { + hasAnyHooks = true + if r.allProcessedBidResponseHooks, err = addHook(r.allProcessedBidResponseHooks, h, id); err != nil { + return err + } + } + + if h, ok := hook.(hookstage.AuctionResponse); ok { + hasAnyHooks = true + if r.auctionResponseHooks, err = addHook(r.auctionResponseHooks, h, id); err != nil { + return err + } + } + + if !hasAnyHooks { + return fmt.Errorf(`hook "%s" does not implement any supported hook interface`, id) + } + + return nil +} + +func getHook[T any](hooks map[string]T, id string) (T, bool) { + hook, ok := hooks[id] + return hook, ok +} + +func addHook[T any](hooks map[string]T, hook T, id string) (map[string]T, error) { + if hooks == nil { + hooks = make(map[string]T) + } + + if _, ok := hooks[id]; ok { + return nil, fmt.Errorf(`hook of type "%T" with id "%s" already registered`, new(T), id) + } + + hooks[id] = hook + + return hooks, nil +} diff --git a/hooks/repo_test.go b/hooks/repo_test.go new file mode 100644 index 00000000000..ae523c98773 --- /dev/null +++ b/hooks/repo_test.go @@ -0,0 +1,76 @@ +package hooks + +import ( + "context" + "fmt" + "testing" + + "github.com/prebid/prebid-server/hooks/hookstage" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewHookRepository(t *testing.T) { + id := "foobar" + testCases := map[string]struct { + isFound bool + providedHook interface{} + expectedHook interface{} + expectedErr error + getHookFn func(HookRepository) (interface{}, bool) + }{ + "Added hook returns": { + isFound: true, + providedHook: hook{}, + expectedHook: hook{}, + expectedErr: nil, + getHookFn: func(repo HookRepository) (interface{}, bool) { + return repo.GetEntrypointHook(id) + }, + }, + "Not found hook": { + isFound: false, + providedHook: hook{}, + expectedHook: nil, + expectedErr: nil, + getHookFn: func(repo HookRepository) (interface{}, bool) { + return repo.GetRawAuctionHook(id) // ask for not implemented hook + }, + }, + "Fails to add type that does not implement any hook interface": { + providedHook: struct{}{}, + expectedErr: fmt.Errorf(`hook "%s" does not implement any supported hook interface`, id), + }, + } + + for name, test := range testCases { + t.Run(name, func(t *testing.T) { + repo, err := NewHookRepository(map[string]interface{}{id: test.providedHook}) + assert.Equal(t, test.expectedErr, err) + if test.expectedErr == nil { + hook, found := test.getHookFn(repo) + assert.Equal(t, test.isFound, found) + assert.Equal(t, test.expectedHook, hook) + } + }) + } +} + +func TestAddHook_FailsToAddHookOfSameTypeAndIdTwice(t *testing.T) { + id := "foobar" + hook := hook{} + repo := hookRepository{} + expectedErr := fmt.Errorf(`hook of type "%T" with id "%s" already registered`, new(hookstage.Entrypoint), id) + + err := repo.add(id, hook) + require.NoError(t, err, "failed to add hook") + + err = repo.add(id, hook) + assert.Equal(t, expectedErr, err) +} + +type hook struct{} + +func (h hook) HandleEntrypointHook(ctx context.Context, context hookstage.ModuleInvocationContext, payload hookstage.EntrypointPayload) (hookstage.HookResult[hookstage.EntrypointPayload], error) { + return hookstage.HookResult[hookstage.EntrypointPayload]{}, nil +} diff --git a/main.go b/main.go index c103863107d..a83266665f0 100644 --- a/main.go +++ b/main.go @@ -4,11 +4,13 @@ import ( "flag" "math/rand" "net/http" + "path/filepath" "runtime" "time" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/router" "github.com/prebid/prebid-server/server" "github.com/prebid/prebid-server/util/task" @@ -24,7 +26,16 @@ func init() { func main() { flag.Parse() // required for glog flags and testing package flags - cfg, err := loadConfig() + bidderInfoPath, err := filepath.Abs(infoDirectory) + if err != nil { + glog.Exitf("Unable to build configuration directory path: %v", err) + } + + bidderInfos, err := config.LoadBidderInfoFromDisk(bidderInfoPath) + if err != nil { + glog.Exitf("Unable to load bidder configurations: %v", err) + } + cfg, err := loadConfig(bidderInfos) if err != nil { glog.Exitf("Configuration could not be loaded or did not pass validation: %v", err) } @@ -44,11 +55,12 @@ func main() { } const configFileName = "pbs" +const infoDirectory = "./static/bidder-info" -func loadConfig() (*config.Configuration, error) { +func loadConfig(bidderInfos config.BidderInfos) (*config.Configuration, error) { v := viper.New() - config.SetupViper(v, configFileName) - return config.New(v) + config.SetupViper(v, configFileName, bidderInfos) + return config.New(v, bidderInfos, openrtb_ext.NormalizeBidderName) } func serve(cfg *config.Configuration) error { diff --git a/main_test.go b/main_test.go index 70eea2825f0..25812ba96ab 100644 --- a/main_test.go +++ b/main_test.go @@ -40,18 +40,18 @@ func forceEnv(t *testing.T, key string, val string) func() { // Test the viper setup func TestViperInit(t *testing.T) { v := viper.New() - config.SetupViper(v, "") + config.SetupViper(v, "", nil) compareStrings(t, "Viper error: external_url expected to be %s, found %s", "http://localhost:8000", v.Get("external_url").(string)) - compareStrings(t, "Viper error: adapters.pulsepoint.endpoint expected to be %s, found %s", "http://bid.contextweb.com/header/s/ortb/prebid-s2s", v.Get("adapters.pulsepoint.endpoint").(string)) + compareStrings(t, "Viper error: accounts.filesystem.directorypath expected to be %s, found %s", "./stored_requests/data/by_id", v.Get("accounts.filesystem.directorypath").(string)) } func TestViperEnv(t *testing.T) { v := viper.New() - config.SetupViper(v, "") + config.SetupViper(v, "", nil) port := forceEnv(t, "PBS_PORT", "7777") defer port() - endpt := forceEnv(t, "PBS_ADAPTERS_PUBMATIC_ENDPOINT", "not_an_endpoint") + endpt := forceEnv(t, "PBS_EXPERIMENT_ADSCERT_INPROCESS_ORIGIN", "not_an_endpoint") defer endpt() ttl := forceEnv(t, "PBS_HOST_COOKIE_TTL_DAYS", "60") @@ -61,7 +61,7 @@ func TestViperEnv(t *testing.T) { defer ipv4Networks() assert.Equal(t, 7777, v.Get("port"), "Basic Config") - assert.Equal(t, "not_an_endpoint", v.Get("adapters.pubmatic.endpoint"), "Nested Config") + assert.Equal(t, "not_an_endpoint", v.Get("experiment.adscert.inprocess.origin"), "Nested Config") assert.Equal(t, 60, v.Get("host_cookie.ttl_days"), "Config With Underscores") assert.ElementsMatch(t, []string{"1.1.1.1/24", "2.2.2.2/24"}, v.Get("request_validation.ipv4_private_networks"), "Arrays") } diff --git a/metrics/config/metrics.go b/metrics/config/metrics.go index d8df48fa059..4f232445de1 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, syncerKeys []string) *DetailedMetricsEngine { +func NewMetricsEngine(cfg *config.Configuration, adapterList []openrtb_ext.BidderName, syncerKeys []string, moduleStageNames map[string][]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, syncerKeys) + returnEngine.GoMetrics = metrics.NewMetrics(gometrics.NewPrefixedRegistry("prebidserver."), adapterList, cfg.Metrics.Disabled, syncerKeys, moduleStageNames) engineList = append(engineList, returnEngine.GoMetrics) // Set up the Influx logger @@ -40,7 +40,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, syncerKeys) + returnEngine.PrometheusMetrics = prometheusmetrics.NewMetrics(cfg.Metrics.Prometheus, cfg.Metrics.Disabled, syncerKeys, moduleStageNames) engineList = append(engineList, returnEngine.PrometheusMetrics) } @@ -86,7 +86,7 @@ func (me *MultiMetricsEngine) RecordConnectionClose(success bool) { } } -//RecordsImps records imps with imp types across all metric engines +// RecordsImps records imps with imp types across all metric engines func (me *MultiMetricsEngine) RecordImps(implabels metrics.ImpLabels) { for _, thisME := range *me { thisME.RecordImps(implabels) @@ -254,6 +254,73 @@ func (me *MultiMetricsEngine) RecordAdapterGDPRRequestBlocked(adapter openrtb_ex } } +// RecordDebugRequest across all engines +func (me *MultiMetricsEngine) RecordDebugRequest(debugEnabled bool, pubId string) { + for _, thisME := range *me { + thisME.RecordDebugRequest(debugEnabled, pubId) + } +} + +func (me *MultiMetricsEngine) RecordStoredResponse(pubId string) { + for _, thisME := range *me { + thisME.RecordStoredResponse(pubId) + } +} + +func (me *MultiMetricsEngine) RecordAdsCertReq(success bool) { + for _, thisME := range *me { + thisME.RecordAdsCertReq(success) + } +} + +func (me *MultiMetricsEngine) RecordAdsCertSignTime(adsCertSignTime time.Duration) { + for _, thisME := range *me { + thisME.RecordAdsCertSignTime(adsCertSignTime) + } +} + +func (me *MultiMetricsEngine) RecordModuleCalled(labels metrics.ModuleLabels, duration time.Duration) { + for _, thisME := range *me { + thisME.RecordModuleCalled(labels, duration) + } +} + +func (me *MultiMetricsEngine) RecordModuleFailed(labels metrics.ModuleLabels) { + for _, thisME := range *me { + thisME.RecordModuleFailed(labels) + } +} + +func (me *MultiMetricsEngine) RecordModuleSuccessNooped(labels metrics.ModuleLabels) { + for _, thisME := range *me { + thisME.RecordModuleSuccessNooped(labels) + } +} + +func (me *MultiMetricsEngine) RecordModuleSuccessUpdated(labels metrics.ModuleLabels) { + for _, thisME := range *me { + thisME.RecordModuleSuccessUpdated(labels) + } +} + +func (me *MultiMetricsEngine) RecordModuleSuccessRejected(labels metrics.ModuleLabels) { + for _, thisME := range *me { + thisME.RecordModuleSuccessRejected(labels) + } +} + +func (me *MultiMetricsEngine) RecordModuleExecutionError(labels metrics.ModuleLabels) { + for _, thisME := range *me { + thisME.RecordModuleExecutionError(labels) + } +} + +func (me *MultiMetricsEngine) RecordModuleTimeout(labels metrics.ModuleLabels) { + for _, thisME := range *me { + thisME.RecordModuleTimeout(labels) + } +} + // 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{} @@ -365,3 +432,39 @@ func (me *NilMetricsEngine) RecordRequestPrivacy(privacy metrics.PrivacyLabels) // RecordAdapterGDPRRequestBlocked as a noop func (me *NilMetricsEngine) RecordAdapterGDPRRequestBlocked(adapter openrtb_ext.BidderName) { } + +// RecordDebugRequest as a noop +func (me *NilMetricsEngine) RecordDebugRequest(debugEnabled bool, pubId string) { +} + +func (me *NilMetricsEngine) RecordStoredResponse(pubId string) { +} + +func (me *NilMetricsEngine) RecordAdsCertReq(success bool) { + +} + +func (me *NilMetricsEngine) RecordAdsCertSignTime(adsCertSignTime time.Duration) { + +} + +func (me *NilMetricsEngine) RecordModuleCalled(labels metrics.ModuleLabels, duration time.Duration) { +} + +func (me *NilMetricsEngine) RecordModuleFailed(labels metrics.ModuleLabels) { +} + +func (me *NilMetricsEngine) RecordModuleSuccessNooped(labels metrics.ModuleLabels) { +} + +func (me *NilMetricsEngine) RecordModuleSuccessUpdated(labels metrics.ModuleLabels) { +} + +func (me *NilMetricsEngine) RecordModuleSuccessRejected(labels metrics.ModuleLabels) { +} + +func (me *NilMetricsEngine) RecordModuleExecutionError(labels metrics.ModuleLabels) { +} + +func (me *NilMetricsEngine) RecordModuleTimeout(labels metrics.ModuleLabels) { +} diff --git a/metrics/config/metrics_test.go b/metrics/config/metrics_test.go index e4227afda77..80aa2550d3f 100644 --- a/metrics/config/metrics_test.go +++ b/metrics/config/metrics_test.go @@ -1,6 +1,7 @@ package config import ( + "fmt" "testing" "time" @@ -11,12 +12,14 @@ import ( gometrics "github.com/rcrowley/go-metrics" ) +var modulesStages = map[string][]string{"foobar": {"entry", "raw"}, "another_module": {"raw", "auction"}} + // Start a simple test to insure we get valid MetricsEngines for various configurations func TestNilMetricsEngine(t *testing.T) { cfg := mainConfig.Configuration{} adapterList := make([]openrtb_ext.BidderName, 0, 2) syncerKeys := []string{"keyA", "keyB"} - testEngine := NewMetricsEngine(&cfg, adapterList, syncerKeys) + testEngine := NewMetricsEngine(&cfg, adapterList, syncerKeys, modulesStages) _, ok := testEngine.MetricsEngine.(*NilMetricsEngine) if !ok { t.Error("Expected a NilMetricsEngine, but didn't get it") @@ -28,7 +31,7 @@ func TestGoMetricsEngine(t *testing.T) { cfg.Metrics.Influxdb.Host = "localhost" adapterList := make([]openrtb_ext.BidderName, 0, 2) syncerKeys := []string{"keyA", "keyB"} - testEngine := NewMetricsEngine(&cfg, adapterList, syncerKeys) + testEngine := NewMetricsEngine(&cfg, adapterList, syncerKeys, modulesStages) _, ok := testEngine.MetricsEngine.(*metrics.Metrics) if !ok { t.Error("Expected a legacy Metrics as MetricsEngine, but didn't get it") @@ -40,7 +43,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{}, nil) + goEngine := metrics.NewMetrics(gometrics.NewPrefixedRegistry("prebidserver."), adapterList, mainConfig.DisabledMetrics{}, nil, modulesStages) engineList := make(MultiMetricsEngine, 2) engineList[0] = goEngine engineList[1] = &NilMetricsEngine{} @@ -75,6 +78,16 @@ func TestMultiMetricsEngine(t *testing.T) { AudioImps: true, NativeImps: true, } + moduleLabels := make([]metrics.ModuleLabels, 0) + for module, stages := range modulesStages { + for _, stage := range stages { + moduleLabels = append(moduleLabels, metrics.ModuleLabels{ + Module: module, + Stage: stage, + AccountID: "test1", + }) + } + } for i := 0; i < 5; i++ { metricsEngine.RecordRequest(labels) metricsEngine.RecordImps(impTypeLabels) @@ -86,6 +99,15 @@ func TestMultiMetricsEngine(t *testing.T) { metricsEngine.RecordAdapterTime(pubLabels, time.Millisecond*20) metricsEngine.RecordPrebidCacheRequestTime(true, time.Millisecond*20) } + for _, module := range moduleLabels { + metricsEngine.RecordModuleCalled(module, time.Millisecond*1) + metricsEngine.RecordModuleFailed(module) + metricsEngine.RecordModuleSuccessNooped(module) + metricsEngine.RecordModuleSuccessUpdated(module) + metricsEngine.RecordModuleSuccessRejected(module) + metricsEngine.RecordModuleExecutionError(module) + metricsEngine.RecordModuleTimeout(module) + } labelsBlacklist := []metrics.Labels{ { Source: metrics.DemandWeb, @@ -166,6 +188,20 @@ func TestMultiMetricsEngine(t *testing.T) { VerifyMetrics(t, "AccountCache.Hit", goEngine.AccountCacheMeter[metrics.CacheHit].Count(), 6) VerifyMetrics(t, "AdapterMetrics.AppNexus.GDPRRequestBlocked", goEngine.AdapterMetrics[openrtb_ext.BidderAppnexus].GDPRRequestBlocked.Count(), 1) + + // verify that each module has its own metric recorded + for module, stages := range modulesStages { + for _, stage := range stages { + VerifyMetrics(t, fmt.Sprintf("ModuleMetrics.%s.%s.Duration", module, stage), goEngine.ModuleMetrics[module][stage].DurationTimer.Count(), 1) + VerifyMetrics(t, fmt.Sprintf("ModuleMetrics.%s.%s.Call", module, stage), goEngine.ModuleMetrics[module][stage].CallCounter.Count(), 1) + VerifyMetrics(t, fmt.Sprintf("ModuleMetrics.%s.%s.Fail", module, stage), goEngine.ModuleMetrics[module][stage].FailureCounter.Count(), 1) + VerifyMetrics(t, fmt.Sprintf("ModuleMetrics.%s.%s.SuccessNoop", module, stage), goEngine.ModuleMetrics[module][stage].SuccessNoopCounter.Count(), 1) + VerifyMetrics(t, fmt.Sprintf("ModuleMetrics.%s.%s.SuccessUpdate", module, stage), goEngine.ModuleMetrics[module][stage].SuccessUpdateCounter.Count(), 1) + VerifyMetrics(t, fmt.Sprintf("ModuleMetrics.%s.%s.SuccessReject", module, stage), goEngine.ModuleMetrics[module][stage].SuccessRejectCounter.Count(), 1) + VerifyMetrics(t, fmt.Sprintf("ModuleMetrics.%s.%s.ExecutionError", module, stage), goEngine.ModuleMetrics[module][stage].ExecutionErrorCounter.Count(), 1) + VerifyMetrics(t, fmt.Sprintf("ModuleMetrics.%s.%s.Timeout", module, stage), goEngine.ModuleMetrics[module][stage].TimeoutCounter.Count(), 1) + } + } } func VerifyMetrics(t *testing.T, name string, actual int64, expected int64) { diff --git a/metrics/go_metrics.go b/metrics/go_metrics.go index c93f10602c7..048eb8a7b59 100644 --- a/metrics/go_metrics.go +++ b/metrics/go_metrics.go @@ -20,6 +20,7 @@ type Metrics struct { ImpMeter metrics.Meter AppRequestMeter metrics.Meter NoCookieMeter metrics.Meter + DebugRequestMeter metrics.Meter RequestTimer metrics.Timer RequestsQueueTimer map[RequestType]map[bool]metrics.Timer PrebidCacheRequestTimerSuccess metrics.Timer @@ -31,6 +32,7 @@ type Metrics struct { AccountCacheMeter map[CacheResult]metrics.Meter DNSLookupTimer metrics.Timer TLSHandshakeTimer metrics.Timer + StoredResponsesMeter metrics.Meter // Metrics for OpenRTB requests specifically. So we can track what % of RequestsMeter are OpenRTB // and know when legacy requests have been abandoned. @@ -66,8 +68,17 @@ type Metrics struct { accountMetricsRWMutex sync.RWMutex exchanges []openrtb_ext.BidderName + modules []string // Will hold boolean values to help us disable metric collection if needed MetricsDisabled config.DisabledMetrics + + // AdsCert metrics + AdsCertRequestsSuccess metrics.Meter + AdsCertRequestsFailure metrics.Meter + adsCertSignTimer metrics.Timer + + // Module metrics + ModuleMetrics map[string]map[string]*ModuleMetrics } // AdapterMetrics houses the metrics for a particular adapter @@ -94,10 +105,24 @@ type MarkupDeliveryMetrics struct { type accountMetrics struct { requestMeter metrics.Meter + debugRequestMeter metrics.Meter bidsReceivedMeter metrics.Meter priceHistogram metrics.Histogram // store account by adapter metrics. Type is map[PBSBidder.BidderCode] - adapterMetrics map[openrtb_ext.BidderName]*AdapterMetrics + adapterMetrics map[openrtb_ext.BidderName]*AdapterMetrics + moduleMetrics map[string]*ModuleMetrics + storedResponsesMeter metrics.Meter +} + +type ModuleMetrics struct { + DurationTimer metrics.Timer + CallCounter metrics.Counter + FailureCounter metrics.Counter + SuccessNoopCounter metrics.Counter + SuccessUpdateCounter metrics.Counter + SuccessRejectCounter metrics.Counter + ExecutionErrorCounter metrics.Counter + TimeoutCounter metrics.Counter } // NewBlankMetrics creates a new Metrics object with all blank metrics object. This may also be useful for @@ -107,7 +132,7 @@ type accountMetrics struct { // rather than loading legacy metrics that never get filled. // This will also eventually let us configure metrics, such as setting a limited set of metrics // for a production instance, and then expanding again when we need more debugging. -func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, disabledMetrics config.DisabledMetrics) *Metrics { +func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, disabledMetrics config.DisabledMetrics, moduleStageNames map[string][]string) *Metrics { blankMeter := &metrics.NilMeter{} blankTimer := &metrics.NilTimer{} @@ -119,6 +144,7 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa ConnectionCloseErrorMeter: blankMeter, ImpMeter: blankMeter, AppRequestMeter: blankMeter, + DebugRequestMeter: blankMeter, NoCookieMeter: blankMeter, RequestTimer: blankTimer, DNSLookupTimer: blankTimer, @@ -138,6 +164,7 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa SetUidMeter: blankMeter, SetUidStatusMeter: make(map[SetUidStatus]metrics.Meter), SyncerSetsMeter: make(map[string]map[SyncerSetUidStatus]metrics.Meter), + StoredResponsesMeter: blankMeter, ImpsTypeBanner: blankMeter, ImpsTypeVideo: blankMeter, @@ -157,13 +184,24 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa accountMetrics: make(map[string]*accountMetrics), MetricsDisabled: disabledMetrics, + AdsCertRequestsSuccess: blankMeter, + AdsCertRequestsFailure: blankMeter, + adsCertSignTimer: blankTimer, + + ModuleMetrics: make(map[string]map[string]*ModuleMetrics), + exchanges: exchanges, + modules: getModuleNames(moduleStageNames), } for _, a := range exchanges { newMetrics.AdapterMetrics[a] = makeBlankAdapterMetrics(newMetrics.MetricsDisabled) } + for module, stages := range moduleStageNames { + newMetrics.ModuleMetrics[module] = makeBlankModuleStageMetrics(stages) + } + for _, t := range RequestTypes() { newMetrics.RequestStatuses[t] = make(map[RequestStatus]metrics.Meter) for _, s := range RequestStatuses() { @@ -200,13 +238,25 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa return newMetrics } +func getModuleNames(moduleStageNames map[string][]string) []string { + names := make([]string, len(moduleStageNames)) + + i := 0 + for moduleName := range moduleStageNames { + names[i] = moduleName + i++ + } + + return names +} + // NewMetrics creates a new Metrics object with needed metrics defined. In time we may develop to the point // where Metrics contains all the metrics we might want to record, and then we build the actual // 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, syncerKeys []string) *Metrics { - newMetrics := NewBlankMetrics(registry, exchanges, disableAccountMetrics) +func NewMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, disableAccountMetrics config.DisabledMetrics, syncerKeys []string, moduleStageNames map[string][]string) *Metrics { + newMetrics := NewBlankMetrics(registry, exchanges, disableAccountMetrics, moduleStageNames) newMetrics.ConnectionCounter = metrics.GetOrRegisterCounter("active_connections", registry) newMetrics.ConnectionAcceptErrorMeter = metrics.GetOrRegisterMeter("connection_accept_errors", registry) newMetrics.ConnectionCloseErrorMeter = metrics.GetOrRegisterMeter("connection_close_errors", registry) @@ -219,11 +269,13 @@ func NewMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, d newMetrics.NoCookieMeter = metrics.GetOrRegisterMeter("no_cookie_requests", registry) newMetrics.AppRequestMeter = metrics.GetOrRegisterMeter("app_requests", registry) + newMetrics.DebugRequestMeter = metrics.GetOrRegisterMeter("debug_requests", registry) newMetrics.RequestTimer = metrics.GetOrRegisterTimer("request_time", registry) newMetrics.DNSLookupTimer = metrics.GetOrRegisterTimer("dns_lookup_time", registry) newMetrics.TLSHandshakeTimer = metrics.GetOrRegisterTimer("tls_handshake_time", registry) newMetrics.PrebidCacheRequestTimerSuccess = metrics.GetOrRegisterTimer("prebid_cache_request_time.ok", registry) newMetrics.PrebidCacheRequestTimerError = metrics.GetOrRegisterTimer("prebid_cache_request_time.err", registry) + newMetrics.StoredResponsesMeter = metrics.GetOrRegisterMeter("stored_responses", registry) for _, dt := range StoredDataTypes() { for _, ft := range StoredDataFetchTypes() { @@ -290,6 +342,14 @@ func NewMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, d newMetrics.PrivacyTCFRequestVersion[version] = metrics.GetOrRegisterMeter(fmt.Sprintf("privacy.request.tcf.%s", string(version)), registry) } + newMetrics.AdsCertRequestsSuccess = metrics.GetOrRegisterMeter("ads_cert_requests.ok", registry) + newMetrics.AdsCertRequestsFailure = metrics.GetOrRegisterMeter("ads_cert_requests.failed", registry) + newMetrics.adsCertSignTimer = metrics.GetOrRegisterTimer("ads_cert_sign_time", registry) + + for module, stages := range moduleStageNames { + registerModuleMetrics(registry, module, stages, newMetrics.ModuleMetrics[module]) + } + return newMetrics } @@ -321,6 +381,28 @@ func makeBlankAdapterMetrics(disabledMetrics config.DisabledMetrics) *AdapterMet return newAdapter } +func makeBlankModuleStageMetrics(stages []string) map[string]*ModuleMetrics { + blankMetrics := map[string]*ModuleMetrics{} + for _, stage := range stages { + blankMetrics[stage] = makeBlankModuleMetrics() + } + + return blankMetrics +} + +func makeBlankModuleMetrics() *ModuleMetrics { + return &ModuleMetrics{ + DurationTimer: &metrics.NilTimer{}, + CallCounter: metrics.NilCounter{}, + FailureCounter: metrics.NilCounter{}, + SuccessNoopCounter: metrics.NilCounter{}, + SuccessUpdateCounter: metrics.NilCounter{}, + SuccessRejectCounter: metrics.NilCounter{}, + ExecutionErrorCounter: metrics.NilCounter{}, + TimeoutCounter: metrics.NilCounter{}, + } +} + func makeBlankBidMarkupMetrics() map[openrtb_ext.BidType]*MarkupDeliveryMetrics { return map[openrtb_ext.BidType]*MarkupDeliveryMetrics{ openrtb_ext.BidTypeAudio: makeBlankMarkupDeliveryMetrics(), @@ -362,6 +444,30 @@ func registerAdapterMetrics(registry metrics.Registry, adapterOrAccount string, am.GDPRRequestBlocked = metrics.GetOrRegisterMeter(fmt.Sprintf("%[1]s.%[2]s.gdpr_request_blocked", adapterOrAccount, exchange), registry) } +func registerModuleMetrics(registry metrics.Registry, module string, stages []string, mm map[string]*ModuleMetrics) { + for _, stage := range stages { + mm[stage].DurationTimer = metrics.GetOrRegisterTimer(fmt.Sprintf("modules.module.%s.stage.%s.duration", module, stage), registry) + mm[stage].CallCounter = metrics.GetOrRegisterCounter(fmt.Sprintf("modules.module.%s.stage.%s.call", module, stage), registry) + mm[stage].FailureCounter = metrics.GetOrRegisterCounter(fmt.Sprintf("modules.module.%s.stage.%s.failure", module, stage), registry) + mm[stage].SuccessNoopCounter = metrics.GetOrRegisterCounter(fmt.Sprintf("modules.module.%s.stage.%s.success.noop", module, stage), registry) + mm[stage].SuccessUpdateCounter = metrics.GetOrRegisterCounter(fmt.Sprintf("modules.module.%s.stage.%s.success.update", module, stage), registry) + mm[stage].SuccessRejectCounter = metrics.GetOrRegisterCounter(fmt.Sprintf("modules.module.%s.stage.%s.success.reject", module, stage), registry) + mm[stage].ExecutionErrorCounter = metrics.GetOrRegisterCounter(fmt.Sprintf("modules.module.%s.stage.%s.execution_error", module, stage), registry) + mm[stage].TimeoutCounter = metrics.GetOrRegisterCounter(fmt.Sprintf("modules.module.%s.stage.%s.timeout", module, stage), registry) + } +} + +func registerAccountModuleMetrics(registry metrics.Registry, id string, module string, mm *ModuleMetrics) { + mm.DurationTimer = metrics.GetOrRegisterTimer(fmt.Sprintf("account.%s.modules.module.%s.duration", id, module), registry) + mm.CallCounter = metrics.GetOrRegisterCounter(fmt.Sprintf("account.%s.modules.module.%s.call", id, module), registry) + mm.FailureCounter = metrics.GetOrRegisterCounter(fmt.Sprintf("account.%s.modules.module.%s.failure", id, module), registry) + mm.SuccessNoopCounter = metrics.GetOrRegisterCounter(fmt.Sprintf("account.%s.modules.module.%s.success.noop", id, module), registry) + mm.SuccessUpdateCounter = metrics.GetOrRegisterCounter(fmt.Sprintf("account.%s.modules.module.%s.success.update", id, module), registry) + mm.SuccessRejectCounter = metrics.GetOrRegisterCounter(fmt.Sprintf("account.%s.modules.module.%s.success.reject", id, module), registry) + mm.ExecutionErrorCounter = metrics.GetOrRegisterCounter(fmt.Sprintf("account.%s.modules.module.%s.execution_error", id, module), registry) + mm.TimeoutCounter = metrics.GetOrRegisterCounter(fmt.Sprintf("account.%s.modules.module.%s.timeout", id, module), registry) +} + func makeDeliveryMetrics(registry metrics.Registry, prefix string, bidType openrtb_ext.BidType) *MarkupDeliveryMetrics { return &MarkupDeliveryMetrics{ AdmMeter: metrics.GetOrRegisterMeter(prefix+"."+string(bidType)+".adm_bids_received", registry), @@ -394,15 +500,24 @@ func (me *Metrics) getAccountMetrics(id string) *accountMetrics { } am = &accountMetrics{} am.requestMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.requests", id), me.MetricsRegistry) + am.debugRequestMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.debug_requests", id), me.MetricsRegistry) am.bidsReceivedMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.bids_received", id), me.MetricsRegistry) am.priceHistogram = metrics.GetOrRegisterHistogram(fmt.Sprintf("account.%s.prices", id), me.MetricsRegistry, metrics.NewExpDecaySample(1028, 0.015)) am.adapterMetrics = make(map[openrtb_ext.BidderName]*AdapterMetrics, len(me.exchanges)) + am.moduleMetrics = make(map[string]*ModuleMetrics) + am.storedResponsesMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.stored_responses", id), me.MetricsRegistry) if !me.MetricsDisabled.AccountAdapterDetails { for _, a := range me.exchanges { am.adapterMetrics[a] = makeBlankAdapterMetrics(me.MetricsDisabled) registerAdapterMetrics(me.MetricsRegistry, fmt.Sprintf("account.%s", id), string(a), am.adapterMetrics[a]) } } + if !me.MetricsDisabled.AccountModulesMetrics { + for _, mod := range me.modules { + am.moduleMetrics[mod] = makeBlankModuleMetrics() + registerAccountModuleMetrics(me.MetricsRegistry, id, mod, am.moduleMetrics[mod]) + } + } me.accountMetrics[id] = am @@ -430,6 +545,25 @@ func (me *Metrics) RecordRequest(labels Labels) { am.requestMeter.Mark(1) } +func (me *Metrics) RecordDebugRequest(debugEnabled bool, pubID string) { + if debugEnabled { + me.DebugRequestMeter.Mark(1) + if pubID != PublisherUnknown { + am := me.getAccountMetrics(pubID) + if !me.MetricsDisabled.AccountDebug { + am.debugRequestMeter.Mark(1) + } + } + } +} + +func (me *Metrics) RecordStoredResponse(pubId string) { + me.StoredResponsesMeter.Mark(1) + if pubId != PublisherUnknown && !me.MetricsDisabled.AccountStoredResponses { + me.getAccountMetrics(pubId).storedResponsesMeter.Mark(1) + } +} + func (me *Metrics) RecordImps(labels ImpLabels) { me.ImpMeter.Mark(int64(1)) if labels.BannerImps { @@ -727,3 +861,147 @@ func (me *Metrics) RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.Bidde am.GDPRRequestBlocked.Mark(1) } + +func (me *Metrics) RecordAdsCertReq(success bool) { + if success { + me.AdsCertRequestsSuccess.Mark(1) + } else { + me.AdsCertRequestsFailure.Mark(1) + } +} + +func (me *Metrics) RecordAdsCertSignTime(adsCertSignTime time.Duration) { + me.adsCertSignTimer.Update(adsCertSignTime) +} + +func (me *Metrics) RecordModuleCalled(labels ModuleLabels, duration time.Duration) { + mm, err := me.getModuleMetric(labels) + if err != nil { + return + } + + // Module metrics + mm.CallCounter.Inc(1) + mm.DurationTimer.Update(duration) + + // Account-Module metrics + if labels.AccountID != "" && labels.AccountID != PublisherUnknown { + if aam, ok := me.getAccountMetrics(labels.AccountID).moduleMetrics[labels.Module]; ok { + aam.CallCounter.Inc(1) + aam.DurationTimer.Update(duration) + } + } +} + +func (me *Metrics) RecordModuleFailed(labels ModuleLabels) { + mm, err := me.getModuleMetric(labels) + if err != nil { + return + } + + // Module metrics + mm.FailureCounter.Inc(1) + + // Account-Module metrics + if labels.AccountID != "" && labels.AccountID != PublisherUnknown { + if aam, ok := me.getAccountMetrics(labels.AccountID).moduleMetrics[labels.Module]; ok { + aam.FailureCounter.Inc(1) + } + } +} + +func (me *Metrics) RecordModuleSuccessNooped(labels ModuleLabels) { + mm, err := me.getModuleMetric(labels) + if err != nil { + return + } + + // Module metrics + mm.SuccessNoopCounter.Inc(1) + + // Account-Module metrics + if labels.AccountID != "" && labels.AccountID != PublisherUnknown { + if aam, ok := me.getAccountMetrics(labels.AccountID).moduleMetrics[labels.Module]; ok { + aam.SuccessNoopCounter.Inc(1) + } + } +} + +func (me *Metrics) RecordModuleSuccessUpdated(labels ModuleLabels) { + mm, err := me.getModuleMetric(labels) + if err != nil { + return + } + + // Module metrics + mm.SuccessUpdateCounter.Inc(1) + + // Account-Module metrics + if labels.AccountID != "" && labels.AccountID != PublisherUnknown { + if aam, ok := me.getAccountMetrics(labels.AccountID).moduleMetrics[labels.Module]; ok { + aam.SuccessUpdateCounter.Inc(1) + } + } +} + +func (me *Metrics) RecordModuleSuccessRejected(labels ModuleLabels) { + mm, err := me.getModuleMetric(labels) + if err != nil { + return + } + + // Module metrics + mm.SuccessRejectCounter.Inc(1) + + // Account-Module metrics + if labels.AccountID != "" && labels.AccountID != PublisherUnknown { + if aam, ok := me.getAccountMetrics(labels.AccountID).moduleMetrics[labels.Module]; ok { + aam.SuccessRejectCounter.Inc(1) + } + } +} + +func (me *Metrics) RecordModuleExecutionError(labels ModuleLabels) { + mm, err := me.getModuleMetric(labels) + if err != nil { + return + } + + // Module metrics + mm.ExecutionErrorCounter.Inc(1) + + // Account-Module metrics + if labels.AccountID != "" && labels.AccountID != PublisherUnknown { + if aam, ok := me.getAccountMetrics(labels.AccountID).moduleMetrics[labels.Module]; ok { + aam.ExecutionErrorCounter.Inc(1) + } + } +} + +func (me *Metrics) RecordModuleTimeout(labels ModuleLabels) { + mm, err := me.getModuleMetric(labels) + if err != nil { + return + } + + // Module metrics + mm.TimeoutCounter.Inc(1) + + // Account-Module metrics + if labels.AccountID != "" && labels.AccountID != PublisherUnknown { + if aam, ok := me.getAccountMetrics(labels.AccountID).moduleMetrics[labels.Module]; ok { + aam.TimeoutCounter.Inc(1) + } + } +} + +func (me *Metrics) getModuleMetric(labels ModuleLabels) (*ModuleMetrics, error) { + mm, ok := me.ModuleMetrics[labels.Module][labels.Stage] + if !ok { + err := fmt.Errorf("Trying to run module %s metrics for stage %s: module metrics not found", labels.Module, labels.Stage) + glog.Errorf(err.Error()) + return nil, err + } + + return mm, nil +} diff --git a/metrics/go_metrics_test.go b/metrics/go_metrics_test.go index dd2430d6b74..d3bd2d66f4c 100644 --- a/metrics/go_metrics_test.go +++ b/metrics/go_metrics_test.go @@ -1,6 +1,7 @@ package metrics import ( + "fmt" "testing" "time" @@ -13,9 +14,11 @@ import ( func TestNewMetrics(t *testing.T) { registry := metrics.NewRegistry() syncerKeys := []string{"foo"} - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{}, syncerKeys) + moduleStageNames := map[string][]string{"foobar": {"entry", "raw"}, "another_module": {"raw", "auction"}} + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{}, syncerKeys, moduleStageNames) ensureContains(t, registry, "app_requests", m.AppRequestMeter) + ensureContains(t, registry, "debug_requests", m.DebugRequestMeter) ensureContains(t, registry, "no_cookie_requests", m.NoCookieMeter) ensureContains(t, registry, "request_time", m.RequestTimer) ensureContains(t, registry, "amp_no_cookie_requests", m.AmpNoCookieMeter) @@ -32,6 +35,7 @@ func TestNewMetrics(t *testing.T) { 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, "stored_responses", m.StoredResponsesMeter) ensureContains(t, registry, "prebid_cache_request_time.ok", m.PrebidCacheRequestTimerSuccess) ensureContains(t, registry, "prebid_cache_request_time.err", m.PrebidCacheRequestTimerError) @@ -72,11 +76,20 @@ func TestNewMetrics(t *testing.T) { 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]) + + ensureContains(t, registry, "ads_cert_requests.ok", m.AdsCertRequestsSuccess) + ensureContains(t, registry, "ads_cert_requests.failed", m.AdsCertRequestsFailure) + + for module, stages := range moduleStageNames { + for _, stage := range stages { + ensureContainsModuleMetrics(t, registry, fmt.Sprintf("modules.module.%s.stage.%s", module, stage), m.ModuleMetrics[module][stage]) + } + } } func TestRecordBidType(t *testing.T) { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{}, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{}, nil, nil) m.RecordAdapterBidReceived(AdapterLabels{ Adapter: openrtb_ext.BidderAppnexus, @@ -119,6 +132,18 @@ func ensureContainsAdapterMetrics(t *testing.T, registry metrics.Registry, name ensureContains(t, registry, name+".connection_wait_time", adapterMetrics.ConnWaitTime) } +func ensureContainsModuleMetrics(t *testing.T, registry metrics.Registry, name string, moduleMetrics *ModuleMetrics) { + t.Helper() + ensureContains(t, registry, name+".duration", moduleMetrics.DurationTimer) + ensureContains(t, registry, name+".call", moduleMetrics.CallCounter) + ensureContains(t, registry, name+".failure", moduleMetrics.FailureCounter) + ensureContains(t, registry, name+".success.noop", moduleMetrics.SuccessNoopCounter) + ensureContains(t, registry, name+".success.update", moduleMetrics.SuccessUpdateCounter) + ensureContains(t, registry, name+".success.reject", moduleMetrics.SuccessRejectCounter) + ensureContains(t, registry, name+".execution_error", moduleMetrics.ExecutionErrorCounter) + ensureContains(t, registry, name+".timeout", moduleMetrics.TimeoutCounter) +} + func TestRecordBidTypeDisabledConfig(t *testing.T) { testCases := []struct { hasAdm bool @@ -164,7 +189,7 @@ func TestRecordBidTypeDisabledConfig(t *testing.T) { for _, test := range testCases { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, test.DisabledMetrics, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, test.DisabledMetrics, nil, nil) m.RecordAdapterBidReceived(AdapterLabels{ Adapter: openrtb_ext.BidderAppnexus, @@ -181,6 +206,72 @@ func TestRecordBidTypeDisabledConfig(t *testing.T) { } } +func TestRecordDebugRequest(t *testing.T) { + testCases := []struct { + description string + givenDisabledMetrics config.DisabledMetrics + givenDebugEnabledFlag bool + givenPubID string + expectedAccountDebugCount int64 + expectedDebugCount int64 + }{ + { + description: "Debug is enabled and account debug is enabled, both metrics should be updated", + givenDisabledMetrics: config.DisabledMetrics{ + AccountAdapterDetails: true, + AccountDebug: false, + }, + givenDebugEnabledFlag: true, + givenPubID: "acct-id", + expectedAccountDebugCount: 1, + expectedDebugCount: 1, + }, + { + description: "Debug and account debug are disabled, niether metrics should be updated", + givenDisabledMetrics: config.DisabledMetrics{ + AccountAdapterDetails: true, + AccountDebug: true, + }, + givenDebugEnabledFlag: false, + givenPubID: "acct-id", + expectedAccountDebugCount: 0, + expectedDebugCount: 0, + }, + { + description: "Debug is enabled and account debug is enabled, but unknown PubID leads to account debug being 0", + givenDisabledMetrics: config.DisabledMetrics{ + AccountAdapterDetails: true, + AccountDebug: false, + }, + givenDebugEnabledFlag: true, + givenPubID: PublisherUnknown, + expectedAccountDebugCount: 0, + expectedDebugCount: 1, + }, + { + description: "Debug is disabled, account debug is enabled, niether metric should update", + givenDisabledMetrics: config.DisabledMetrics{ + AccountAdapterDetails: true, + AccountDebug: false, + }, + givenDebugEnabledFlag: false, + givenPubID: "acct-id", + expectedAccountDebugCount: 0, + expectedDebugCount: 0, + }, + } + for _, test := range testCases { + registry := metrics.NewRegistry() + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, test.givenDisabledMetrics, nil, nil) + + m.RecordDebugRequest(test.givenDebugEnabledFlag, test.givenPubID) + am := m.getAccountMetrics(test.givenPubID) + + assert.Equal(t, test.expectedDebugCount, m.DebugRequestMeter.Count()) + assert.Equal(t, test.expectedAccountDebugCount, am.debugRequestMeter.Count()) + } +} + func TestRecordDNSTime(t *testing.T) { testCases := []struct { description string @@ -200,7 +291,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}, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) m.RecordDNSTime(test.inDnsLookupDuration) @@ -227,7 +318,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}, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) m.RecordTLSHandshakeTime(test.tLSHandshakeDuration) @@ -332,7 +423,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}, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AdapterConnectionMetrics: test.in.connMetricsDisabled}, nil, nil) m.RecordAdapterConnections(test.in.adapterName, test.in.connWasReused, test.in.connWait) @@ -344,14 +435,15 @@ 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}, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{AccountAdapterDetails: true, AccountModulesMetrics: true}, nil, map[string][]string{"foobar": {"entry", "raw"}}) assert.True(t, m.MetricsDisabled.AccountAdapterDetails, "Accound adapter metrics should be disabled") + assert.True(t, m.MetricsDisabled.AccountModulesMetrics, "Accound modules 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}, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) m.RecordPrebidCacheRequestTime(true, 42) @@ -361,7 +453,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}, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) m.RecordPrebidCacheRequestTime(false, 42) @@ -429,7 +521,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}, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) m.RecordStoredDataFetchTime(StoredDataLabels{ DataType: tt.dataType, DataFetchType: tt.fetchType, @@ -503,7 +595,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}, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) m.RecordStoredDataError(StoredDataLabels{ DataType: tt.dataType, Error: tt.errorType, @@ -516,7 +608,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}, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) // CCPA m.RecordRequestPrivacy(PrivacyLabels{ @@ -591,7 +683,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}, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AdapterGDPRRequestBlocked: tt.metricsDisabled}, nil, nil) m.RecordAdapterGDPRRequestBlocked(tt.adapterName) @@ -601,7 +693,7 @@ 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) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{}, nil, nil) // Known m.RecordCookieSync(CookieSyncBadRequest) @@ -619,7 +711,7 @@ func TestRecordCookieSync(t *testing.T) { 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) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{}, syncerKeys, nil) // Known m.RecordSyncerRequest("foo", SyncerCookieSyncOK) @@ -638,7 +730,7 @@ func TestRecordSyncerRequest(t *testing.T) { func TestRecordSetUid(t *testing.T) { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{}, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{}, nil, nil) // Known m.RecordSetUid(SetUidOptOut) @@ -657,7 +749,7 @@ func TestRecordSetUid(t *testing.T) { 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) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{}, syncerKeys, nil) // Known m.RecordSyncerSet("foo", SyncerSetUidCleared) @@ -672,6 +764,182 @@ func TestRecordSyncerSet(t *testing.T) { assert.Equal(t, m.SyncerSetsMeter["foo"][SyncerSetUidCleared].Count(), int64(1)) } +func TestStoredResponses(t *testing.T) { + testCases := []struct { + description string + givenPubID string + accountStoredResponsesMetricsDisabled bool + expectedAccountStoredResponsesCount int64 + expectedStoredResponsesCount int64 + }{ + { + description: "Publisher id is given, account stored responses disabled, both metrics should be updated", + givenPubID: "acct-id", + accountStoredResponsesMetricsDisabled: true, + expectedAccountStoredResponsesCount: 0, + expectedStoredResponsesCount: 1, + }, + { + description: "Publisher id is given, account stored responses enabled, both metrics should be updated", + givenPubID: "acct-id", + accountStoredResponsesMetricsDisabled: false, + expectedAccountStoredResponsesCount: 1, + expectedStoredResponsesCount: 1, + }, + { + description: "Publisher id is unknown, account stored responses enabled, only expectedStoredResponsesCount metric should be updated", + givenPubID: PublisherUnknown, + accountStoredResponsesMetricsDisabled: false, + expectedAccountStoredResponsesCount: 0, + expectedStoredResponsesCount: 1, + }, + { + description: "Publisher id is unknown, account stored responses disabled, only expectedStoredResponsesCount metric should be updated", + givenPubID: PublisherUnknown, + accountStoredResponsesMetricsDisabled: true, + expectedAccountStoredResponsesCount: 0, + expectedStoredResponsesCount: 1, + }, + } + for _, test := range testCases { + registry := metrics.NewRegistry() + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AccountStoredResponses: test.accountStoredResponsesMetricsDisabled}, nil, nil) + + m.RecordStoredResponse(test.givenPubID) + am := m.getAccountMetrics(test.givenPubID) + + assert.Equal(t, test.expectedStoredResponsesCount, m.StoredResponsesMeter.Count()) + assert.Equal(t, test.expectedAccountStoredResponsesCount, am.storedResponsesMeter.Count()) + } +} + +func TestRecordAdsCertSignTime(t *testing.T) { + testCases := []struct { + description string + inAdsCertSignDuration time.Duration + outExpDuration time.Duration + }{ + { + description: "Five second AdsCertSign time", + inAdsCertSignDuration: time.Second * 5, + outExpDuration: time.Second * 5, + }, + { + description: "Five millisecond AdsCertSign time", + inAdsCertSignDuration: time.Millisecond * 5, + outExpDuration: time.Millisecond * 5, + }, + { + description: "Zero AdsCertSign time", + inAdsCertSignDuration: time.Duration(0), + outExpDuration: time.Duration(0), + }, + } + for _, test := range testCases { + registry := metrics.NewRegistry() + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{}, nil, nil) + + m.RecordAdsCertSignTime(test.inAdsCertSignDuration) + + assert.Equal(t, test.outExpDuration.Nanoseconds(), m.adsCertSignTimer.Sum(), test.description) + } +} + +func TestRecordAdsCertReqMetric(t *testing.T) { + testCases := []struct { + description string + requestSuccess bool + expectedSuccessRequestsCount int64 + expectedFailedRequestsCount int64 + }{ + { + description: "Record failed request, expected success request count is 0 and failed request count is 1", + requestSuccess: false, + expectedSuccessRequestsCount: 0, + expectedFailedRequestsCount: 1, + }, + { + description: "Record successful request, expected success request count is 1 and failed request count is 0", + requestSuccess: true, + expectedSuccessRequestsCount: 1, + expectedFailedRequestsCount: 0, + }, + } + + for _, test := range testCases { + registry := metrics.NewRegistry() + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{}, nil, nil) + + m.RecordAdsCertReq(test.requestSuccess) + + assert.Equal(t, test.expectedSuccessRequestsCount, m.AdsCertRequestsSuccess.Count(), test.description) + assert.Equal(t, test.expectedFailedRequestsCount, m.AdsCertRequestsFailure.Count(), test.description) + } +} + +func TestRecordModuleAccountMetrics(t *testing.T) { + registry := metrics.NewRegistry() + module := "foobar" + stage1 := "entrypoint" + stage2 := "raw_auction" + stage3 := "processed_auction" + + testCases := []struct { + description string + givenModuleName string + givenStageName string + givenPubID string + givenDisabledMetrics config.DisabledMetrics + expectedModuleMetricCount int64 + expectedAccountMetricCount int64 + }{ + { + description: "Entrypoint stage should not record account metrics", + givenModuleName: module, + givenStageName: stage1, + givenDisabledMetrics: config.DisabledMetrics{AccountModulesMetrics: false}, + expectedModuleMetricCount: 1, + expectedAccountMetricCount: 0, + }, + { + description: "Rawauction stage should record both metrics", + givenModuleName: module, + givenStageName: stage2, + givenPubID: "acc-1", + givenDisabledMetrics: config.DisabledMetrics{AccountModulesMetrics: false}, + expectedModuleMetricCount: 1, + expectedAccountMetricCount: 1, + }, + { + description: "Rawauction stage should not record account metrics because they are disabled", + givenModuleName: module, + givenStageName: stage3, + givenPubID: "acc-1", + givenDisabledMetrics: config.DisabledMetrics{AccountModulesMetrics: true}, + expectedModuleMetricCount: 1, + expectedAccountMetricCount: 0, + }, + } + for _, test := range testCases { + m := NewMetrics(registry, nil, test.givenDisabledMetrics, nil, map[string][]string{module: {stage1, stage2, stage3}}) + + m.RecordModuleCalled(ModuleLabels{ + Module: test.givenModuleName, + Stage: test.givenStageName, + AccountID: test.givenPubID, + }, time.Microsecond) + am := m.getAccountMetrics(test.givenPubID) + + assert.Equal(t, test.expectedModuleMetricCount, m.ModuleMetrics[test.givenModuleName][test.givenStageName].CallCounter.Count()) + if !test.givenDisabledMetrics.AccountModulesMetrics { + assert.Equal(t, test.expectedAccountMetricCount, am.moduleMetrics[test.givenModuleName].CallCounter.Count()) + assert.Equal(t, test.expectedAccountMetricCount, am.moduleMetrics[test.givenModuleName].DurationTimer.Count()) + } else { + assert.Len(t, am.moduleMetrics, 0, "Account modules metrics are disabled, they should not be collected. Actual result %d account metrics collected \n", len(am.moduleMetrics)) + } + } +} + 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 b70907ea465..4acee77f6aa 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -49,6 +49,12 @@ type PrivacyLabels struct { LMTEnforced bool } +type ModuleLabels struct { + Module string + Stage string + AccountID string +} + type StoredDataType string const ( @@ -57,6 +63,7 @@ const ( CategoryDataType StoredDataType = "category" RequestDataType StoredDataType = "request" VideoDataType StoredDataType = "video" + ResponseDataType StoredDataType = "response" ) func StoredDataTypes() []StoredDataType { @@ -66,6 +73,7 @@ func StoredDataTypes() []StoredDataType { CategoryDataType, RequestDataType, VideoDataType, + ResponseDataType, } } @@ -200,12 +208,13 @@ func CookieTypes() []CookieFlag { // Request/return status const ( - RequestStatusOK RequestStatus = "ok" - RequestStatusBadInput RequestStatus = "badinput" - RequestStatusErr RequestStatus = "err" - RequestStatusNetworkErr RequestStatus = "networkerr" - RequestStatusBlacklisted RequestStatus = "blacklistedacctorapp" - RequestStatusQueueTimeout RequestStatus = "queuetimeout" + RequestStatusOK RequestStatus = "ok" + RequestStatusBadInput RequestStatus = "badinput" + RequestStatusErr RequestStatus = "err" + RequestStatusNetworkErr RequestStatus = "networkerr" + RequestStatusBlacklisted RequestStatus = "blacklistedacctorapp" + RequestStatusQueueTimeout RequestStatus = "queuetimeout" + RequestStatusAccountConfigErr RequestStatus = "acctconfigerr" ) func RequestStatuses() []RequestStatus { @@ -216,6 +225,7 @@ func RequestStatuses() []RequestStatus { RequestStatusNetworkErr, RequestStatusBlacklisted, RequestStatusQueueTimeout, + RequestStatusAccountConfigErr, } } @@ -238,6 +248,7 @@ const ( AdapterErrorBadServerResponse AdapterError = "badserverresponse" AdapterErrorTimeout AdapterError = "timeout" AdapterErrorFailedToRequestBids AdapterError = "failedtorequestbid" + AdapterErrorValidation AdapterError = "validation" AdapterErrorUnknown AdapterError = "unknown_error" ) @@ -247,6 +258,7 @@ func AdapterErrors() []AdapterError { AdapterErrorBadServerResponse, AdapterErrorTimeout, AdapterErrorFailedToRequestBids, + AdapterErrorValidation, AdapterErrorUnknown, } } @@ -296,12 +308,13 @@ func TCFVersionToValue(version int) TCFVersionValue { type CookieSyncStatus string const ( - CookieSyncOK CookieSyncStatus = "ok" - CookieSyncBadRequest CookieSyncStatus = "bad_request" - CookieSyncOptOut CookieSyncStatus = "opt_out" - CookieSyncGDPRHostCookieBlocked CookieSyncStatus = "gdpr_blocked_host_cookie" - CookieSyncAccountBlocked CookieSyncStatus = "acct_blocked" - CookieSyncAccountInvalid CookieSyncStatus = "acct_invalid" + CookieSyncOK CookieSyncStatus = "ok" + CookieSyncBadRequest CookieSyncStatus = "bad_request" + CookieSyncOptOut CookieSyncStatus = "opt_out" + CookieSyncGDPRHostCookieBlocked CookieSyncStatus = "gdpr_blocked_host_cookie" + CookieSyncAccountBlocked CookieSyncStatus = "acct_blocked" + CookieSyncAccountConfigMalformed CookieSyncStatus = "acct_config_malformed" + CookieSyncAccountInvalid CookieSyncStatus = "acct_invalid" ) // CookieSyncStatuses returns possible cookie sync statuses. @@ -311,6 +324,9 @@ func CookieSyncStatuses() []CookieSyncStatus { CookieSyncBadRequest, CookieSyncOptOut, CookieSyncGDPRHostCookieBlocked, + CookieSyncAccountBlocked, + CookieSyncAccountConfigMalformed, + CookieSyncAccountInvalid, } } @@ -339,11 +355,14 @@ 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" + SetUidOK SetUidStatus = "ok" + SetUidBadRequest SetUidStatus = "bad_request" + SetUidOptOut SetUidStatus = "opt_out" + SetUidGDPRHostCookieBlocked SetUidStatus = "gdpr_blocked_host_cookie" + SetUidAccountBlocked SetUidStatus = "acct_blocked" + SetUidAccountConfigMalformed SetUidStatus = "acct_config_malformed" + SetUidAccountInvalid SetUidStatus = "acct_invalid" + SetUidSyncerUnknown SetUidStatus = "syncer_unknown" ) // SetUidStatuses returns possible setuid statuses. @@ -353,6 +372,9 @@ func SetUidStatuses() []SetUidStatus { SetUidBadRequest, SetUidOptOut, SetUidGDPRHostCookieBlocked, + SetUidAccountBlocked, + SetUidAccountConfigMalformed, + SetUidAccountInvalid, SetUidSyncerUnknown, } } @@ -409,4 +431,15 @@ type MetricsEngine interface { RecordTimeoutNotice(success bool) RecordRequestPrivacy(privacy PrivacyLabels) RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.BidderName) + RecordDebugRequest(debugEnabled bool, pubId string) + RecordStoredResponse(pubId string) + RecordAdsCertReq(success bool) + RecordAdsCertSignTime(adsCertSignTime time.Duration) + RecordModuleCalled(labels ModuleLabels, duration time.Duration) + RecordModuleFailed(labels ModuleLabels) + RecordModuleSuccessNooped(labels ModuleLabels) + RecordModuleSuccessUpdated(labels ModuleLabels) + RecordModuleSuccessRejected(labels ModuleLabels) + RecordModuleExecutionError(labels ModuleLabels) + RecordModuleTimeout(labels ModuleLabels) } diff --git a/metrics/metrics_mock.go b/metrics/metrics_mock.go index c8d5311c3a4..7bdd05620bd 100644 --- a/metrics/metrics_mock.go +++ b/metrics/metrics_mock.go @@ -145,3 +145,48 @@ func (me *MetricsEngineMock) RecordRequestPrivacy(privacy PrivacyLabels) { func (me *MetricsEngineMock) RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.BidderName) { me.Called(adapterName) } + +// RecordDebugRequest mock +func (me *MetricsEngineMock) RecordDebugRequest(debugEnabled bool, pubId string) { + me.Called(debugEnabled, pubId) +} + +func (me *MetricsEngineMock) RecordStoredResponse(pubId string) { + me.Called(pubId) +} + +func (me *MetricsEngineMock) RecordAdsCertReq(success bool) { + me.Called(success) +} + +func (me *MetricsEngineMock) RecordAdsCertSignTime(adsCertSignTime time.Duration) { + me.Called(adsCertSignTime) +} + +func (me *MetricsEngineMock) RecordModuleCalled(labels ModuleLabels, duration time.Duration) { + me.Called(labels, duration) +} + +func (me *MetricsEngineMock) RecordModuleFailed(labels ModuleLabels) { + me.Called(labels) +} + +func (me *MetricsEngineMock) RecordModuleSuccessNooped(labels ModuleLabels) { + me.Called(labels) +} + +func (me *MetricsEngineMock) RecordModuleSuccessUpdated(labels ModuleLabels) { + me.Called(labels) +} + +func (me *MetricsEngineMock) RecordModuleSuccessRejected(labels ModuleLabels) { + me.Called(labels) +} + +func (me *MetricsEngineMock) RecordModuleExecutionError(labels ModuleLabels) { + me.Called(labels) +} + +func (me *MetricsEngineMock) RecordModuleTimeout(labels ModuleLabels) { + me.Called(labels) +} diff --git a/metrics/prometheus/preload.go b/metrics/prometheus/preload.go index 338b1e8b347..a61f26c36e0 100644 --- a/metrics/prometheus/preload.go +++ b/metrics/prometheus/preload.go @@ -5,7 +5,7 @@ import ( "github.com/prometheus/client_golang/prometheus" ) -func preloadLabelValues(m *Metrics, syncerKeys []string) { +func preloadLabelValues(m *Metrics, syncerKeys []string, moduleStageNames map[string][]string) { var ( setUidStatusValues = setUidStatusesAsString() adapterErrorValues = adapterErrorsAsString() @@ -77,6 +77,10 @@ func preloadLabelValues(m *Metrics, syncerKeys []string) { storedDataFetchTypeLabel: storedDataFetchTypeValues, }) + preloadLabelValuesForHistogram(m.storedResponsesFetchTimer, map[string][]string{ + storedDataFetchTypeLabel: storedDataFetchTypeValues, + }) + preloadLabelValuesForCounter(m.storedAccountErrors, map[string][]string{ storedDataErrorLabel: storedDataErrorValues, }) @@ -97,6 +101,10 @@ func preloadLabelValues(m *Metrics, syncerKeys []string) { storedDataErrorLabel: storedDataErrorValues, }) + preloadLabelValuesForCounter(m.storedResponsesErrors, map[string][]string{ + storedDataErrorLabel: storedDataErrorValues, + }) + preloadLabelValuesForCounter(m.requestsWithoutCookie, map[string][]string{ requestTypeLabel: requestTypeValues, }) @@ -137,6 +145,10 @@ func preloadLabelValues(m *Metrics, syncerKeys []string) { hasBidsLabel: boolValues, }) + preloadLabelValuesForCounter(m.adsCertRequests, map[string][]string{ + successLabel: boolValues, + }) + if !m.metricsDisabled.AdapterConnectionMetrics { preloadLabelValuesForCounter(m.adapterCreatedConnections, map[string][]string{ adapterLabel: adapterValues, @@ -195,6 +207,40 @@ func preloadLabelValues(m *Metrics, syncerKeys []string) { adapterLabel: adapterValues, }) } + + for module, stageValues := range moduleStageNames { + preloadLabelValuesForHistogram(m.moduleDuration[module], map[string][]string{ + stageLabel: stageValues, + }) + + preloadLabelValuesForCounter(m.moduleCalls[module], map[string][]string{ + stageLabel: stageValues, + }) + + preloadLabelValuesForCounter(m.moduleFailures[module], map[string][]string{ + stageLabel: stageValues, + }) + + preloadLabelValuesForCounter(m.moduleSuccessNoops[module], map[string][]string{ + stageLabel: stageValues, + }) + + preloadLabelValuesForCounter(m.moduleSuccessUpdates[module], map[string][]string{ + stageLabel: stageValues, + }) + + preloadLabelValuesForCounter(m.moduleSuccessRejects[module], map[string][]string{ + stageLabel: stageValues, + }) + + preloadLabelValuesForCounter(m.moduleExecutionErrors[module], map[string][]string{ + stageLabel: stageValues, + }) + + preloadLabelValuesForCounter(m.moduleTimeouts[module], map[string][]string{ + stageLabel: stageValues, + }) + } } func preloadLabelValuesForCounter(counter *prometheus.CounterVec, labelsWithValues map[string][]string) { diff --git a/metrics/prometheus/prometheus.go b/metrics/prometheus/prometheus.go index 368424c594c..ca5166cdd50 100644 --- a/metrics/prometheus/prometheus.go +++ b/metrics/prometheus/prometheus.go @@ -27,6 +27,7 @@ type Metrics struct { impressionsLegacy prometheus.Counter prebidCacheWriteTimer *prometheus.HistogramVec requests *prometheus.CounterVec + debugRequests prometheus.Counter requestsTimer *prometheus.HistogramVec requestsQueueTimer *prometheus.HistogramVec requestsWithoutCookie *prometheus.CounterVec @@ -50,6 +51,11 @@ type Metrics struct { privacyCOPPA *prometheus.CounterVec privacyLMT *prometheus.CounterVec privacyTCF *prometheus.CounterVec + storedResponses prometheus.Counter + storedResponsesFetchTimer *prometheus.HistogramVec + storedResponsesErrors *prometheus.CounterVec + adsCertRequests *prometheus.CounterVec + adsCertSignTimer prometheus.Histogram // Adapter Metrics adapterBids *prometheus.CounterVec @@ -68,7 +74,19 @@ type Metrics struct { syncerSets *prometheus.CounterVec // Account Metrics - accountRequests *prometheus.CounterVec + accountRequests *prometheus.CounterVec + accountDebugRequests *prometheus.CounterVec + accountStoredResponses *prometheus.CounterVec + + // Module Metrics as a map where the key is the module name + moduleDuration map[string]*prometheus.HistogramVec + moduleCalls map[string]*prometheus.CounterVec + moduleFailures map[string]*prometheus.CounterVec + moduleSuccessNoops map[string]*prometheus.CounterVec + moduleSuccessUpdates map[string]*prometheus.CounterVec + moduleSuccessRejects map[string]*prometheus.CounterVec + moduleExecutionErrors map[string]*prometheus.CounterVec + moduleTimeouts map[string]*prometheus.CounterVec metricsDisabled config.DisabledMetrics } @@ -92,6 +110,7 @@ const ( privacyBlockedLabel = "privacy_blocked" requestStatusLabel = "request_status" requestTypeLabel = "request_type" + stageLabel = "stage" statusLabel = "status" successLabel = "success" syncerLabel = "syncer" @@ -129,7 +148,7 @@ const ( ) // NewMetrics initializes a new Prometheus metrics instance with preloaded label values. -func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMetrics, syncerKeys []string) *Metrics { +func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMetrics, syncerKeys []string, moduleStageNames map[string][]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} @@ -182,6 +201,10 @@ func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMet "Count of total requests to Prebid Server labeled by type and status.", []string{requestTypeLabel, requestStatusLabel}) + metrics.debugRequests = newCounterWithoutLabels(cfg, reg, + "debug_requests", + "Count of total requests to Prebid Server that have debug enabled") + metrics.requestsTimer = newHistogramVec(cfg, reg, "request_time_seconds", "Seconds to resolve successful Prebid Server requests labeled by type.", @@ -305,6 +328,21 @@ func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMet []string{adapterLabel}) } + metrics.storedResponsesFetchTimer = newHistogramVec(cfg, reg, + "stored_response_fetch_time_seconds", + "Seconds to fetch stored responses labeled by fetch type", + []string{storedDataFetchTypeLabel}, + standardTimeBuckets) + + metrics.storedResponsesErrors = newCounter(cfg, reg, + "stored_response_errors", + "Count of stored video errors by error type", + []string{storedDataErrorLabel}) + + metrics.storedResponses = newCounterWithoutLabels(cfg, reg, + "stored_responses", + "Count of total requests to Prebid Server that have stored responses") + metrics.adapterBids = newCounter(cfg, reg, "adapter_bids", "Count of bids labeled by adapter and markup delivery type (adm or nurl).", @@ -370,24 +408,108 @@ func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMet "Count of total requests to Prebid Server labeled by account.", []string{accountLabel}) + metrics.accountDebugRequests = newCounter(cfg, reg, + "account_debug_requests", + "Count of total requests to Prebid Server that have debug enabled labled by account", + []string{accountLabel}) + metrics.requestsQueueTimer = newHistogramVec(cfg, reg, "request_queue_time", "Seconds request was waiting in queue", []string{requestTypeLabel, requestStatusLabel}, queuedRequestTimeBuckets) + metrics.accountStoredResponses = newCounter(cfg, reg, + "account_stored_responses", + "Count of total requests to Prebid Server that have stored responses labled by account", + []string{accountLabel}) + + metrics.adsCertSignTimer = newHistogram(cfg, reg, + "ads_cert_sign_time", + "Seconds to generate an AdsCert header", + standardTimeBuckets) + + metrics.adsCertRequests = newCounter(cfg, reg, + "ads_cert_requests", + "Count of AdsCert request, and if they were successfully sent.", + []string{successLabel}) + + createModulesMetrics(cfg, reg, &metrics, moduleStageNames, standardTimeBuckets) + metrics.Gatherer = reg - metricsPrefix := fmt.Sprintf("%s_%s_", cfg.Namespace, cfg.Subsystem) + metricsPrefix := "" + if len(cfg.Namespace) > 0 { + metricsPrefix += fmt.Sprintf("%s_", cfg.Namespace) + } + if len(cfg.Subsystem) > 0 { + metricsPrefix += fmt.Sprintf("%s_", cfg.Subsystem) + } metrics.Registerer = prometheus.WrapRegistererWithPrefix(metricsPrefix, reg) metrics.Registerer.MustRegister(promCollector.NewGoCollector()) - preloadLabelValues(&metrics, syncerKeys) + preloadLabelValues(&metrics, syncerKeys, moduleStageNames) return &metrics } +func createModulesMetrics(cfg config.PrometheusMetrics, registry *prometheus.Registry, m *Metrics, moduleStageNames map[string][]string, standardTimeBuckets []float64) { + l := len(moduleStageNames) + m.moduleDuration = make(map[string]*prometheus.HistogramVec, l) + m.moduleCalls = make(map[string]*prometheus.CounterVec, l) + m.moduleFailures = make(map[string]*prometheus.CounterVec, l) + m.moduleSuccessNoops = make(map[string]*prometheus.CounterVec, l) + m.moduleSuccessUpdates = make(map[string]*prometheus.CounterVec, l) + m.moduleSuccessRejects = make(map[string]*prometheus.CounterVec, l) + m.moduleExecutionErrors = make(map[string]*prometheus.CounterVec, l) + m.moduleTimeouts = make(map[string]*prometheus.CounterVec, l) + + // create for each registered module its own metric + for module := range moduleStageNames { + m.moduleDuration[module] = newHistogramVec(cfg, registry, + fmt.Sprintf("modules_%s_duration", module), + "Amount of seconds a module processed a hook labeled by stage name.", + []string{stageLabel}, + standardTimeBuckets) + + m.moduleCalls[module] = newCounter(cfg, registry, + fmt.Sprintf("modules_%s_called", module), + "Count of module calls labeled by stage name.", + []string{stageLabel}) + + m.moduleFailures[module] = newCounter(cfg, registry, + fmt.Sprintf("modules_%s_failed", module), + "Count of module fails labeled by stage name.", + []string{stageLabel}) + + m.moduleSuccessNoops[module] = newCounter(cfg, registry, + fmt.Sprintf("modules_%s_success_noops", module), + "Count of module successful noops labeled by stage name.", + []string{stageLabel}) + + m.moduleSuccessUpdates[module] = newCounter(cfg, registry, + fmt.Sprintf("modules_%s_success_updates", module), + "Count of module successful updates labeled by stage name.", + []string{stageLabel}) + + m.moduleSuccessRejects[module] = newCounter(cfg, registry, + fmt.Sprintf("modules_%s_success_rejects", module), + "Count of module successful rejects labeled by stage name.", + []string{stageLabel}) + + m.moduleExecutionErrors[module] = newCounter(cfg, registry, + fmt.Sprintf("modules_%s_execution_errors", module), + "Count of module execution errors labeled by stage name.", + []string{stageLabel}) + + m.moduleTimeouts[module] = newCounter(cfg, registry, + fmt.Sprintf("modules_%s_timeouts", module), + "Count of module timeouts labeled by stage name.", + []string{stageLabel}) + } +} + func newCounter(cfg config.PrometheusMetrics, registry *prometheus.Registry, name, help string, labels []string) *prometheus.CounterVec { opts := prometheus.CounterOpts{ Namespace: cfg.Namespace, @@ -477,6 +599,26 @@ func (m *Metrics) RecordRequest(labels metrics.Labels) { } } +func (m *Metrics) RecordDebugRequest(debugEnabled bool, pubID string) { + if debugEnabled { + m.debugRequests.Inc() + if !m.metricsDisabled.AccountDebug && pubID != metrics.PublisherUnknown { + m.accountDebugRequests.With(prometheus.Labels{ + accountLabel: pubID, + }).Inc() + } + } +} + +func (m *Metrics) RecordStoredResponse(pubId string) { + m.storedResponses.Inc() + if !m.metricsDisabled.AccountStoredResponses && pubId != metrics.PublisherUnknown { + m.accountStoredResponses.With(prometheus.Labels{ + accountLabel: pubId, + }).Inc() + } +} + func (m *Metrics) RecordImps(labels metrics.ImpLabels) { m.impressions.With(prometheus.Labels{ isBannerLabel: strconv.FormatBool(labels.BannerImps), @@ -516,6 +658,10 @@ func (m *Metrics) RecordStoredDataFetchTime(labels metrics.StoredDataLabels, len m.storedVideoFetchTimer.With(prometheus.Labels{ storedDataFetchTypeLabel: string(labels.DataFetchType), }).Observe(length.Seconds()) + case metrics.ResponseDataType: + m.storedResponsesFetchTimer.With(prometheus.Labels{ + storedDataFetchTypeLabel: string(labels.DataFetchType), + }).Observe(length.Seconds()) } } @@ -541,6 +687,10 @@ func (m *Metrics) RecordStoredDataError(labels metrics.StoredDataLabels) { m.storedVideoErrors.With(prometheus.Labels{ storedDataErrorLabel: string(labels.Error), }).Inc() + case metrics.ResponseDataType: + m.storedResponsesErrors.With(prometheus.Labels{ + storedDataErrorLabel: string(labels.Error), + }).Inc() } } @@ -731,3 +881,64 @@ func (m *Metrics) RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.Bidder adapterLabel: string(adapterName), }).Inc() } + +func (m *Metrics) RecordAdsCertReq(success bool) { + if success { + m.adsCertRequests.With(prometheus.Labels{ + successLabel: requestSuccessful, + }).Inc() + } else { + m.adsCertRequests.With(prometheus.Labels{ + successLabel: requestFailed, + }).Inc() + } +} +func (m *Metrics) RecordAdsCertSignTime(adsCertSignTime time.Duration) { + m.adsCertSignTimer.Observe(adsCertSignTime.Seconds()) +} + +func (m *Metrics) RecordModuleCalled(labels metrics.ModuleLabels, duration time.Duration) { + m.moduleCalls[labels.Module].With(prometheus.Labels{ + stageLabel: labels.Stage, + }).Inc() + + m.moduleDuration[labels.Module].With(prometheus.Labels{ + stageLabel: labels.Stage, + }).Observe(duration.Seconds()) +} + +func (m *Metrics) RecordModuleFailed(labels metrics.ModuleLabels) { + m.moduleFailures[labels.Module].With(prometheus.Labels{ + stageLabel: labels.Stage, + }).Inc() +} + +func (m *Metrics) RecordModuleSuccessNooped(labels metrics.ModuleLabels) { + m.moduleSuccessNoops[labels.Module].With(prometheus.Labels{ + stageLabel: labels.Stage, + }).Inc() +} + +func (m *Metrics) RecordModuleSuccessUpdated(labels metrics.ModuleLabels) { + m.moduleSuccessUpdates[labels.Module].With(prometheus.Labels{ + stageLabel: labels.Stage, + }).Inc() +} + +func (m *Metrics) RecordModuleSuccessRejected(labels metrics.ModuleLabels) { + m.moduleSuccessRejects[labels.Module].With(prometheus.Labels{ + stageLabel: labels.Stage, + }).Inc() +} + +func (m *Metrics) RecordModuleExecutionError(labels metrics.ModuleLabels) { + m.moduleExecutionErrors[labels.Module].With(prometheus.Labels{ + stageLabel: labels.Stage, + }).Inc() +} + +func (m *Metrics) RecordModuleTimeout(labels metrics.ModuleLabels) { + m.moduleTimeouts[labels.Module].With(prometheus.Labels{ + stageLabel: labels.Stage, + }).Inc() +} diff --git a/metrics/prometheus/prometheus_test.go b/metrics/prometheus/prometheus_test.go index c7b293dad5b..ce1fa085c9c 100644 --- a/metrics/prometheus/prometheus_test.go +++ b/metrics/prometheus/prometheus_test.go @@ -13,13 +13,15 @@ import ( "github.com/stretchr/testify/assert" ) +var modulesStages = map[string][]string{"foobar": {"entry", "raw"}, "another_module": {"raw", "auction"}} + func createMetricsForTesting() *Metrics { syncerKeys := []string{} return NewMetrics(config.PrometheusMetrics{ Port: 8080, Namespace: "prebid", Subsystem: "server", - }, config.DisabledMetrics{}, syncerKeys) + }, config.DisabledMetrics{}, syncerKeys, modulesStages) } func TestMetricCountGatekeeping(t *testing.T) { @@ -156,6 +158,54 @@ func TestRequestMetric(t *testing.T) { }) } +func TestDebugRequestMetric(t *testing.T) { + testCases := []struct { + description string + givenDebugEnabledFlag bool + givenAccountDebugMetricsDisabled bool + expectedAccountDebugCount float64 + expectedDebugCount float64 + }{ + { + description: "Debug is enabled and account debug is enabled, both metrics should be updated", + givenDebugEnabledFlag: true, + givenAccountDebugMetricsDisabled: false, + expectedDebugCount: 1, + expectedAccountDebugCount: 1, + }, + { + description: "Debug and account debug are disabled, niether metrics should be updated", + givenDebugEnabledFlag: false, + givenAccountDebugMetricsDisabled: true, + expectedDebugCount: 0, + expectedAccountDebugCount: 0, + }, + { + description: "Debug is enabled but account debug is disabled, only non-account debug count should increment", + givenDebugEnabledFlag: true, + givenAccountDebugMetricsDisabled: true, + expectedDebugCount: 1, + expectedAccountDebugCount: 0, + }, + { + description: "Debug is disabled and account debug is enabled, niether metrics should increment", + givenDebugEnabledFlag: false, + givenAccountDebugMetricsDisabled: false, + expectedDebugCount: 0, + expectedAccountDebugCount: 0, + }, + } + + for _, test := range testCases { + m := createMetricsForTesting() + m.metricsDisabled.AccountDebug = test.givenAccountDebugMetricsDisabled + m.RecordDebugRequest(test.givenDebugEnabledFlag, "acct-id") + + assertCounterVecValue(t, "", "account debug requests", m.accountDebugRequests, test.expectedAccountDebugCount, prometheus.Labels{accountLabel: "acct-id"}) + assertCounterValue(t, "", "debug requests", m.debugRequests, test.expectedDebugCount) + } +} + func TestRequestMetricWithoutCookie(t *testing.T) { requestType := metrics.ReqTypeORTB2Web performTest := func(m *Metrics, cookieFlag metrics.CookieFlag) { @@ -454,6 +504,11 @@ func TestRecordStoredDataFetchTime(t *testing.T) { dataType: metrics.VideoDataType, fetchType: metrics.FetchDelta, }, + { + description: "Update stored responses histogram with delta label", + dataType: metrics.ResponseDataType, + fetchType: metrics.FetchDelta, + }, } for _, tt := range tests { @@ -477,6 +532,8 @@ func TestRecordStoredDataFetchTime(t *testing.T) { metricsTimer = m.storedRequestFetchTimer case metrics.VideoDataType: metricsTimer = m.storedVideoFetchTimer + case metrics.ResponseDataType: + metricsTimer = m.storedResponsesFetchTimer } result := getHistogramFromHistogramVec( @@ -554,6 +611,12 @@ func TestRecordStoredDataError(t *testing.T) { errorType: metrics.StoredDataErrorUndefined, metricName: "stored_video_errors", }, + { + description: "Update stored_response_errors counter with network label", + dataType: metrics.ResponseDataType, + errorType: metrics.StoredDataErrorNetwork, + metricName: "stored_response_errors", + }, } for _, tt := range tests { @@ -575,6 +638,8 @@ func TestRecordStoredDataError(t *testing.T) { metricsCounter = m.storedRequestErrors case metrics.VideoDataType: metricsCounter = m.storedVideoErrors + case metrics.ResponseDataType: + metricsCounter = m.storedResponsesErrors } assertCounterVecValue(t, tt.description, tt.metricName, metricsCounter, @@ -1413,7 +1478,7 @@ func TestDisabledMetrics(t *testing.T) { AdapterConnectionMetrics: true, AdapterGDPRRequestBlocked: true, }, - nil) + nil, nil) // Assert counter vector was not initialized assert.Nil(t, prometheusMetrics.adapterReusedConnections, "Counter Vector adapterReusedConnections should be nil") @@ -1578,3 +1643,186 @@ func TestRecordAdapterGDPRRequestBlocked(t *testing.T) { adapterLabel: string(openrtb_ext.BidderAppnexus), }) } + +func TestStoredResponsesMetric(t *testing.T) { + testCases := []struct { + description string + publisherId string + accountStoredResponsesMetricsDisabled bool + expectedAccountStoredResponsesCount float64 + expectedStoredResponsesCount float64 + }{ + + { + description: "Publisher id is given, account stored responses enabled, expected both account stored responses and stored responses counter to have a record", + publisherId: "acct-id", + accountStoredResponsesMetricsDisabled: false, + expectedAccountStoredResponsesCount: 1, + expectedStoredResponsesCount: 1, + }, + { + description: "Publisher id is given, account stored responses disabled, expected stored responses counter only to have a record", + publisherId: "acct-id", + accountStoredResponsesMetricsDisabled: true, + expectedAccountStoredResponsesCount: 0, + expectedStoredResponsesCount: 1, + }, + { + description: "Publisher id is unknown, account stored responses enabled, expected stored responses counter only to have a record", + publisherId: metrics.PublisherUnknown, + accountStoredResponsesMetricsDisabled: true, + expectedAccountStoredResponsesCount: 0, + expectedStoredResponsesCount: 1, + }, + { + description: "Publisher id is unknown, account stored responses disabled, expected stored responses counter only to have a record", + publisherId: metrics.PublisherUnknown, + accountStoredResponsesMetricsDisabled: false, + expectedAccountStoredResponsesCount: 0, + expectedStoredResponsesCount: 1, + }, + } + + for _, test := range testCases { + m := createMetricsForTesting() + m.metricsDisabled.AccountStoredResponses = test.accountStoredResponsesMetricsDisabled + m.RecordStoredResponse(test.publisherId) + + assertCounterVecValue(t, "", "account stored responses", m.accountStoredResponses, test.expectedAccountStoredResponsesCount, prometheus.Labels{accountLabel: "acct-id"}) + assertCounterValue(t, "", "stored responses", m.storedResponses, test.expectedStoredResponsesCount) + } +} + +func TestRecordAdsCertReqMetric(t *testing.T) { + testCases := []struct { + description string + requestSuccess bool + expectedSuccessRequestsCount float64 + expectedFailedRequestsCount float64 + }{ + { + description: "Record failed request, expected success request count is 0 and failed request count is 1", + requestSuccess: false, + expectedSuccessRequestsCount: 0, + expectedFailedRequestsCount: 1, + }, + { + description: "Record successful request, expected success request count is 1 and failed request count is 0", + requestSuccess: true, + expectedSuccessRequestsCount: 1, + expectedFailedRequestsCount: 0, + }, + } + + for _, test := range testCases { + m := createMetricsForTesting() + m.RecordAdsCertReq(test.requestSuccess) + assertCounterVecValue(t, test.description, "successfully signed requests", m.adsCertRequests, test.expectedSuccessRequestsCount, prometheus.Labels{successLabel: requestSuccessful}) + assertCounterVecValue(t, test.description, "unsuccessfully signed requests", m.adsCertRequests, test.expectedFailedRequestsCount, prometheus.Labels{successLabel: requestFailed}) + } +} + +func TestRecordAdsCertSignTime(t *testing.T) { + type testIn struct { + adsCertSignDuration time.Duration + } + type testOut struct { + expDuration float64 + expCount uint64 + } + testCases := []struct { + description string + in testIn + out testOut + }{ + { + description: "Five second AdsCert sign time", + in: testIn{ + adsCertSignDuration: time.Second * 5, + }, + out: testOut{ + expDuration: 5, + expCount: 1, + }, + }, + { + description: "Five millisecond AdsCert sign time", + in: testIn{ + adsCertSignDuration: time.Millisecond * 5, + }, + out: testOut{ + expDuration: 0.005, + expCount: 1, + }, + }, + { + description: "Zero AdsCert sign time", + in: testIn{}, + out: testOut{ + expDuration: 0, + expCount: 1, + }, + }, + } + for i, test := range testCases { + pm := createMetricsForTesting() + pm.RecordAdsCertSignTime(test.in.adsCertSignDuration) + + m := dto.Metric{} + pm.adsCertSignTimer.Write(&m) + histogram := *m.GetHistogram() + + assert.Equal(t, test.out.expCount, histogram.GetSampleCount(), "[%d] Incorrect number of histogram entries. Desc: %s\n", i, test.description) + assert.Equal(t, test.out.expDuration, histogram.GetSampleSum(), "[%d] Incorrect number of histogram cumulative values. Desc: %s\n", i, test.description) + } +} + +func TestRecordModuleMetrics(t *testing.T) { + m := createMetricsForTesting() + + // check that each module has its own metric recorded with correct stage labels + for module, stages := range modulesStages { + for _, stage := range stages { + // first record the metrics + m.RecordModuleCalled(metrics.ModuleLabels{ + Module: module, + Stage: stage, + }, time.Millisecond*1) + m.RecordModuleFailed(metrics.ModuleLabels{ + Module: module, + Stage: stage, + }) + m.RecordModuleSuccessNooped(metrics.ModuleLabels{ + Module: module, + Stage: stage, + }) + m.RecordModuleSuccessUpdated(metrics.ModuleLabels{ + Module: module, + Stage: stage, + }) + m.RecordModuleSuccessRejected(metrics.ModuleLabels{ + Module: module, + Stage: stage, + }) + m.RecordModuleExecutionError(metrics.ModuleLabels{ + Module: module, + Stage: stage, + }) + m.RecordModuleTimeout(metrics.ModuleLabels{ + Module: module, + Stage: stage, + }) + + // now check that the values are correct + result := getHistogramFromHistogramVec(m.moduleDuration[module], stageLabel, stage) + assertHistogram(t, fmt.Sprintf("module_%s_duration", module), result, 1, 0.001) + assertCounterVecValue(t, "Module calls performed", fmt.Sprintf("%s metric recorded during %s stage", module, stage), m.moduleCalls[module], 1, prometheus.Labels{stageLabel: stage}) + assertCounterVecValue(t, "Module calls failed", fmt.Sprintf("%s metric recorded during %s stage", module, stage), m.moduleFailures[module], 1, prometheus.Labels{stageLabel: stage}) + assertCounterVecValue(t, "Module success noop action", fmt.Sprintf("%s metric recorded during %s stage", module, stage), m.moduleSuccessNoops[module], 1, prometheus.Labels{stageLabel: stage}) + assertCounterVecValue(t, "Module success update action", fmt.Sprintf("%s metric recorded during %s stage", module, stage), m.moduleSuccessUpdates[module], 1, prometheus.Labels{stageLabel: stage}) + assertCounterVecValue(t, "Module success reject action", fmt.Sprintf("%s metric recorded during %s stage", module, stage), m.moduleSuccessRejects[module], 1, prometheus.Labels{stageLabel: stage}) + assertCounterVecValue(t, "Module execution error", fmt.Sprintf("%s metric recorded during %s stage", module, stage), m.moduleExecutionErrors[module], 1, prometheus.Labels{stageLabel: stage}) + assertCounterVecValue(t, "Module timeout", fmt.Sprintf("%s metric recorded during %s stage", module, stage), m.moduleTimeouts[module], 1, prometheus.Labels{stageLabel: stage}) + } + } +} diff --git a/modules/builder.go b/modules/builder.go new file mode 100644 index 00000000000..803ab8bfea1 --- /dev/null +++ b/modules/builder.go @@ -0,0 +1,7 @@ +package modules + +// builders returns mapping between module name and its builder +// vendor and module names are chosen based on the module directory name +func builders() ModuleBuilders { + return ModuleBuilders{} +} diff --git a/modules/generator/builder.tmpl b/modules/generator/builder.tmpl new file mode 100644 index 00000000000..f89cc21c87f --- /dev/null +++ b/modules/generator/builder.tmpl @@ -0,0 +1,21 @@ +package modules + +{{if .}} +import ( + {{- range .}} + {{.Vendor}}{{.Module | Title}} "github.com/prebid/prebid-server/modules/{{.Vendor}}/{{.Module}}" + {{- end}} +) +{{end}} + +// builders returns mapping between module name and its builder +// vendor and module names are chosen based on the module directory name +func builders() ModuleBuilders { + return ModuleBuilders{ + {{- range .}} + "{{.Vendor}}": { + "{{.Module}}": {{.Vendor}}{{.Module | Title}}.Builder, + }, + {{- end}} + } +} diff --git a/modules/generator/buildergen.go b/modules/generator/buildergen.go new file mode 100644 index 00000000000..b906682b7a3 --- /dev/null +++ b/modules/generator/buildergen.go @@ -0,0 +1,70 @@ +//go:build ignore + +package main + +import ( + "bytes" + "fmt" + "go/format" + "io/fs" + "os" + "path/filepath" + "regexp" + "strings" + "text/template" +) + +var ( + r = regexp.MustCompile("^([^/]+)/([^/]+)/module.go$") + tmplName = "builder.tmpl" + outName = "builder.go" +) + +type Module struct { + Vendor string + Module string +} + +func main() { + var modules []Module + + filepath.WalkDir("./", func(path string, d fs.DirEntry, err error) error { + if !r.MatchString(path) { + return nil + } + match := r.FindStringSubmatch(path) + modules = append(modules, Module{ + Vendor: match[1], + Module: match[2], + }) + return nil + }) + + funcMap := template.FuncMap{"Title": strings.Title} + t, err := template.New(tmplName).Funcs(funcMap).ParseFiles(fmt.Sprintf("generator/%s", tmplName)) + if err != nil { + panic(fmt.Sprintf("failed to parse builder template: %s", err)) + } + + f, err := os.Create(outName) + if err != nil { + panic(fmt.Sprintf("failed to create %s file: %s", outName, err)) + } + defer f.Close() + + var buf bytes.Buffer + if err = t.Execute(&buf, modules); err != nil { + panic(fmt.Sprintf("failed to generate %s file content: %s", outName, err)) + } + + content, err := format.Source(buf.Bytes()) + if err != nil { + panic(fmt.Sprintf("failed to format generated code: %s", err)) + } + + if _, err = f.Write(content); err != nil { + panic(fmt.Sprintf("failed to write file content: %s", err)) + } + + fmt.Printf("%s file successfully generated\n", outName) +} diff --git a/modules/helpers.go b/modules/helpers.go new file mode 100644 index 00000000000..c7fe9f73f31 --- /dev/null +++ b/modules/helpers.go @@ -0,0 +1,73 @@ +package modules + +import ( + "fmt" + "strings" + + "github.com/prebid/prebid-server/hooks" + "github.com/prebid/prebid-server/hooks/hookstage" +) + +var moduleReplacer = strings.NewReplacer(".", "_", "-", "_") + +func createModuleStageNamesCollection(modules map[string]interface{}) (map[string][]string, error) { + moduleStageNameCollector := make(map[string][]string) + var added bool + + for id, hook := range modules { + if _, ok := hook.(hookstage.Entrypoint); ok { + added = true + stageName := hooks.StageEntrypoint.String() + moduleStageNameCollector = addModuleStageName(moduleStageNameCollector, id, stageName) + } + + if _, ok := hook.(hookstage.RawAuctionRequest); ok { + added = true + stageName := hooks.StageRawAuctionRequest.String() + moduleStageNameCollector = addModuleStageName(moduleStageNameCollector, id, stageName) + } + + if _, ok := hook.(hookstage.ProcessedAuctionRequest); ok { + added = true + stageName := hooks.StageProcessedAuctionRequest.String() + moduleStageNameCollector = addModuleStageName(moduleStageNameCollector, id, stageName) + } + + if _, ok := hook.(hookstage.BidderRequest); ok { + added = true + stageName := hooks.StageBidderRequest.String() + moduleStageNameCollector = addModuleStageName(moduleStageNameCollector, id, stageName) + } + + if _, ok := hook.(hookstage.RawBidderResponse); ok { + added = true + stageName := hooks.StageRawBidderResponse.String() + moduleStageNameCollector = addModuleStageName(moduleStageNameCollector, id, stageName) + } + + if _, ok := hook.(hookstage.AllProcessedBidResponses); ok { + added = true + stageName := hooks.StageAllProcessedBidResponses.String() + moduleStageNameCollector = addModuleStageName(moduleStageNameCollector, id, stageName) + } + + if _, ok := hook.(hookstage.AuctionResponse); ok { + added = true + stageName := hooks.StageAuctionResponse.String() + moduleStageNameCollector = addModuleStageName(moduleStageNameCollector, id, stageName) + } + + if !added { + return nil, fmt.Errorf(`hook "%s" does not implement any supported hook interface`, id) + } + } + + return moduleStageNameCollector, nil +} + +func addModuleStageName(moduleStageNameCollector map[string][]string, id string, stage string) map[string][]string { + str := moduleReplacer.Replace(id) + moduleStageNameCollector[str] = append(moduleStageNameCollector[str], stage) + + return moduleStageNameCollector +} diff --git a/modules/modules.go b/modules/modules.go new file mode 100644 index 00000000000..e595e1e1819 --- /dev/null +++ b/modules/modules.go @@ -0,0 +1,78 @@ +package modules + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/hooks" +) + +//go:generate go run ./generator/buildergen.go + +// NewBuilder returns a new module builder. +func NewBuilder() Builder { + return &builder{builders()} +} + +// Builder is the interfaces intended for building modules +// implementing hook interfaces [github.com/prebid/prebid-server/hooks/hookstage]. +type Builder interface { + // Build initializes existing hook modules passing them config and other dependencies. + // It returns hook repository created based on the implemented hook interfaces by modules + // and a map of modules to a list of stage names for which module provides hooks + // or an error encountered during module initialization. + Build(cfg config.Modules, client *http.Client) (hooks.HookRepository, map[string][]string, error) +} + +type ( + // ModuleBuilders mapping between module name and its builder: map[vendor]map[module]ModuleBuilderFn + ModuleBuilders map[string]map[string]ModuleBuilderFn + // ModuleBuilderFn returns an interface{} type that implements certain hook interfaces + ModuleBuilderFn func(cfg json.RawMessage, client *http.Client) (interface{}, error) +) + +type builder struct { + builders ModuleBuilders +} + +// Build walks over the list of registered modules and initializes them. +// +// The ID chosen for the module's hooks represents a fully qualified module path in the format +// "vendor.module_name" and should be used to retrieve module hooks from the hooks.HookRepository. +// +// Method returns a hooks.HookRepository and a map of modules to a list of stage names +// for which module provides hooks or an error occurred during modules initialization. +func (m *builder) Build(cfg config.Modules, client *http.Client) (hooks.HookRepository, map[string][]string, error) { + modules := make(map[string]interface{}) + for vendor, moduleBuilders := range m.builders { + for moduleName, builder := range moduleBuilders { + var err error + var conf json.RawMessage + + id := fmt.Sprintf("%s.%s", vendor, moduleName) + if data, ok := cfg[vendor][moduleName]; ok { + if conf, err = json.Marshal(data); err != nil { + return nil, nil, fmt.Errorf(`failed to marshal "%s" module config: %s`, id, err) + } + } + + module, err := builder(conf, client) + if err != nil { + return nil, nil, fmt.Errorf(`failed to init "%s" module: %s`, id, err) + } + + modules[id] = module + } + } + + collection, err := createModuleStageNamesCollection(modules) + if err != nil { + return nil, nil, err + } + + repo, err := hooks.NewHookRepository(modules) + + return repo, collection, err +} diff --git a/modules/modules_test.go b/modules/modules_test.go new file mode 100644 index 00000000000..d7c150a64bd --- /dev/null +++ b/modules/modules_test.go @@ -0,0 +1,120 @@ +package modules + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "math" + "net/http" + "testing" + + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/hooks" + "github.com/prebid/prebid-server/hooks/hookstage" + "github.com/stretchr/testify/assert" +) + +func TestModuleBuilderBuild(t *testing.T) { + vendor := "acme" + moduleName := "foobar" + + testCases := map[string]struct { + isHookFound bool + expectedModStageColl map[string][]string + expectedHook interface{} + givenModule interface{} + givenConfig config.Modules + expectedErr error + givenHookBuilderErr error + givenGetHookFn func(repo hooks.HookRepository, module string) (interface{}, bool) + }{ + "Can build with entrypoint hook without config": { + isHookFound: true, + expectedModStageColl: map[string][]string{vendor + "_" + moduleName: {hooks.StageEntrypoint.String(), hooks.StageAuctionResponse.String()}}, + expectedHook: module{}, + givenModule: module{}, + givenGetHookFn: func(repo hooks.HookRepository, module string) (interface{}, bool) { + return repo.GetEntrypointHook(module) + }, + }, + "Can build with entrypoint hook with config": { + isHookFound: true, + expectedModStageColl: map[string][]string{vendor + "_" + moduleName: {hooks.StageEntrypoint.String(), hooks.StageAuctionResponse.String()}}, + expectedHook: module{}, + givenModule: module{}, + givenConfig: map[string]map[string]interface{}{vendor: {moduleName: map[string]bool{"enabled": true}}}, + givenGetHookFn: func(repo hooks.HookRepository, module string) (interface{}, bool) { + return repo.GetEntrypointHook(module) + }, + }, + "Can build with auction response hook": { + isHookFound: true, + expectedModStageColl: map[string][]string{vendor + "_" + moduleName: {hooks.StageEntrypoint.String(), hooks.StageAuctionResponse.String()}}, + expectedHook: module{}, + givenModule: module{}, + givenConfig: map[string]map[string]interface{}{"vendor": {"module": map[string]bool{"enabled": true}}}, + givenGetHookFn: func(repo hooks.HookRepository, module string) (interface{}, bool) { + return repo.GetAuctionResponseHook(module) + }, + }, + "Fails to find not registered hook": { + isHookFound: false, + expectedModStageColl: map[string][]string{vendor + "_" + moduleName: {hooks.StageEntrypoint.String(), hooks.StageAuctionResponse.String()}}, + expectedHook: nil, + givenModule: module{}, + givenConfig: map[string]map[string]interface{}{vendor: {"module": map[string]bool{"enabled": true}}}, + givenGetHookFn: func(repo hooks.HookRepository, module string) (interface{}, bool) { + return repo.GetAllProcessedBidResponsesHook(module) // ask for hook not implemented in module + }, + }, + "Fails if module does not implement any hook interface": { + expectedHook: struct{}{}, + expectedErr: fmt.Errorf(`hook "%s.%s" does not implement any supported hook interface`, vendor, moduleName), + }, + "Fails if module builder function returns error": { + givenModule: module{}, + givenConfig: map[string]map[string]interface{}{vendor: {moduleName: map[string]string{"media_type": "video"}}}, + givenHookBuilderErr: errors.New("failed to build module"), + expectedErr: fmt.Errorf(`failed to init "%s.%s" module: %s`, vendor, moduleName, "failed to build module"), + }, + "Fails if config marshaling returns error": { + givenModule: module{}, + givenConfig: map[string]map[string]interface{}{vendor: {moduleName: math.Inf(1)}}, + expectedErr: fmt.Errorf(`failed to marshal "%s.%s" module config: json: unsupported value: +Inf`, vendor, moduleName), + }, + } + + for name, test := range testCases { + t.Run(name, func(t *testing.T) { + builder := &builder{ + builders: ModuleBuilders{ + vendor: { + moduleName: func(cfg json.RawMessage, client *http.Client) (interface{}, error) { + return test.givenModule, test.givenHookBuilderErr + }, + }, + }, + } + + repo, coll, err := builder.Build(test.givenConfig, http.DefaultClient) + assert.Equal(t, test.expectedErr, err) + if test.expectedErr == nil { + hook, found := test.givenGetHookFn(repo, fmt.Sprintf("%s.%s", vendor, moduleName)) + assert.Equal(t, test.isHookFound, found) + assert.IsType(t, test.expectedHook, hook) + assert.Equal(t, test.expectedModStageColl, coll) + } + }) + } +} + +type module struct{} + +func (h module) HandleEntrypointHook(_ context.Context, _ hookstage.ModuleInvocationContext, _ hookstage.EntrypointPayload) (hookstage.HookResult[hookstage.EntrypointPayload], error) { + return hookstage.HookResult[hookstage.EntrypointPayload]{}, nil +} + +func (h module) HandleAuctionResponseHook(_ context.Context, _ hookstage.ModuleInvocationContext, _ hookstage.AuctionResponsePayload) (hookstage.HookResult[hookstage.AuctionResponsePayload], error) { + return hookstage.HookResult[hookstage.AuctionResponsePayload]{}, nil +} diff --git a/openrtb_ext/alternatebiddercodes.go b/openrtb_ext/alternatebiddercodes.go new file mode 100644 index 00000000000..49291a3e5e8 --- /dev/null +++ b/openrtb_ext/alternatebiddercodes.go @@ -0,0 +1,58 @@ +package openrtb_ext + +import "fmt" + +// ExtAlternateBidderCodes defines list of alternate bidder codes allowed by adatpers. This overrides host level configs. +type ExtAlternateBidderCodes struct { + Enabled bool `mapstructure:"enabled" json:"enabled"` + Bidders map[string]ExtAdapterAlternateBidderCodes `mapstructure:"bidders" json:"bidders"` +} + +type ExtAdapterAlternateBidderCodes struct { + Enabled bool `mapstructure:"enabled" json:"enabled"` + AllowedBidderCodes []string `mapstructure:"allowedbiddercodes" json:"allowedbiddercodes"` +} + +func (bidderCodes *ExtAlternateBidderCodes) IsValidBidderCode(bidder, alternateBidder string) (bool, error) { + if alternateBidder == "" || bidder == alternateBidder { + return true, nil + } + + if !bidderCodes.Enabled { + return false, alternateBidderDisabledError(bidder, alternateBidder) + } + + if bidderCodes.Bidders == nil { + return false, alternateBidderNotDefinedError(bidder, alternateBidder) + } + + adapterCfg, ok := bidderCodes.Bidders[bidder] + if !ok { + return false, alternateBidderNotDefinedError(bidder, alternateBidder) + } + + if !adapterCfg.Enabled { + // config has bidder entry but is not enabled, report it + return false, alternateBidderDisabledError(bidder, alternateBidder) + } + + if adapterCfg.AllowedBidderCodes == nil || (len(adapterCfg.AllowedBidderCodes) == 1 && adapterCfg.AllowedBidderCodes[0] == "*") { + return true, nil + } + + for _, code := range adapterCfg.AllowedBidderCodes { + if alternateBidder == code { + return true, nil + } + } + + return false, fmt.Errorf("invalid biddercode %q sent by adapter %q", alternateBidder, bidder) +} + +func alternateBidderDisabledError(bidder, alternateBidder string) error { + return fmt.Errorf("alternateBidderCodes disabled for %q, rejecting bids for %q", bidder, alternateBidder) +} + +func alternateBidderNotDefinedError(bidder, alternateBidder string) error { + return fmt.Errorf("alternateBidderCodes not defined for adapter %q, rejecting bids for %q", bidder, alternateBidder) +} diff --git a/openrtb_ext/alternatebiddercodes_test.go b/openrtb_ext/alternatebiddercodes_test.go new file mode 100644 index 00000000000..438aebad559 --- /dev/null +++ b/openrtb_ext/alternatebiddercodes_test.go @@ -0,0 +1,180 @@ +package openrtb_ext + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAlternateBidderCodes_IsValidBidderCode(t *testing.T) { + type fields struct { + Enabled bool + Bidders map[string]ExtAdapterAlternateBidderCodes + } + type args struct { + bidder string + alternateBidder string + } + tests := []struct { + name string + fields fields + args args + wantIsValid bool + wantErr error + }{ + { + name: "alternateBidder is not set/blank (default non-extra bid case)", + wantIsValid: true, + }, + { + name: "alternateBidder and bidder are same (default non-extra bid case with seat's alternateBidder explicitly set)", + args: args{ + bidder: "pubmatic", + alternateBidder: "pubmatic", + }, + wantIsValid: true, + }, + { + name: "account.alternatebiddercodes config not defined (default, reject bid)", + args: args{ + bidder: "pubmatic", + alternateBidder: "groupm", + }, + wantIsValid: false, + wantErr: errors.New(`alternateBidderCodes disabled for "pubmatic", rejecting bids for "groupm"`), + }, + { + name: "account.alternatebiddercodes config enabled but adapter config not defined", + args: args{ + bidder: "pubmatic", + alternateBidder: "groupm", + }, + fields: fields{Enabled: true}, + wantIsValid: false, + wantErr: errors.New(`alternateBidderCodes not defined for adapter "pubmatic", rejecting bids for "groupm"`), + }, + { + name: "account.alternatebiddercodes config enabled but adapter config is not available", + args: args{ + bidder: "pubmatic", + alternateBidder: "groupm", + }, + fields: fields{ + Enabled: true, + Bidders: map[string]ExtAdapterAlternateBidderCodes{ + "appnexus": {}, + }, + }, + wantIsValid: false, + wantErr: errors.New(`alternateBidderCodes not defined for adapter "pubmatic", rejecting bids for "groupm"`), + }, + { + name: "account.alternatebiddercodes config enabled but adapter config is disabled", + args: args{ + bidder: "pubmatic", + alternateBidder: "groupm", + }, + fields: fields{ + Enabled: true, + Bidders: map[string]ExtAdapterAlternateBidderCodes{ + "pubmatic": {Enabled: false}, + }, + }, + wantIsValid: false, + wantErr: errors.New(`alternateBidderCodes disabled for "pubmatic", rejecting bids for "groupm"`), + }, + { + name: "account.alternatebiddercodes and adapter config enabled but adapter config does not have allowedBidderCodes defined", + args: args{ + bidder: "pubmatic", + alternateBidder: "groupm", + }, + fields: fields{ + Enabled: true, + Bidders: map[string]ExtAdapterAlternateBidderCodes{ + "pubmatic": {Enabled: true}, + }, + }, + wantIsValid: true, + }, + { + name: "allowedBidderCodes is *", + args: args{ + bidder: "pubmatic", + alternateBidder: "groupm", + }, + fields: fields{ + Enabled: true, + Bidders: map[string]ExtAdapterAlternateBidderCodes{ + "pubmatic": { + Enabled: true, + AllowedBidderCodes: []string{"*"}, + }, + }, + }, + wantIsValid: true, + }, + { + name: "allowedBidderCodes is in the list", + args: args{ + bidder: "pubmatic", + alternateBidder: "groupm", + }, + fields: fields{ + Enabled: true, + Bidders: map[string]ExtAdapterAlternateBidderCodes{ + "pubmatic": { + Enabled: true, + AllowedBidderCodes: []string{"groupm"}, + }, + }, + }, + wantIsValid: true, + }, + { + name: "allowedBidderCodes is not in the list", + args: args{ + bidder: "pubmatic", + alternateBidder: "groupm", + }, + fields: fields{ + Enabled: true, + Bidders: map[string]ExtAdapterAlternateBidderCodes{ + "pubmatic": { + Enabled: true, + AllowedBidderCodes: []string{"xyz"}, + }, + }, + }, + wantIsValid: false, + wantErr: errors.New(`invalid biddercode "groupm" sent by adapter "pubmatic"`), + }, + { + name: "account.alternatebiddercodes and adapter config enabled but adapter config has allowedBidderCodes list empty", + args: args{ + bidder: "pubmatic", + alternateBidder: "groupm", + }, + fields: fields{ + Enabled: true, + Bidders: map[string]ExtAdapterAlternateBidderCodes{ + "pubmatic": {Enabled: true, AllowedBidderCodes: []string{}}, + }, + }, + wantIsValid: false, + wantErr: errors.New(`invalid biddercode "groupm" sent by adapter "pubmatic"`), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &ExtAlternateBidderCodes{ + Enabled: tt.fields.Enabled, + Bidders: tt.fields.Bidders, + } + gotIsValid, gotErr := a.IsValidBidderCode(tt.args.bidder, tt.args.alternateBidder) + assert.Equal(t, tt.wantIsValid, gotIsValid) + assert.Equal(t, tt.wantErr, gotErr) + }) + } +} diff --git a/openrtb_ext/app.go b/openrtb_ext/app.go index 658262537bf..3e55a980f5d 100644 --- a/openrtb_ext/app.go +++ b/openrtb_ext/app.go @@ -6,10 +6,7 @@ type ExtApp struct { } // ExtAppPrebid further defines the contract for bidrequest.app.ext.prebid. -// We are only enforcing that these two properties be strings if they are provided. -// They are optional with no current constraints on value, so we don't need a custom -// UnmarshalJSON() method at this time. type ExtAppPrebid struct { - Source string `json:"source"` - Version string `json:"version"` + Source string `json:"source,omitempty"` + Version string `json:"version,omitempty"` } diff --git a/openrtb_ext/bid.go b/openrtb_ext/bid.go index 1b72b25bd92..61c720fe023 100644 --- a/openrtb_ext/bid.go +++ b/openrtb_ext/bid.go @@ -23,6 +23,7 @@ type ExtBidPrebid struct { Video *ExtBidPrebidVideo `json:"video,omitempty"` Events *ExtBidPrebidEvents `json:"events,omitempty"` BidId string `json:"bidid,omitempty"` + Passthrough json.RawMessage `json:"passthrough,omitempty"` } // ExtBidPrebidCache defines the contract for bidresponse.seatbid.bid[i].ext.prebid.cache @@ -53,6 +54,7 @@ type ExtBidPrebidMeta struct { NetworkName string `json:"networkName,omitempty"` PrimaryCategoryID string `json:"primaryCatId,omitempty"` SecondaryCategoryIDs []string `json:"secondaryCatIds,omitempty"` + AdapterCode string `json:"adaptercode,omitempty"` } // ExtBidPrebidVideo defines the contract for bidresponse.seatbid.bid[i].ext.prebid.video @@ -172,4 +174,5 @@ const ( StoredRequestAttributes = "storedrequestattributes" OriginalBidCpmKey = "origbidcpm" OriginalBidCurKey = "origbidcur" + Passthrough = "passthrough" ) diff --git a/openrtb_ext/bid_request_video.go b/openrtb_ext/bid_request_video.go index 29e62da3d35..171bd6dcb47 100644 --- a/openrtb_ext/bid_request_video.go +++ b/openrtb_ext/bid_request_video.go @@ -1,6 +1,6 @@ package openrtb_ext -import "github.com/mxmCherry/openrtb/v15/openrtb2" +import "github.com/prebid/openrtb/v17/openrtb2" type BidRequestVideo struct { // Attribute: diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 05328ee4c92..2fde9c5b96e 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -5,7 +5,7 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" + "os" "path/filepath" "strings" @@ -35,6 +35,7 @@ const ( BidderReservedGPID BidderName = "gpid" // Reserved for Global Placement ID (GPID). BidderReservedPrebid BidderName = "prebid" // Reserved for Prebid Server configuration. BidderReservedSKAdN BidderName = "skadn" // Reserved for Apple's SKAdNetwork OpenRTB extension. + BidderReservedTID BidderName = "tid" // Reserved for Per-Impression Transactions IDs for Multi-Impression Bid Requests. ) // IsBidderNameReserved returns true if the specified name is a case insensitive match for a reserved bidder name. @@ -67,6 +68,10 @@ func IsBidderNameReserved(name string) bool { return true } + if strings.EqualFold(name, string(BidderReservedTID)) { + return true + } + return false } @@ -94,7 +99,9 @@ const ( BidderAdot BidderName = "adot" BidderAdpone BidderName = "adpone" BidderAdprime BidderName = "adprime" + BidderAdrino BidderName = "adrino" BidderAdtarget BidderName = "adtarget" + BidderAdtrgtme BidderName = "adtrgtme" BidderAdtelligent BidderName = "adtelligent" BidderAdvangelists BidderName = "advangelists" BidderAdView BidderName = "adview" @@ -106,19 +113,26 @@ const ( BidderApacdex BidderName = "apacdex" BidderApplogy BidderName = "applogy" BidderAppnexus BidderName = "appnexus" + BidderAppush BidderName = "appush" BidderAudienceNetwork BidderName = "audienceNetwork" + BidderAutomatad BidderName = "automatad" BidderAvocet BidderName = "avocet" BidderAxonix BidderName = "axonix" BidderBeachfront BidderName = "beachfront" BidderBeintoo BidderName = "beintoo" BidderBetween BidderName = "between" + BidderBeyondMedia BidderName = "beyondmedia" BidderBidmachine BidderName = "bidmachine" BidderBidmyadz BidderName = "bidmyadz" BidderBidsCube BidderName = "bidscube" + BidderBidstack BidderName = "bidstack" BidderBizzclick BidderName = "bizzclick" BidderBliink BidderName = "bliink" + BidderBlue BidderName = "blue" BidderBmtm BidderName = "bmtm" + BidderBoldwin BidderName = "boldwin" BidderBrightroll BidderName = "brightroll" + BidderCcx BidderName = "ccx" BidderCoinzilla BidderName = "coinzilla" BidderColossus BidderName = "colossus" BidderCompass BidderName = "compass" @@ -128,15 +142,18 @@ const ( BidderCpmstar BidderName = "cpmstar" BidderCriteo BidderName = "criteo" BidderDatablocks BidderName = "datablocks" - BidderDmx BidderName = "dmx" BidderDecenterAds BidderName = "decenterads" BidderDeepintent BidderName = "deepintent" + BidderDianomi BidderName = "dianomi" + BidderDmx BidderName = "dmx" BidderEmxDigital BidderName = "emx_digital" BidderEngageBDR BidderName = "engagebdr" BidderEngageBDROrtb BidderName = "engagebdr_ortb" BidderEPlanning BidderName = "eplanning" BidderEpom BidderName = "epom" BidderEVolution BidderName = "e_volution" + BidderFreewheelSSP BidderName = "freewheelssp" + BidderFreewheelSSPOld BidderName = "freewheel-ssp" BidderGamma BidderName = "gamma" BidderGamoshi BidderName = "gamoshi" BidderGrid BidderName = "grid" @@ -146,6 +163,7 @@ const ( BidderHuaweiAds BidderName = "huaweiads" BidderImpactify BidderName = "impactify" BidderImprovedigital BidderName = "improvedigital" + BidderInfyTV BidderName = "infytv" BidderInMobi BidderName = "inmobi" BidderInteractiveoffers BidderName = "interactiveoffers" BidderInvibes BidderName = "invibes" @@ -153,6 +171,7 @@ const ( BidderIx BidderName = "ix" BidderJANet BidderName = "janet" BidderJixie BidderName = "jixie" + BidderKargo BidderName = "kargo" BidderKayzen BidderName = "kayzen" BidderKidoz BidderName = "kidoz" BidderKrushmedia BidderName = "krushmedia" @@ -160,18 +179,18 @@ const ( BidderLockerDome BidderName = "lockerdome" BidderLogicad BidderName = "logicad" BidderLunaMedia BidderName = "lunamedia" - BidderSaLunaMedia BidderName = "sa_lunamedia" BidderMadvertise BidderName = "madvertise" BidderMarsmedia BidderName = "marsmedia" BidderMediafuse BidderName = "mediafuse" + BidderMedianet BidderName = "medianet" BidderMgid BidderName = "mgid" BidderMobfoxpb BidderName = "mobfoxpb" BidderMobileFuse BidderName = "mobilefuse" - BidderMedianet BidderName = "medianet" BidderNanoInteractive BidderName = "nanointeractive" BidderNextMillennium BidderName = "nextmillennium" BidderNinthDecimal BidderName = "ninthdecimal" BidderNoBid BidderName = "nobid" + BidderOFTMedia BidderName = "oftmedia" BidderOneTag BidderName = "onetag" BidderOpenWeb BidderName = "openweb" BidderOpenx BidderName = "openx" @@ -192,6 +211,8 @@ const ( BidderRichaudience BidderName = "richaudience" BidderRTBHouse BidderName = "rtbhouse" BidderRubicon BidderName = "rubicon" + BidderSeedingAlliance BidderName = "seedingAlliance" + BidderSaLunaMedia BidderName = "sa_lunamedia" BidderSharethrough BidderName = "sharethrough" BidderSilverMob BidderName = "silvermob" BidderSmaato BidderName = "smaato" @@ -202,8 +223,12 @@ const ( BidderSmileWanted BidderName = "smilewanted" BidderSonobi BidderName = "sonobi" BidderSovrn BidderName = "sovrn" + BidderSspBC BidderName = "sspBC" BidderStreamkey BidderName = "streamkey" + BidderStroeerCore BidderName = "stroeerCore" + BidderSuntContent BidderName = "suntContent" BidderSynacormedia BidderName = "synacormedia" + BidderTaboola BidderName = "taboola" BidderTappx BidderName = "tappx" BidderTelaria BidderName = "telaria" BidderTrafficGate BidderName = "trafficgate" @@ -217,15 +242,14 @@ const ( BidderVerizonMedia BidderName = "verizonmedia" BidderVideoByte BidderName = "videobyte" BidderVidoomy BidderName = "vidoomy" - BidderVisx BidderName = "visx" BidderViewdeos BidderName = "viewdeos" + BidderVisx BidderName = "visx" BidderVrtcal BidderName = "vrtcal" BidderYahooSSP BidderName = "yahoossp" BidderYeahmobi BidderName = "yeahmobi" BidderYieldlab BidderName = "yieldlab" BidderYieldmo BidderName = "yieldmo" BidderYieldone BidderName = "yieldone" - BidderYSSP BidderName = "yssp" BidderZeroClickFraud BidderName = "zeroclickfraud" ) @@ -250,7 +274,9 @@ func CoreBidderNames() []BidderName { BidderAdot, BidderAdpone, BidderAdprime, + BidderAdrino, BidderAdtarget, + BidderAdtrgtme, BidderAdtelligent, BidderAdvangelists, BidderAdView, @@ -262,19 +288,26 @@ func CoreBidderNames() []BidderName { BidderApacdex, BidderApplogy, BidderAppnexus, + BidderAppush, BidderAudienceNetwork, + BidderAutomatad, BidderAvocet, BidderAxonix, BidderBeachfront, BidderBeintoo, BidderBetween, + BidderBeyondMedia, BidderBidmachine, BidderBidmyadz, BidderBidsCube, + BidderBidstack, BidderBizzclick, BidderBliink, + BidderBlue, BidderBmtm, + BidderBoldwin, BidderBrightroll, + BidderCcx, BidderCoinzilla, BidderColossus, BidderCompass, @@ -286,6 +319,7 @@ func CoreBidderNames() []BidderName { BidderDatablocks, BidderDecenterAds, BidderDeepintent, + BidderDianomi, BidderDmx, BidderEmxDigital, BidderEngageBDR, @@ -293,6 +327,8 @@ func CoreBidderNames() []BidderName { BidderEPlanning, BidderEpom, BidderEVolution, + BidderFreewheelSSP, + BidderFreewheelSSPOld, BidderGamma, BidderGamoshi, BidderGrid, @@ -302,6 +338,7 @@ func CoreBidderNames() []BidderName { BidderHuaweiAds, BidderImpactify, BidderImprovedigital, + BidderInfyTV, BidderInMobi, BidderInteractiveoffers, BidderInvibes, @@ -309,6 +346,7 @@ func CoreBidderNames() []BidderName { BidderIx, BidderJANet, BidderJixie, + BidderKargo, BidderKayzen, BidderKidoz, BidderKrushmedia, @@ -316,7 +354,6 @@ func CoreBidderNames() []BidderName { BidderLockerDome, BidderLogicad, BidderLunaMedia, - BidderSaLunaMedia, BidderMadvertise, BidderMarsmedia, BidderMediafuse, @@ -328,14 +365,15 @@ func CoreBidderNames() []BidderName { BidderNextMillennium, BidderNinthDecimal, BidderNoBid, + BidderOFTMedia, BidderOneTag, BidderOpenWeb, BidderOpenx, BidderOperaads, BidderOrbidder, BidderOutbrain, - BidderPGAM, BidderPangle, + BidderPGAM, BidderPlaywire, BidderPlaywireOrtb, BidderPubmatic, @@ -348,6 +386,8 @@ func CoreBidderNames() []BidderName { BidderRichaudience, BidderRTBHouse, BidderRubicon, + BidderSeedingAlliance, + BidderSaLunaMedia, BidderSharethrough, BidderSilverMob, BidderSmaato, @@ -358,8 +398,12 @@ func CoreBidderNames() []BidderName { BidderSmileWanted, BidderSonobi, BidderSovrn, + BidderSspBC, BidderStreamkey, + BidderStroeerCore, + BidderSuntContent, BidderSynacormedia, + BidderTaboola, BidderTappx, BidderTelaria, BidderTrafficGate, @@ -381,7 +425,6 @@ func CoreBidderNames() []BidderName { BidderYieldlab, BidderYieldmo, BidderYieldone, - BidderYSSP, BidderZeroClickFraud, } } @@ -430,7 +473,7 @@ func NormalizeBidderName(name string) (BidderName, bool) { return bidderName, exists } -// The BidderParamValidator is used to enforce bidrequest.imp[i].ext.{anyBidder} values. +// The BidderParamValidator is used to enforce bidrequest.imp[i].ext.prebid.bidder.{anyBidder} values. // // This is treated differently from the other types because we rely on JSON-schemas to validate bidder params. type BidderParamValidator interface { @@ -442,7 +485,7 @@ type BidderParamValidator interface { // NewBidderParamsValidator makes a BidderParamValidator, assuming all the necessary files exist in the filesystem. // This will error if, for example, a Bidder gets added but no JSON schema is written for them. func NewBidderParamsValidator(schemaDirectory string) (BidderParamValidator, error) { - fileInfos, err := ioutil.ReadDir(schemaDirectory) + fileInfos, err := os.ReadDir(schemaDirectory) if err != nil { return nil, fmt.Errorf("Failed to read JSON schemas from directory %s. %v", schemaDirectory, err) } @@ -466,7 +509,7 @@ func NewBidderParamsValidator(schemaDirectory string) (BidderParamValidator, err return nil, fmt.Errorf("Failed to load json schema at %s: %v", toOpen, err) } - fileBytes, err := ioutil.ReadFile(fmt.Sprintf("%s/%s", schemaDirectory, fileInfo.Name())) + fileBytes, err := os.ReadFile(fmt.Sprintf("%s/%s", schemaDirectory, fileInfo.Name())) if err != nil { return nil, fmt.Errorf("Failed to read file %s/%s: %v", schemaDirectory, fileInfo.Name(), err) } diff --git a/openrtb_ext/bidders_test.go b/openrtb_ext/bidders_test.go index 056223e2e7e..f95d4a36540 100644 --- a/openrtb_ext/bidders_test.go +++ b/openrtb_ext/bidders_test.go @@ -113,6 +113,10 @@ func TestIsBidderNameReserved(t *testing.T) { {"skadn", true}, {"skADN", true}, {"SKADN", true}, + {"tid", true}, + {"TId", true}, + {"Tid", true}, + {"TiD", true}, {"notreserved", false}, } diff --git a/openrtb_ext/bidders_validate_test.go b/openrtb_ext/bidders_validate_test.go index f0a1d158773..8a8f9936147 100644 --- a/openrtb_ext/bidders_validate_test.go +++ b/openrtb_ext/bidders_validate_test.go @@ -50,7 +50,7 @@ func TestBidderUniquenessGatekeeping(t *testing.T) { // - Exclude duplicates of adapters for the same bidder, as it's unlikely a publisher will use both. var bidders []string for _, bidder := range CoreBidderNames() { - if bidder != BidderTripleliftNative && bidder != BidderAdkernelAdn { + if bidder != BidderTripleliftNative && bidder != BidderAdkernelAdn && bidder != BidderFreewheelSSPOld { bidders = append(bidders, string(bidder)) } } diff --git a/openrtb_ext/convert_down.go b/openrtb_ext/convert_down.go new file mode 100644 index 00000000000..d473271ad0f --- /dev/null +++ b/openrtb_ext/convert_down.go @@ -0,0 +1,179 @@ +package openrtb_ext + +func ConvertDownTo25(r *RequestWrapper) error { + // schain + if err := moveSupplyChainFrom26To25(r); err != nil { + return err + } + + // gdpr + if err := moveGDPRFrom26To25(r); err != nil { + return err + } + if err := moveConsentFrom26To25(r); err != nil { + return err + } + + // ccpa + if err := moveUSPrivacyFrom26To25(r); err != nil { + return err + } + + // eid + if err := moveEIDFrom26To25(r); err != nil { + return err + } + + // imp + for _, imp := range r.GetImp() { + if err := moveRewardedFrom26ToPrebidExt(imp); err != nil { + return err + } + } + + // Do not remove new OpenRTB 2.6 fields. The spec specifies that bidders and exchanges + // must tolerate new or unexpected fields gracefully, either ignoring them or treating + // them as unknown. + + return nil +} + +// moveSupplyChainFrom26To25 modifies the request to move the OpenRTB 2.6 supply chain +// object (req.source.schain) to the OpenRTB 2.5 location (req.source.ext.schain). If the +// OpenRTB 2.5 location is already present it may be overwritten. The OpenRTB 2.5 location +// is expected to be empty. +func moveSupplyChainFrom26To25(r *RequestWrapper) error { + if r.Source == nil || r.Source.SChain == nil { + return nil + } + + // read and clear 2.6 location + schain26 := r.Source.SChain + r.Source.SChain = nil + + // move to 2.5 location + sourceExt, err := r.GetSourceExt() + if err != nil { + return err + } + sourceExt.SetSChain(schain26) + + return nil +} + +// moveGDPRFrom26To25 modifies the request to move the OpenRTB 2.6 GDPR signal +// field (req.regs.gdpr) to the OpenRTB 2.5 location (req.regs.ext.gdpr). If the +// OpenRTB 2.5 location is already present it may be overwritten. The OpenRTB 2.5 +// location is expected to be empty. +func moveGDPRFrom26To25(r *RequestWrapper) error { + if r.Regs == nil || r.Regs.GDPR == nil { + return nil + } + + // read and clear 2.6 location + gdpr26 := r.Regs.GDPR + r.Regs.GDPR = nil + + // move to 2.5 location + regExt, err := r.GetRegExt() + if err != nil { + return err + } + regExt.SetGDPR(gdpr26) + + return nil +} + +// moveConsentFrom26To25 modifies the request to move the OpenRTB 2.6 GDPR consent +// field (req.user.consent) to the OpenRTB 2.5 location (req.user.ext.consent). If +// the OpenRTB 2.5 location is already present it may be overwritten. The OpenRTB 2.5 +// location is expected to be empty. +func moveConsentFrom26To25(r *RequestWrapper) error { + if r.User == nil || len(r.User.Consent) == 0 { + return nil + } + + // read and clear 2.6 location + consent26 := r.User.Consent + r.User.Consent = "" + + // move to 2.5 location + userExt, err := r.GetUserExt() + if err != nil { + return err + } + userExt.SetConsent(&consent26) + + return nil +} + +// moveUSPrivacyFrom26To25 modifies the request to move the OpenRTB 2.6 US Privacy (CCPA) +// consent string (req.regs.us_privacy) to the OpenRTB 2.5 location (req.regs.ext.us_privacy). +// If the OpenRTB 2.5 location is already present it may be overwritten. The OpenRTB 2.5 +// location is expected to be empty. +func moveUSPrivacyFrom26To25(r *RequestWrapper) error { + if r.Regs == nil || len(r.Regs.USPrivacy) == 0 { + return nil + } + + // read and clear 2.6 location + usprivacy26 := r.Regs.USPrivacy + r.Regs.USPrivacy = "" + + // move to 2.5 location + regExt, err := r.GetRegExt() + if err != nil { + return err + } + regExt.SetUSPrivacy(usprivacy26) + + return nil +} + +// moveEIDFrom26To25 modifies the request to move the OpenRTB 2.6 external identifiers +// (req.user.eids) to the OpenRTB 2.5 location (req.user.ext.eids). If the OpenRTB 2.5 +// location is already present it may be overwritten. The OpenRTB 2.5 location is +// expected to be empty. +func moveEIDFrom26To25(r *RequestWrapper) error { + if r.User == nil || r.User.EIDs == nil { + return nil + } + + // read and clear 2.6 location + eid26 := r.User.EIDs + r.User.EIDs = nil + + // move to 2.5 location + userExt, err := r.GetUserExt() + if err != nil { + return err + } + userExt.SetEid(&eid26) + + return nil +} + +// moveRewardedFrom26ToPrebidExt modifies the impression to move the OpenRTB 2.6 rewarded +// signal (imp[].rwdd) to the OpenRTB 2.x Prebid specific location (imp[].ext.prebid.is_rewarded_inventory). +// If the Prebid specific location is already present, it may be overwritten. The Prebid specific +// location is expected to be empty. +func moveRewardedFrom26ToPrebidExt(i *ImpWrapper) error { + if i.Rwdd == 0 { + return nil + } + + // read and clear 2.6 location + rwdd26 := i.Rwdd + i.Rwdd = 0 + + // move to Prebid specific location + impExt, err := i.GetImpExt() + if err != nil { + return err + } + impExtPrebid := impExt.GetOrCreatePrebid() + impExtPrebid.IsRewardedInventory = &rwdd26 + impExt.SetPrebid(impExtPrebid) + + return nil +} diff --git a/openrtb_ext/convert_down_test.go b/openrtb_ext/convert_down_test.go new file mode 100644 index 00000000000..44981546d64 --- /dev/null +++ b/openrtb_ext/convert_down_test.go @@ -0,0 +1,408 @@ +package openrtb_ext + +import ( + "encoding/json" + "testing" + + "github.com/prebid/openrtb/v17/openrtb2" + "github.com/stretchr/testify/assert" +) + +func TestConvertDownTo25(t *testing.T) { + testCases := []struct { + description string + givenRequest openrtb2.BidRequest + expectedRequest openrtb2.BidRequest + expectedErr string + }{ + { + description: "2.6 -> 2.5", + givenRequest: openrtb2.BidRequest{ + ID: "anyID", + Imp: []openrtb2.Imp{{Rwdd: 1}}, + Source: &openrtb2.Source{SChain: &openrtb2.SupplyChain{Complete: 1, Nodes: []openrtb2.SupplyChainNode{}, Ver: "2"}}, + Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(1), USPrivacy: "3"}, + User: &openrtb2.User{Consent: "1", EIDs: []openrtb2.EID{{Source: "42"}}}, + }, + expectedRequest: openrtb2.BidRequest{ + ID: "anyID", + Imp: []openrtb2.Imp{{Ext: json.RawMessage(`{"prebid":{"is_rewarded_inventory":1}}`)}}, + Source: &openrtb2.Source{Ext: json.RawMessage(`{"schain":{"complete":1,"nodes":[],"ver":"2"}}`)}, + Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr":1,"us_privacy":"3"}`)}, + User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"1","eids":[{"source":"42"}]}`)}, + }, + }, + { + description: "2.6 -> 2.5 + Other Ext Fields", + givenRequest: openrtb2.BidRequest{ + ID: "anyID", + Imp: []openrtb2.Imp{{Rwdd: 1, Ext: json.RawMessage(`{"other":"otherImp"}`)}}, + Ext: json.RawMessage(`{"other":"otherExt"}`), + Source: &openrtb2.Source{SChain: &openrtb2.SupplyChain{Complete: 1, Nodes: []openrtb2.SupplyChainNode{}, Ver: "2"}, Ext: json.RawMessage(`{"other":"otherSource"}`)}, + Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(1), USPrivacy: "3", Ext: json.RawMessage(`{"other":"otherRegs"}`)}, + User: &openrtb2.User{Consent: "1", EIDs: []openrtb2.EID{{Source: "42"}}, Ext: json.RawMessage(`{"other":"otherUser"}`)}, + }, + expectedRequest: openrtb2.BidRequest{ + ID: "anyID", + Imp: []openrtb2.Imp{{Ext: json.RawMessage(`{"other":"otherImp","prebid":{"is_rewarded_inventory":1}}`)}}, + Ext: json.RawMessage(`{"other":"otherExt"}`), + Source: &openrtb2.Source{Ext: json.RawMessage(`{"other":"otherSource","schain":{"complete":1,"nodes":[],"ver":"2"}}`)}, + Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr":1,"other":"otherRegs","us_privacy":"3"}`)}, + User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"1","eids":[{"source":"42"}],"other":"otherUser"}`)}, + }, + }, + { + description: "Malformed - SChain", + givenRequest: openrtb2.BidRequest{ + ID: "anyID", + Source: &openrtb2.Source{SChain: &openrtb2.SupplyChain{Complete: 1, Nodes: []openrtb2.SupplyChainNode{}, Ver: "2"}, Ext: json.RawMessage(`malformed`)}, + }, + expectedErr: "invalid character 'm' looking for beginning of value", + }, + { + description: "Malformed - GDPR", + givenRequest: openrtb2.BidRequest{ + ID: "anyID", + Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(1), Ext: json.RawMessage(`malformed`)}, + }, + expectedErr: "invalid character 'm' looking for beginning of value", + }, + { + description: "Malformed - Consent", + givenRequest: openrtb2.BidRequest{ + ID: "anyID", + User: &openrtb2.User{Consent: "1", Ext: json.RawMessage(`malformed`)}, + }, + expectedErr: "invalid character 'm' looking for beginning of value", + }, + { + description: "Malformed - USPrivacy", + givenRequest: openrtb2.BidRequest{ + ID: "anyID", + Regs: &openrtb2.Regs{USPrivacy: "3", Ext: json.RawMessage(`malformed`)}, + }, + expectedErr: "invalid character 'm' looking for beginning of value", + }, + { + description: "Malformed - EID", + givenRequest: openrtb2.BidRequest{ + ID: "anyID", + User: &openrtb2.User{EIDs: []openrtb2.EID{{Source: "42"}}, Ext: json.RawMessage(`malformed`)}, + }, + expectedErr: "invalid character 'm' looking for beginning of value", + }, + { + description: "Malformed - Imp", + givenRequest: openrtb2.BidRequest{ + ID: "anyID", + Imp: []openrtb2.Imp{{Rwdd: 1, Ext: json.RawMessage(`malformed`)}}, + }, + expectedErr: "invalid character 'm' looking for beginning of value", + }, + } + + for _, test := range testCases { + w := &RequestWrapper{BidRequest: &test.givenRequest} + err := ConvertDownTo25(w) + if len(test.expectedErr) > 0 { + assert.EqualError(t, err, test.expectedErr, test.description) + } else { + assert.NoError(t, w.RebuildRequest(), test.description) + assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description) + } + } +} + +func TestMoveSupplyChainFrom26To25(t *testing.T) { + var ( + schain1 = &openrtb2.SupplyChain{Complete: 1, Nodes: []openrtb2.SupplyChainNode{}, Ver: "1"} + schain1Json = json.RawMessage(`{"schain":{"complete":1,"nodes":[],"ver":"1"}}`) + schain2Json = json.RawMessage(`{"schain":{"complete":1,"nodes":[],"ver":"2"}}`) + ) + + testCases := []struct { + description string + givenRequest openrtb2.BidRequest + expectedRequest openrtb2.BidRequest + expectedErr string + }{ + { + description: "Not Present - Source", + givenRequest: openrtb2.BidRequest{}, + expectedRequest: openrtb2.BidRequest{}, + }, + { + description: "Not Present - Source Schain", + givenRequest: openrtb2.BidRequest{Source: &openrtb2.Source{}}, + expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{}}, + }, + { + description: "2.6 Migrated To 2.5", + givenRequest: openrtb2.BidRequest{Source: &openrtb2.Source{SChain: schain1}}, + expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: schain1Json}}, + }, + { + description: "2.5 Overwritten", + givenRequest: openrtb2.BidRequest{Source: &openrtb2.Source{SChain: schain1, Ext: schain2Json}}, + expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: schain1Json}}, + }, + { + description: "Malformed", + givenRequest: openrtb2.BidRequest{Source: &openrtb2.Source{SChain: schain1, Ext: json.RawMessage(`malformed`)}}, + expectedErr: "invalid character 'm' looking for beginning of value", + }, + } + + for _, test := range testCases { + w := &RequestWrapper{BidRequest: &test.givenRequest} + err := moveSupplyChainFrom26To25(w) + + if len(test.expectedErr) > 0 { + assert.EqualError(t, err, test.expectedErr, test.description) + } else { + assert.NoError(t, w.RebuildRequest(), test.description) + assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description) + } + } +} + +func TestMoveGDPRFrom26To25(t *testing.T) { + testCases := []struct { + description string + givenRequest openrtb2.BidRequest + expectedRequest openrtb2.BidRequest + expectedErr string + }{ + { + description: "Not Present - Regs", + givenRequest: openrtb2.BidRequest{}, + expectedRequest: openrtb2.BidRequest{}, + }, + { + description: "Not Present - Regs GDPR", + givenRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{}}, + expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{}}, + }, + { + description: "2.6 Migrated To 2.5", + givenRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(0)}}, + expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr":0}`)}}, + }, + { + description: "2.5 Overwritten", + givenRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(0), Ext: json.RawMessage(`{"gdpr":1}`)}}, + expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr":0}`)}}, + }, + { + description: "Malformed", + givenRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(0), Ext: json.RawMessage(`malformed`)}}, + expectedErr: "invalid character 'm' looking for beginning of value", + }, + } + + for _, test := range testCases { + w := &RequestWrapper{BidRequest: &test.givenRequest} + err := moveGDPRFrom26To25(w) + + if len(test.expectedErr) > 0 { + assert.EqualError(t, err, test.expectedErr, test.description) + } else { + assert.NoError(t, w.RebuildRequest(), test.description) + assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description) + } + } +} + +func TestMoveConsentFrom26To25(t *testing.T) { + testCases := []struct { + description string + givenRequest openrtb2.BidRequest + expectedRequest openrtb2.BidRequest + expectedErr string + }{ + { + description: "Not Present - User", + givenRequest: openrtb2.BidRequest{}, + expectedRequest: openrtb2.BidRequest{}, + }, + { + description: "Not Present - User Consent", + givenRequest: openrtb2.BidRequest{User: &openrtb2.User{}}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{}}, + }, + { + description: "2.6 Migrated To 2.5", + givenRequest: openrtb2.BidRequest{User: &openrtb2.User{Consent: "1"}}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"1"}`)}}, + }, + { + description: "2.5 Overwritten", + givenRequest: openrtb2.BidRequest{User: &openrtb2.User{Consent: "1", Ext: json.RawMessage(`{"consent":"2"}`)}}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"1"}`)}}, + }, + { + description: "Malformed", + givenRequest: openrtb2.BidRequest{User: &openrtb2.User{Consent: "1", Ext: json.RawMessage(`malformed`)}}, + expectedErr: "invalid character 'm' looking for beginning of value", + }, + } + + for _, test := range testCases { + w := &RequestWrapper{BidRequest: &test.givenRequest} + err := moveConsentFrom26To25(w) + + if len(test.expectedErr) > 0 { + assert.EqualError(t, err, test.expectedErr, test.description) + } else { + assert.NoError(t, w.RebuildRequest(), test.description) + assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description) + } + } +} + +func TestMoveUSPrivacyFrom26To25(t *testing.T) { + testCases := []struct { + description string + givenRequest openrtb2.BidRequest + expectedRequest openrtb2.BidRequest + expectedErr string + }{ + { + description: "Not Present - Regs", + givenRequest: openrtb2.BidRequest{}, + expectedRequest: openrtb2.BidRequest{}, + }, + { + description: "Not Present - Regs USPrivacy", + givenRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{}}, + expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{}}, + }, + { + description: "2.6 Migrated To 2.5", + givenRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{USPrivacy: "1"}}, + expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"1"}`)}}, + }, + { + description: "2.5 Overwritten", + givenRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{USPrivacy: "1", Ext: json.RawMessage(`{"us_privacy":"2"}`)}}, + expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"1"}`)}}, + }, + { + description: "Malformed", + givenRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{USPrivacy: "1", Ext: json.RawMessage(`malformed`)}}, + expectedErr: "invalid character 'm' looking for beginning of value", + }, + } + + for _, test := range testCases { + w := &RequestWrapper{BidRequest: &test.givenRequest} + err := moveUSPrivacyFrom26To25(w) + + if len(test.expectedErr) > 0 { + assert.EqualError(t, err, test.expectedErr, test.description) + } else { + assert.NoError(t, w.RebuildRequest(), test.description) + assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description) + } + } +} + +func TestMoveEIDFrom26To25(t *testing.T) { + var ( + eid1 = []openrtb2.EID{{Source: "1"}} + eid1Json = json.RawMessage(`{"eids":[{"source":"1"}]}`) + eid2Json = json.RawMessage(`{"eids":[{"source":"2"}]}`) + ) + + testCases := []struct { + description string + givenRequest openrtb2.BidRequest + expectedRequest openrtb2.BidRequest + expectedErr string + }{ + { + description: "Not Present - User", + givenRequest: openrtb2.BidRequest{}, + expectedRequest: openrtb2.BidRequest{}, + }, + { + description: "Not Present - User EIDs", + givenRequest: openrtb2.BidRequest{User: &openrtb2.User{}}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{}}, + }, + { + description: "2.6 Migrated To 2.5", + givenRequest: openrtb2.BidRequest{User: &openrtb2.User{EIDs: eid1}}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Ext: eid1Json}}, + }, + { + description: "2.6 Migrated To 2.5 - Empty", + givenRequest: openrtb2.BidRequest{User: &openrtb2.User{EIDs: []openrtb2.EID{}}}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{}}, + }, + { + description: "2.5 Overwritten", + givenRequest: openrtb2.BidRequest{User: &openrtb2.User{EIDs: eid1, Ext: eid2Json}}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Ext: eid1Json}}, + }, + { + description: "Malformed", + givenRequest: openrtb2.BidRequest{User: &openrtb2.User{EIDs: eid1, Ext: json.RawMessage(`malformed`)}}, + expectedErr: "invalid character 'm' looking for beginning of value", + }, + } + + for _, test := range testCases { + w := &RequestWrapper{BidRequest: &test.givenRequest} + err := moveEIDFrom26To25(w) + + if len(test.expectedErr) > 0 { + assert.EqualError(t, err, test.expectedErr, test.description) + } else { + assert.NoError(t, w.RebuildRequest(), test.description) + assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description) + } + } +} + +func TestMoveRewardedFrom26ToPrebidExt(t *testing.T) { + testCases := []struct { + description string + givenImp openrtb2.Imp + expectedImp openrtb2.Imp + expectedErr string + }{ + { + description: "Not Present", + givenImp: openrtb2.Imp{}, + expectedImp: openrtb2.Imp{}, + }, + { + description: "2.6 Migrated To Prebid Ext", + givenImp: openrtb2.Imp{Rwdd: 1}, + expectedImp: openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{"is_rewarded_inventory":1}}`)}, + }, + { + description: "Prebid Ext Overwritten", + givenImp: openrtb2.Imp{Rwdd: 1, Ext: json.RawMessage(`{"prebid":{"is_rewarded_inventory":2}}`)}, + expectedImp: openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{"is_rewarded_inventory":1}}`)}, + }, + { + description: "Malformed", + givenImp: openrtb2.Imp{Rwdd: 1, Ext: json.RawMessage(`malformed`)}, + expectedErr: "invalid character 'm' looking for beginning of value", + }, + } + + for _, test := range testCases { + w := &ImpWrapper{Imp: &test.givenImp} + err := moveRewardedFrom26ToPrebidExt(w) + + if len(test.expectedErr) > 0 { + assert.EqualError(t, err, test.expectedErr, test.description) + } else { + assert.NoError(t, w.RebuildImp(), test.description) + assert.Equal(t, test.expectedImp, *w.Imp, test.description) + } + } +} diff --git a/openrtb_ext/convert_up.go b/openrtb_ext/convert_up.go new file mode 100644 index 00000000000..b4861cb7c25 --- /dev/null +++ b/openrtb_ext/convert_up.go @@ -0,0 +1,181 @@ +package openrtb_ext + +import ( + "fmt" + + "github.com/prebid/openrtb/v17/openrtb2" +) + +func ConvertUpTo26(r *RequestWrapper) error { + if err := convertUpEnsureExt(r); err != nil { + return err + } + + // schain + moveSupplyChainFrom24To25(r) + moveSupplyChainFrom25To26(r) + + // gdpr + moveGDPRFrom25To26(r) + moveConsentFrom25To26(r) + + // ccpa + moveUSPrivacyFrom25To26(r) + + // eid + moveEIDFrom25To26(r) + + // imp + for _, imp := range r.GetImp() { + moveRewardedFromPrebidExtTo26(imp) + } + + return nil +} + +// convertUpEnsureExt gets all extension objects required for migration to verify there +// are no access errors. +func convertUpEnsureExt(r *RequestWrapper) error { + if _, err := r.GetRequestExt(); err != nil { + return fmt.Errorf("req.ext is invalid: %v", err) + } + + if _, err := r.GetSourceExt(); err != nil { + return fmt.Errorf("req.source.ext is invalid: %v", err) + } + + if _, err := r.GetRegExt(); err != nil { + return fmt.Errorf("req.regs.ext is invalid: %v", err) + } + + if _, err := r.GetUserExt(); err != nil { + return fmt.Errorf("req.user.ext is invalid: %v", err) + } + + for i, imp := range r.GetImp() { + if _, err := imp.GetImpExt(); err != nil { + return fmt.Errorf("imp[%v].imp.ext is invalid: %v", i, err) + } + } + + return nil +} + +// moveSupplyChainFrom24To25 modifies the request to move the OpenRTB 2.4 supply chain +// object (req.ext.schain) to the OpenRTB 2.5 location (req.source.ext.schain). If the +// OpenRTB 2.5 location is already present the OpenRTB 2.4 supply chain object is dropped. +func moveSupplyChainFrom24To25(r *RequestWrapper) { + // read and clear 2.4 location + reqExt, _ := r.GetRequestExt() + schain24 := reqExt.GetSChain() + reqExt.SetSChain(nil) + + // move to 2.5 location if not already present + sourceExt, _ := r.GetSourceExt() + if sourceExt.GetSChain() == nil { + sourceExt.SetSChain(schain24) + } +} + +// moveSupplyChainFrom25To26 modifies the request to move the OpenRTB 2.5 supply chain +// object (req.source.ext.schain) to the OpenRTB 2.6 location (req.source.schain). If the +// OpenRTB 2.6 location is already present the OpenRTB 2.5 supply chain object is dropped. +func moveSupplyChainFrom25To26(r *RequestWrapper) { + // read and clear 2.5 location + sourceExt, _ := r.GetSourceExt() + schain25 := sourceExt.GetSChain() + sourceExt.SetSChain(nil) + + // move to 2.6 location if not already present + if schain25 != nil { + // source may be nil if moved indirectly from an OpenRTB 2.4 location, since the ext + // is not defined on the source object. + if r.Source == nil { + r.Source = &openrtb2.Source{} + } + + if r.Source.SChain == nil { + r.Source.SChain = schain25 + } + } +} + +// moveGDPRFrom25To26 modifies the request to move the OpenRTB 2.5 GDPR signal field +// (req.regs.ext.gdpr) to the OpenRTB 2.6 location (req.regs.gdpr). If the OpenRTB 2.6 +// location is already present the OpenRTB 2.5 GDPR signal is dropped. +func moveGDPRFrom25To26(r *RequestWrapper) { + // read and clear 2.5 location + regsExt, _ := r.GetRegExt() + gdpr25 := regsExt.GetGDPR() + regsExt.SetGDPR(nil) + + // move to 2.6 location + if gdpr25 != nil && r.Regs.GDPR == nil { + r.Regs.GDPR = gdpr25 + } +} + +// moveConsentFrom25To26 modifies the request to move the OpenRTB 2.5 GDPR consent field +// (req.user.ext.consent) to the OpenRTB 2.6 location (req.user.consent). If the OpenRTB 2.6 +// location is already present the OpenRTB 2.5 GDPR consent is dropped. +func moveConsentFrom25To26(r *RequestWrapper) { + // read and clear 2.5 location + userExt, _ := r.GetUserExt() + consent25 := userExt.GetConsent() + userExt.SetConsent(nil) + + // move to 2.6 location + if consent25 != nil && r.User.Consent == "" { + r.User.Consent = *consent25 + } +} + +// moveUSPrivacyFrom25To26 modifies the request to move the OpenRTB 2.5 US Privacy (CCPA) +// consent string (req.regs.ext.usprivacy) to the OpenRTB 2.6 location (req.regs.usprivacy). +// If the OpenRTB 2.6 location is already present the OpenRTB 2.5 consent string is dropped. +func moveUSPrivacyFrom25To26(r *RequestWrapper) { + // read and clear 2.5 location + regsExt, _ := r.GetRegExt() + usPrivacy25 := regsExt.GetUSPrivacy() + regsExt.SetUSPrivacy("") + + // move to 2.6 location + if usPrivacy25 != "" && r.Regs.USPrivacy == "" { + r.Regs.USPrivacy = usPrivacy25 + } +} + +// moveEIDFrom25To26 modifies the request to move the OpenRTB 2.5 external identifiers +// (req.user.ext.eids) to the OpenRTB 2.6 location (req.user.eids). If the OpenRTB 2.6 +// location is already present the OpenRTB 2.5 external identifiers is dropped. +func moveEIDFrom25To26(r *RequestWrapper) { + // read and clear 2.5 location + userExt, _ := r.GetUserExt() + eid25 := userExt.GetEid() + userExt.SetEid(nil) + + // move to 2.6 location + if eid25 != nil && r.User.EIDs == nil { + r.User.EIDs = *eid25 + } +} + +// moveRewardedFromPrebidExtTo26 modifies the impression to move the Prebid specific +// rewarded video signal (imp[].ext.prebid.is_rewarded_inventory) to the OpenRTB 2.6 +// location (imp[].rwdd). If the OpenRTB 2.6 location is already present the Prebid +// specific extension is dropped. +func moveRewardedFromPrebidExtTo26(i *ImpWrapper) { + // read and clear prebid ext + impExt, _ := i.GetImpExt() + rwddPrebidExt := (*int8)(nil) + if p := impExt.GetPrebid(); p != nil { + rwddPrebidExt = p.IsRewardedInventory + p.IsRewardedInventory = nil + impExt.SetPrebid(p) + } + + // move to 2.6 location + if rwddPrebidExt != nil && i.Rwdd == 0 { + i.Rwdd = *rwddPrebidExt + } +} diff --git a/openrtb_ext/convert_up_test.go b/openrtb_ext/convert_up_test.go new file mode 100644 index 00000000000..6764b678601 --- /dev/null +++ b/openrtb_ext/convert_up_test.go @@ -0,0 +1,450 @@ +package openrtb_ext + +import ( + "encoding/json" + "testing" + + "github.com/prebid/openrtb/v17/openrtb2" + "github.com/stretchr/testify/assert" +) + +func TestConvertUpTo26(t *testing.T) { + testCases := []struct { + description string + givenRequest openrtb2.BidRequest + expectedRequest openrtb2.BidRequest + expectedErr string + }{ + { + description: "Malformed", + givenRequest: openrtb2.BidRequest{ + Ext: json.RawMessage(`malformed`), + }, + expectedErr: "req.ext is invalid: invalid character 'm' looking for beginning of value", + }, + { + description: "2.4 -> 2.6", + givenRequest: openrtb2.BidRequest{ + ID: "anyID", + Imp: []openrtb2.Imp{{Ext: json.RawMessage(`{"prebid":{"is_rewarded_inventory":1}}`)}}, + Ext: json.RawMessage(`{"schain":{"complete":1,"nodes":[],"ver":"2"}}`), + Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr":1,"us_privacy":"3"}`)}, + User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"1","eids":[{"source":"42"}]}`)}, + }, + expectedRequest: openrtb2.BidRequest{ + ID: "anyID", + Imp: []openrtb2.Imp{{Rwdd: 1}}, + Source: &openrtb2.Source{SChain: &openrtb2.SupplyChain{Complete: 1, Nodes: []openrtb2.SupplyChainNode{}, Ver: "2"}}, + Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(1), USPrivacy: "3"}, + User: &openrtb2.User{Consent: "1", EIDs: []openrtb2.EID{{Source: "42"}}}, + }, + }, + { + description: "2.4 -> 2.6 + Other Ext Fields", + givenRequest: openrtb2.BidRequest{ + ID: "anyID", + Imp: []openrtb2.Imp{{Ext: json.RawMessage(`{"prebid":{"is_rewarded_inventory":1},"other":"otherImp"}`)}}, + Ext: json.RawMessage(`{"schain":{"complete":1,"nodes":[],"ver":"2"},"other":"otherExt"}`), + Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr":1,"other":"otherRegs","us_privacy":"3"}`)}, + User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"1","eids":[{"source":"42"}],"other":"otherUser"}`)}, + }, + expectedRequest: openrtb2.BidRequest{ + ID: "anyID", + Imp: []openrtb2.Imp{{Rwdd: 1, Ext: json.RawMessage(`{"other":"otherImp"}`)}}, + Ext: json.RawMessage(`{"other":"otherExt"}`), + Source: &openrtb2.Source{SChain: &openrtb2.SupplyChain{Complete: 1, Nodes: []openrtb2.SupplyChainNode{}, Ver: "2"}}, + Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(1), USPrivacy: "3", Ext: json.RawMessage(`{"other":"otherRegs"}`)}, + User: &openrtb2.User{Consent: "1", EIDs: []openrtb2.EID{{Source: "42"}}, Ext: json.RawMessage(`{"other":"otherUser"}`)}, + }, + }, + { + description: "2.5 -> 2.6", + givenRequest: openrtb2.BidRequest{ + ID: "anyID", + Imp: []openrtb2.Imp{{Ext: json.RawMessage(`{"prebid":{"is_rewarded_inventory":1}}`)}}, + Source: &openrtb2.Source{Ext: json.RawMessage(`{"schain":{"complete":1,"nodes":[],"ver":"2"}}`)}, + Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr":1,"us_privacy":"3"}`)}, + User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"1","eids":[{"source":"42"}]}`)}, + }, + expectedRequest: openrtb2.BidRequest{ + ID: "anyID", + Imp: []openrtb2.Imp{{Rwdd: 1}}, + Source: &openrtb2.Source{SChain: &openrtb2.SupplyChain{Complete: 1, Nodes: []openrtb2.SupplyChainNode{}, Ver: "2"}}, + Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(1), USPrivacy: "3"}, + User: &openrtb2.User{Consent: "1", EIDs: []openrtb2.EID{{Source: "42"}}}, + }, + }, + { + description: "2.5 -> 2.6 + Other Ext Fields", + givenRequest: openrtb2.BidRequest{ + ID: "anyID", + Imp: []openrtb2.Imp{{Ext: json.RawMessage(`{"prebid":{"is_rewarded_inventory":1},"other":"otherImp"}`)}}, + Ext: json.RawMessage(`{"other":"otherExt"}`), + Source: &openrtb2.Source{Ext: json.RawMessage(`{"schain":{"complete":1,"nodes":[],"ver":"2"},"other":"otherSource"}`)}, + Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr":1,"us_privacy":"3","other":"otherRegs"}`)}, + User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"1","eids":[{"source":"42"}],"other":"otherUser"}`)}, + }, + expectedRequest: openrtb2.BidRequest{ + ID: "anyID", + Imp: []openrtb2.Imp{{Rwdd: 1, Ext: json.RawMessage(`{"other":"otherImp"}`)}}, + Ext: json.RawMessage(`{"other":"otherExt"}`), + Source: &openrtb2.Source{SChain: &openrtb2.SupplyChain{Complete: 1, Nodes: []openrtb2.SupplyChainNode{}, Ver: "2"}, Ext: json.RawMessage(`{"other":"otherSource"}`)}, + Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(1), USPrivacy: "3", Ext: json.RawMessage(`{"other":"otherRegs"}`)}, + User: &openrtb2.User{Consent: "1", EIDs: []openrtb2.EID{{Source: "42"}}, Ext: json.RawMessage(`{"other":"otherUser"}`)}, + }, + }, + } + + for _, test := range testCases { + w := &RequestWrapper{BidRequest: &test.givenRequest} + err := ConvertUpTo26(w) + if len(test.expectedErr) > 0 { + assert.EqualError(t, err, test.expectedErr, test.description) + } else { + assert.NoError(t, w.RebuildRequest(), test.description) + assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description) + } + } +} + +func TestConvertUpEnsureExt(t *testing.T) { + testCases := []struct { + description string + givenRequest openrtb2.BidRequest + expectedErr string + }{ + { + description: "Empty", + givenRequest: openrtb2.BidRequest{}, + }, + { + description: "Ext", + givenRequest: openrtb2.BidRequest{Ext: json.RawMessage("malformed")}, + expectedErr: "req.ext is invalid: invalid character 'm' looking for beginning of value", + }, + { + description: "Source.Ext", + givenRequest: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: json.RawMessage("malformed")}}, + expectedErr: "req.source.ext is invalid: invalid character 'm' looking for beginning of value", + }, + { + description: "Regs.Ext", + givenRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage("malformed")}}, + expectedErr: "req.regs.ext is invalid: invalid character 'm' looking for beginning of value", + }, + { + description: "User.Ext", + givenRequest: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage("malformed")}}, + expectedErr: "req.user.ext is invalid: invalid character 'm' looking for beginning of value", + }, + { + description: "Imp.Ext", + givenRequest: openrtb2.BidRequest{Imp: []openrtb2.Imp{{Ext: json.RawMessage("malformed")}}}, + expectedErr: "imp[0].imp.ext is invalid: invalid character 'm' looking for beginning of value", + }, + } + + for _, test := range testCases { + w := &RequestWrapper{BidRequest: &test.givenRequest} + err := convertUpEnsureExt(w) + if len(test.expectedErr) > 0 { + assert.EqualError(t, err, test.expectedErr, test.description) + } else { + assert.NoError(t, err, test.description) + } + } +} + +func TestMoveSupplyChainFrom24To25(t *testing.T) { + var ( + schain1 = json.RawMessage(`{"schain":{"complete":1,"nodes":[],"ver":"1"}}`) + schain2 = json.RawMessage(`{"schain":{"complete":1,"nodes":[],"ver":"2"}}`) + ) + + testCases := []struct { + description string + givenRequest openrtb2.BidRequest + expectedRequest openrtb2.BidRequest + }{ + { + description: "Not Present", + givenRequest: openrtb2.BidRequest{}, + expectedRequest: openrtb2.BidRequest{}, + }, + { + description: "2.4 Migrated To 2.5 - Source Doesn't Exist", + givenRequest: openrtb2.BidRequest{Ext: schain1}, + expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: schain1}}, + }, + { + description: "2.4 Migrated To 2.5 - Source Exists", + givenRequest: openrtb2.BidRequest{Source: &openrtb2.Source{}, Ext: schain1}, + expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: schain1}}, + }, + { + description: "2.4 Dropped", + givenRequest: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: schain1}, Ext: schain2}, + expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: schain1}}, + }, + { + description: "2.5 Left Alone", + givenRequest: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: schain1}}, + expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: schain1}}, + }, + } + + for _, test := range testCases { + w := &RequestWrapper{BidRequest: &test.givenRequest} + moveSupplyChainFrom24To25(w) + assert.NoError(t, w.RebuildRequest(), test.description) + assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description) + } +} + +func TestConvertSupplyChainFrom25To26(t *testing.T) { + var ( + schain1 = &openrtb2.SupplyChain{Complete: 1, Nodes: []openrtb2.SupplyChainNode{}, Ver: "1"} + schain1Json = json.RawMessage(`{"schain":{"complete":1,"nodes":[],"ver":"1"}}`) + schain2Json = json.RawMessage(`{"schain":{"complete":1,"nodes":[],"ver":"2"}}`) + ) + + testCases := []struct { + description string + givenRequest openrtb2.BidRequest + expectedRequest openrtb2.BidRequest + }{ + { + description: "Not Present", + givenRequest: openrtb2.BidRequest{}, + expectedRequest: openrtb2.BidRequest{}, + }, + { + description: "2.5 Migrated To 2.6", + givenRequest: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: schain1Json}}, + expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{SChain: schain1}}, + }, + { + description: "2.5 Dropped", + givenRequest: openrtb2.BidRequest{Source: &openrtb2.Source{SChain: schain1, Ext: schain2Json}}, + expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{SChain: schain1}}, + }, + { + description: "2.6 Left Alone", + givenRequest: openrtb2.BidRequest{Source: &openrtb2.Source{SChain: schain1}}, + expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{SChain: schain1}}, + }, + } + + for _, test := range testCases { + w := &RequestWrapper{BidRequest: &test.givenRequest} + moveSupplyChainFrom25To26(w) + assert.NoError(t, w.RebuildRequest(), test.description) + assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description) + } +} + +func TestMoveGDPRFrom25To26(t *testing.T) { + testCases := []struct { + description string + givenRequest openrtb2.BidRequest + expectedRequest openrtb2.BidRequest + }{ + { + description: "Not Present", + givenRequest: openrtb2.BidRequest{}, + expectedRequest: openrtb2.BidRequest{}, + }, + { + description: "2.5 Migrated To 2.6", + givenRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr":0}`)}}, + expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(0)}}, + }, + { + description: "2.5 Dropped", + givenRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(0), Ext: json.RawMessage(`{"gdpr":1}`)}}, + expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(0)}}, + }, + { + description: "2.6 Left Alone", + givenRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(0)}}, + expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(0)}}, + }, + } + + for _, test := range testCases { + w := &RequestWrapper{BidRequest: &test.givenRequest} + moveGDPRFrom25To26(w) + assert.NoError(t, w.RebuildRequest(), test.description) + assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description) + } +} + +func TestMoveConsentFrom25To26(t *testing.T) { + testCases := []struct { + description string + givenRequest openrtb2.BidRequest + expectedRequest openrtb2.BidRequest + }{ + { + description: "Not Present", + givenRequest: openrtb2.BidRequest{}, + expectedRequest: openrtb2.BidRequest{}, + }, + { + description: "2.5 Migrated To 2.6", + givenRequest: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"1"}`)}}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Consent: "1"}}, + }, + { + description: "2.5 Dropped", + givenRequest: openrtb2.BidRequest{User: &openrtb2.User{Consent: "1", Ext: json.RawMessage(`{"consent":2}`)}}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Consent: "1"}}, + }, + { + description: "2.6 Left Alone", + givenRequest: openrtb2.BidRequest{User: &openrtb2.User{Consent: "1"}}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Consent: "1"}}, + }, + } + + for _, test := range testCases { + w := &RequestWrapper{BidRequest: &test.givenRequest} + moveConsentFrom25To26(w) + assert.NoError(t, w.RebuildRequest(), test.description) + assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description) + } +} + +func TestMoveUSPrivacyFrom25To26(t *testing.T) { + testCases := []struct { + description string + givenRequest openrtb2.BidRequest + expectedRequest openrtb2.BidRequest + }{ + { + description: "Not Present", + givenRequest: openrtb2.BidRequest{}, + expectedRequest: openrtb2.BidRequest{}, + }, + { + description: "2.5 Migrated To 2.6", + givenRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"1"}`)}}, + expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{USPrivacy: "1"}}, + }, + { + description: "2.5 Dropped", + givenRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{USPrivacy: "1", Ext: json.RawMessage(`{"us_privacy":"2"}`)}}, + expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{USPrivacy: "1"}}, + }, + { + description: "2.6 Left Alone", + givenRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{USPrivacy: "1"}}, + expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{USPrivacy: "1"}}, + }, + } + + for _, test := range testCases { + w := &RequestWrapper{BidRequest: &test.givenRequest} + moveUSPrivacyFrom25To26(w) + assert.NoError(t, w.RebuildRequest(), test.description) + assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description) + } +} + +func TestMoveEIDFrom25To26(t *testing.T) { + var ( + eid1 = []openrtb2.EID{{Source: "1"}} + eid1Json = json.RawMessage(`{"eids":[{"source":"1"}]}`) + eid2Json = json.RawMessage(`{"eids":[{"source":"2"}]}`) + ) + + testCases := []struct { + description string + givenRequest openrtb2.BidRequest + expectedRequest openrtb2.BidRequest + }{ + { + description: "Not Present", + givenRequest: openrtb2.BidRequest{}, + expectedRequest: openrtb2.BidRequest{}, + }, + { + description: "2.5 Migrated To 2.6", + givenRequest: openrtb2.BidRequest{User: &openrtb2.User{Ext: eid1Json}}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{EIDs: eid1}}, + }, + { + description: "2.5 Dropped", + givenRequest: openrtb2.BidRequest{User: &openrtb2.User{EIDs: eid1, Ext: eid2Json}}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{EIDs: eid1}}, + }, + { + description: "2.6 Left Alone", + givenRequest: openrtb2.BidRequest{User: &openrtb2.User{EIDs: eid1}}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{EIDs: eid1}}, + }, + } + + for _, test := range testCases { + w := &RequestWrapper{BidRequest: &test.givenRequest} + moveEIDFrom25To26(w) + assert.NoError(t, w.RebuildRequest(), test.description) + assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description) + } +} + +func TestMoveRewardedFromPrebidExtTo26(t *testing.T) { + var ( + rwdd1Json = json.RawMessage(`{"prebid":{"is_rewarded_inventory":1}}`) + rwdd2Json = json.RawMessage(`{"prebid":{"is_rewarded_inventory":2}}`) + ) + + testCases := []struct { + description string + givenImp openrtb2.Imp + expectedImp openrtb2.Imp + }{ + { + description: "Not Present - No Ext", + givenImp: openrtb2.Imp{}, + expectedImp: openrtb2.Imp{}, + }, + { + description: "Not Present - Empty Ext", + givenImp: openrtb2.Imp{Ext: json.RawMessage(`{}`)}, + expectedImp: openrtb2.Imp{Ext: json.RawMessage(`{}`)}, + }, + { + description: "Not Present - Null Prebid Ext", + givenImp: openrtb2.Imp{Ext: json.RawMessage(`{"prebid":null}`)}, + expectedImp: openrtb2.Imp{}, // empty prebid object pruned by RebuildImp + }, + { + description: "Not Present - Empty Prebid Ext", + givenImp: openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{}}`)}, + expectedImp: openrtb2.Imp{}, // empty prebid object pruned by RebuildImp + }, + { + description: "Prebid Ext Migrated To 2.6", + givenImp: openrtb2.Imp{Ext: rwdd1Json}, + expectedImp: openrtb2.Imp{Rwdd: 1}, + }, + { + description: "2.5 Dropped", + givenImp: openrtb2.Imp{Rwdd: 1, Ext: rwdd2Json}, + expectedImp: openrtb2.Imp{Rwdd: 1}, + }, + { + description: "2.6 Left Alone", + givenImp: openrtb2.Imp{Rwdd: 1}, + expectedImp: openrtb2.Imp{Rwdd: 1}, + }, + } + + for _, test := range testCases { + w := &ImpWrapper{Imp: &test.givenImp} + moveRewardedFromPrebidExtTo26(w) + assert.NoError(t, w.RebuildImp(), test.description) + assert.Equal(t, test.expectedImp, *w.Imp, test.description) + } +} diff --git a/openrtb_ext/deal_tier.go b/openrtb_ext/deal_tier.go index 8aeedb81a5e..b8bb9803e0d 100644 --- a/openrtb_ext/deal_tier.go +++ b/openrtb_ext/deal_tier.go @@ -3,7 +3,7 @@ package openrtb_ext import ( "encoding/json" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" ) // DealTier defines the configuration of a deal tier. diff --git a/openrtb_ext/deal_tier_test.go b/openrtb_ext/deal_tier_test.go index 29d58d6c071..e71dc06aea0 100644 --- a/openrtb_ext/deal_tier_test.go +++ b/openrtb_ext/deal_tier_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/stretchr/testify/assert" ) diff --git a/openrtb_ext/device.go b/openrtb_ext/device.go index 1e8605562d2..8c5b36733b9 100644 --- a/openrtb_ext/device.go +++ b/openrtb_ext/device.go @@ -70,7 +70,7 @@ type ExtDevicePrebid struct { // ExtDeviceInt defines the contract for bidrequest.device.ext.prebid.interstitial type ExtDeviceInt struct { - MinWidthPerc int64 `json:"minwidtheperc"` + MinWidthPerc int64 `json:"minwidthperc"` MinHeightPerc int64 `json:"minheightperc"` } diff --git a/openrtb_ext/imp.go b/openrtb_ext/imp.go index 0afc3225f97..9e62a97f238 100644 --- a/openrtb_ext/imp.go +++ b/openrtb_ext/imp.go @@ -7,7 +7,7 @@ import ( // ExtImpPrebid defines the contract for bidrequest.imp[i].ext.prebid type ExtImpPrebid struct { // StoredRequest specifies which stored impression to use, if any. - StoredRequest *ExtStoredRequest `json:"storedrequest"` + StoredRequest *ExtStoredRequest `json:"storedrequest,omitempty"` // StoredResponse specifies which stored impression to use, if any. StoredAuctionResponse *ExtStoredAuctionResponse `json:"storedauctionresponse,omitempty"` @@ -16,12 +16,14 @@ type ExtImpPrebid struct { StoredBidResponse []ExtStoredBidResponse `json:"storedbidresponse,omitempty"` // IsRewardedInventory is a signal intended for video impressions. Must be 0 or 1. - IsRewardedInventory int8 `json:"is_rewarded_inventory"` + IsRewardedInventory *int8 `json:"is_rewarded_inventory,omitempty"` - // Bidder is the preferred approach for providing paramters to be interepreted by the bidder's adapter. - Bidder map[string]json.RawMessage `json:"bidder"` + // Bidder is the preferred approach for providing parameters to be interpreted by the bidder's adapter. + Bidder map[string]json.RawMessage `json:"bidder,omitempty"` Options *Options `json:"options,omitempty"` + + Passthrough json.RawMessage `json:"passthrough,omitempty"` } // ExtStoredRequest defines the contract for bidrequest.imp[i].ext.prebid.storedrequest @@ -36,8 +38,9 @@ type ExtStoredAuctionResponse struct { // ExtStoredBidResponse defines the contract for bidrequest.imp[i].ext.prebid.storedbidresponse type ExtStoredBidResponse struct { - ID string `json:"id"` - Bidder string `json:"bidder"` + ID string `json:"id"` + Bidder string `json:"bidder"` + ReplaceImpId *bool `json:"replaceimpid"` } type Options struct { diff --git a/openrtb_ext/imp_33across.go b/openrtb_ext/imp_33across.go index 23a874d95e7..3a46fab497f 100644 --- a/openrtb_ext/imp_33across.go +++ b/openrtb_ext/imp_33across.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImp33across defines the contract for bidrequest.imp[i].ext.33across +// ExtImp33across defines the contract for bidrequest.imp[i].ext.prebid.bidder.33across type ExtImp33across struct { SiteId string `json:"siteId"` ZoneId string `json:"zoneId,omitempty"` diff --git a/openrtb_ext/imp_adkernel.go b/openrtb_ext/imp_adkernel.go index d54149fe585..a286cfc4946 100644 --- a/openrtb_ext/imp_adkernel.go +++ b/openrtb_ext/imp_adkernel.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpAdkernel defines the contract for bidrequest.imp[i].ext.adkernel +// ExtImpAdkernel defines the contract for bidrequest.imp[i].ext.prebid.bidder.adkernel type ExtImpAdkernel struct { ZoneId int `json:"zoneId"` } diff --git a/openrtb_ext/imp_adkernelAdn.go b/openrtb_ext/imp_adkernelAdn.go index fee22318045..d2d0369ad19 100644 --- a/openrtb_ext/imp_adkernelAdn.go +++ b/openrtb_ext/imp_adkernelAdn.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpAdkernelAdn defines the contract for bidrequest.imp[i].ext.adkernelAdn +// ExtImpAdkernelAdn defines the contract for bidrequest.imp[i].ext.prebid.bidder.adkernelAdn type ExtImpAdkernelAdn struct { PublisherID int `json:"pubId"` } diff --git a/openrtb_ext/imp_adnuntius.go b/openrtb_ext/imp_adnuntius.go index bde30f0518f..b1e41e806b6 100644 --- a/openrtb_ext/imp_adnuntius.go +++ b/openrtb_ext/imp_adnuntius.go @@ -1,6 +1,7 @@ package openrtb_ext type ImpExtAdnunitus struct { - Auid string `json:"auId"` - Network string `json:"network"` + Auid string `json:"auId"` + Network string `json:"network"` + NoCookies bool `json:noCookies` } diff --git a/openrtb_ext/imp_adrino.go b/openrtb_ext/imp_adrino.go new file mode 100644 index 00000000000..7c5c53b2cc1 --- /dev/null +++ b/openrtb_ext/imp_adrino.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ExtImpAdrino struct { + Hash string `json:"hash"` +} diff --git a/openrtb_ext/imp_adtarget.go b/openrtb_ext/imp_adtarget.go index a8ac70a17d1..ab6cb5642c6 100644 --- a/openrtb_ext/imp_adtarget.go +++ b/openrtb_ext/imp_adtarget.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpAdtarget defines the contract for bidrequest.imp[i].ext.adtarget +// ExtImpAdtarget defines the contract for bidrequest.imp[i].ext.prebid.bidder.adtarget type ExtImpAdtarget struct { SourceId int `json:"aid"` PlacementId int `json:"placementId,omitempty"` diff --git a/openrtb_ext/imp_adtelligent.go b/openrtb_ext/imp_adtelligent.go index 43e65337f60..c2233209352 100644 --- a/openrtb_ext/imp_adtelligent.go +++ b/openrtb_ext/imp_adtelligent.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpAdtelligent defines the contract for bidrequest.imp[i].ext.adtelligent +// ExtImpAdtelligent defines the contract for bidrequest.imp[i].ext.prebid.bidder.adtelligent type ExtImpAdtelligent struct { SourceId int `json:"aid"` PlacementId int `json:"placementId,omitempty"` diff --git a/openrtb_ext/imp_adtrgtme.go b/openrtb_ext/imp_adtrgtme.go new file mode 100644 index 00000000000..8f4e575493d --- /dev/null +++ b/openrtb_ext/imp_adtrgtme.go @@ -0,0 +1,4 @@ +package openrtb_ext + +// ExtImpAdtrgtme defines the contract for bidrequest.imp[i].ext.prebid.bidder.adtrgtme +type ExtImpAdtrgtme struct{} diff --git a/openrtb_ext/imp_adyoulike.go b/openrtb_ext/imp_adyoulike.go index 67a94123734..a28227efdc6 100644 --- a/openrtb_ext/imp_adyoulike.go +++ b/openrtb_ext/imp_adyoulike.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpAdyoulike defines the contract for bidrequest.imp[i].ext.adyoulike +// ExtImpAdyoulike defines the contract for bidrequest.imp[i].ext.prebid.bidder.adyoulike type ExtImpAdyoulike struct { // placementId, only mandatory field PlacementId string `json:"placement"` diff --git a/openrtb_ext/imp_algorix.go b/openrtb_ext/imp_algorix.go index d395600c159..15475b550d4 100644 --- a/openrtb_ext/imp_algorix.go +++ b/openrtb_ext/imp_algorix.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpAlgoriX defines the contract for bidrequest.imp[i].ext.algorix +// ExtImpAlgoriX defines the contract for bidrequest.imp[i].ext.prebid.bidder.algorix type ExtImpAlgorix struct { Sid string `json:"sid"` Token string `json:"token"` diff --git a/openrtb_ext/imp_appnexus.go b/openrtb_ext/imp_appnexus.go index 83d762b3c19..a956a05f16c 100644 --- a/openrtb_ext/imp_appnexus.go +++ b/openrtb_ext/imp_appnexus.go @@ -2,7 +2,7 @@ package openrtb_ext import "encoding/json" -// ExtImpAppnexus defines the contract for bidrequest.imp[i].ext.appnexus +// ExtImpAppnexus defines the contract for bidrequest.imp[i].ext.prebid.bidder.appnexus type ExtImpAppnexus struct { LegacyPlacementId int `json:"placementId"` LegacyInvCode string `json:"invCode"` @@ -20,7 +20,7 @@ type ExtImpAppnexus struct { AdPodId bool `json:"generate_ad_pod_id"` } -// ExtImpAppnexusKeyVal defines the contract for bidrequest.imp[i].ext.appnexus.keywords[i] +// ExtImpAppnexusKeyVal defines the contract for bidrequest.imp[i].ext.prebid.bidder.appnexus.keywords[i] type ExtImpAppnexusKeyVal struct { Key string `json:"key,omitempty"` Values []string `json:"value,omitempty"` diff --git a/openrtb_ext/imp_appush.go b/openrtb_ext/imp_appush.go new file mode 100644 index 00000000000..6a38360379c --- /dev/null +++ b/openrtb_ext/imp_appush.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ImpExtAppush struct { + PlacementID string `json:"placementId"` + EndpointID string `json:"endpointId"` +} diff --git a/openrtb_ext/imp_avocet.go b/openrtb_ext/imp_avocet.go index 7c9ca8c6eed..4f361cdd271 100644 --- a/openrtb_ext/imp_avocet.go +++ b/openrtb_ext/imp_avocet.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpAvocet defines the contract for bidrequest.imp[i].ext.avocet +// ExtImpAvocet defines the contract for bidrequest.imp[i].ext.prebid.bidder.avocet type ExtImpAvocet struct { Placement string `json:"placement,omitempty"` PlacementCode string `json:"placement_code,omitempty"` diff --git a/openrtb_ext/imp_beyondmedia.go b/openrtb_ext/imp_beyondmedia.go new file mode 100644 index 00000000000..d9cb1ef838a --- /dev/null +++ b/openrtb_ext/imp_beyondmedia.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ImpExtBeyondMedia struct { + PlacementID string `json:"placementId"` +} diff --git a/openrtb_ext/imp_bidstack.go b/openrtb_ext/imp_bidstack.go new file mode 100644 index 00000000000..c3451c81937 --- /dev/null +++ b/openrtb_ext/imp_bidstack.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ImpExtBidstack struct { + PublisherID string `json:"publisherId"` +} diff --git a/openrtb_ext/imp_boldwin.go b/openrtb_ext/imp_boldwin.go new file mode 100644 index 00000000000..c7c4767d8a9 --- /dev/null +++ b/openrtb_ext/imp_boldwin.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ImpExtBoldwin struct { + PlacementID string `json:"placementId"` + EndpointID string `json:"endpointId"` +} diff --git a/openrtb_ext/imp_brightroll.go b/openrtb_ext/imp_brightroll.go index 9d396f4a858..bd42d4aae5d 100644 --- a/openrtb_ext/imp_brightroll.go +++ b/openrtb_ext/imp_brightroll.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpBrightroll defines the contract for bidrequest.imp[i].ext.brightroll +// ExtImpBrightroll defines the contract for bidrequest.imp[i].ext.prebid.bidder.brightroll type ExtImpBrightroll struct { Publisher string `json:"publisher"` } diff --git a/openrtb_ext/imp_ccx.go b/openrtb_ext/imp_ccx.go new file mode 100644 index 00000000000..bff400e10d9 --- /dev/null +++ b/openrtb_ext/imp_ccx.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ExtImpCcx struct { + PlacementID string `json:"placementId"` +} diff --git a/openrtb_ext/imp_consumable.go b/openrtb_ext/imp_consumable.go index 09601630d3d..fe916b09972 100644 --- a/openrtb_ext/imp_consumable.go +++ b/openrtb_ext/imp_consumable.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpConsumable defines the contract for bidrequest.imp[i].ext.consumable +// ExtImpConsumable defines the contract for bidrequest.imp[i].ext.prebid.bidder.consumable type ExtImpConsumable struct { NetworkId int `json:"networkId,omitempty"` SiteId int `json:"siteId,omitempty"` diff --git a/openrtb_ext/imp_criteo.go b/openrtb_ext/imp_criteo.go index e200aace496..66726fb5dda 100644 --- a/openrtb_ext/imp_criteo.go +++ b/openrtb_ext/imp_criteo.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpCriteo defines the contract for bidrequest.imp[i].ext.criteo +// ExtImpCriteo defines the contract for bidrequest.imp[i].ext.prebid.bidder.criteo type ExtImpCriteo struct { ZoneID int64 `json:"zoneId"` NetworkID int64 `json:"networkId"` diff --git a/openrtb_ext/imp_datablocks.go b/openrtb_ext/imp_datablocks.go index fdb5dd398d4..c8659d3ff11 100644 --- a/openrtb_ext/imp_datablocks.go +++ b/openrtb_ext/imp_datablocks.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpDatablocks defines the contract for bidrequest.imp[i].ext.datablocks +// ExtImpDatablocks defines the contract for bidrequest.imp[i].ext.prebid.bidder.datablocks type ExtImpDatablocks struct { SourceId int `json:"sourceId"` Host string `json:"host"` diff --git a/openrtb_ext/imp_deepintent.go b/openrtb_ext/imp_deepintent.go index 34c537d4898..b50c8fe0643 100644 --- a/openrtb_ext/imp_deepintent.go +++ b/openrtb_ext/imp_deepintent.go @@ -1,5 +1,5 @@ package openrtb_ext type ExtImpDeepintent struct { - TagID string `json:tagId` + TagID string `json:"tagId"` } diff --git a/openrtb_ext/imp_dianomi.go b/openrtb_ext/imp_dianomi.go new file mode 100644 index 00000000000..21df9681b1a --- /dev/null +++ b/openrtb_ext/imp_dianomi.go @@ -0,0 +1,10 @@ +package openrtb_ext + +import ( + "encoding/json" +) + +type ExtImpDianomi struct { + SmartadId json.Number `json:"smartadId,omitempty"` + PriceType string `json:"priceType,omitempty"` +} diff --git a/openrtb_ext/imp_engagebdr.go b/openrtb_ext/imp_engagebdr.go index 14cb444c040..db500111a78 100644 --- a/openrtb_ext/imp_engagebdr.go +++ b/openrtb_ext/imp_engagebdr.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpEngageBDR defines the contract for bidrequest.imp[i].ext.engagebdr +// ExtImpEngageBDR defines the contract for bidrequest.imp[i].ext.prebid.bidder.engagebdr type ExtImpEngageBDR struct { Sspid string `json:"sspid"` } diff --git a/openrtb_ext/imp_eplanning.go b/openrtb_ext/imp_eplanning.go index ef0e4f2ba89..ea6cfccf436 100644 --- a/openrtb_ext/imp_eplanning.go +++ b/openrtb_ext/imp_eplanning.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpEPlanning defines the contract for bidrequest.imp[i].ext.eplanning +// ExtImpEPlanning defines the contract for bidrequest.imp[i].ext.prebid.bidder.eplanning type ExtImpEPlanning struct { ClientID string `json:"ci"` AdUnitCode string `json:"adunit_code"` diff --git a/openrtb_ext/imp_freewheelssp.go b/openrtb_ext/imp_freewheelssp.go new file mode 100644 index 00000000000..11c22ff8154 --- /dev/null +++ b/openrtb_ext/imp_freewheelssp.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ImpExtFreewheelSSP struct { + ZoneId int `json:"zoneId"` +} diff --git a/openrtb_ext/imp_gamma.go b/openrtb_ext/imp_gamma.go index b9547c75f47..cb7a9df2b96 100644 --- a/openrtb_ext/imp_gamma.go +++ b/openrtb_ext/imp_gamma.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpGamma defines the contract for bidrequest.imp[i].ext.gamma +// ExtImpGamma defines the contract for bidrequest.imp[i].ext.prebid.bidder.gamma type ExtImpGamma struct { PartnerID string `json:"id"` ZoneID string `json:"zid"` diff --git a/openrtb_ext/imp_gamoshi.go b/openrtb_ext/imp_gamoshi.go index ecedc84788c..4453b09dd0b 100644 --- a/openrtb_ext/imp_gamoshi.go +++ b/openrtb_ext/imp_gamoshi.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpGamoshi defines the contract for bidrequest.imp[i].ext.gamoshi +// ExtImpGamoshi defines the contract for bidrequest.imp[i].ext.prebid.bidder.gamoshi type ExtImpGamoshi struct { SupplyPartnerId string `json:"supplyPartnerId"` FavoredMediaType string `json:"favoredMediaType"` diff --git a/openrtb_ext/imp_grid.go b/openrtb_ext/imp_grid.go index c4530426227..a063cc95fa9 100644 --- a/openrtb_ext/imp_grid.go +++ b/openrtb_ext/imp_grid.go @@ -2,7 +2,7 @@ package openrtb_ext import "encoding/json" -// ExtImpGrid defines the contract for bidrequest.imp[i].ext.grid +// ExtImpGrid defines the contract for bidrequest.imp[i].ext.prebid.bidder.grid type ExtImpGrid struct { Uid int `json:"uid"` Keywords json.RawMessage `json:"keywords,omitempty"` diff --git a/openrtb_ext/imp_gumgum.go b/openrtb_ext/imp_gumgum.go index 8e0d7dd8983..96a1308d663 100644 --- a/openrtb_ext/imp_gumgum.go +++ b/openrtb_ext/imp_gumgum.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpGumGum defines the contract for bidrequest.imp[i].ext.gumgum +// ExtImpGumGum defines the contract for bidrequest.imp[i].ext.prebid.bidder.gumgum // Either Zone or PubId must be present, others are optional parameters type ExtImpGumGum struct { Zone string `json:"zone,omitempty"` @@ -9,12 +9,12 @@ type ExtImpGumGum struct { Slot float64 `json:"slot,omitempty"` } -// ExtImpGumGumVideo defines the contract for bidresponse.seatbid.bid[i].ext.gumgum.video +// ExtImpGumGumVideo defines the contract for bidresponse.seatbid.bid[i].ext.prebid.bidder.gumgum.video type ExtImpGumGumVideo struct { IrisID string `json:"irisid,omitempty"` } -// ExtImpGumGumBanner defines the contract for bidresponse.seatbid.bid[i].ext.gumgum.banner +// ExtImpGumGumBanner defines the contract for bidresponse.seatbid.bid[i].ext.prebid.bidder.gumgum.banner type ExtImpGumGumBanner struct { Si float64 `json:"si,omitempty"` MaxW float64 `json:"maxw,omitempty"` diff --git a/openrtb_ext/imp_impactify.go b/openrtb_ext/imp_impactify.go index 122a98a4110..466c66a40b5 100644 --- a/openrtb_ext/imp_impactify.go +++ b/openrtb_ext/imp_impactify.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpImpactify defines the contract for bidrequest.imp[i].ext.impactify +// ExtImpImpactify defines the contract for bidrequest.imp[i].ext.prebid.bidder.impactify type ExtImpImpactify struct { AppID string `json:"appId"` Format string `json:"format"` diff --git a/openrtb_ext/imp_infytv.go b/openrtb_ext/imp_infytv.go new file mode 100644 index 00000000000..b7307c42e1a --- /dev/null +++ b/openrtb_ext/imp_infytv.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ImpExtInfytv struct { + PlacementID string `json:"placementId"` + PublisherID string `json:"publisherId"` +} diff --git a/openrtb_ext/imp_ix.go b/openrtb_ext/imp_ix.go index 99cd3a215e4..9f977fb0dcd 100644 --- a/openrtb_ext/imp_ix.go +++ b/openrtb_ext/imp_ix.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpIx defines the contract for bidrequest.imp[i].ext.ix +// ExtImpIx defines the contract for bidrequest.imp[i].ext.prebid.bidder.ix type ExtImpIx struct { SiteId string `json:"siteId"` Size []int `json:"size"` diff --git a/openrtb_ext/imp_kargo.go b/openrtb_ext/imp_kargo.go new file mode 100644 index 00000000000..682561fb1f0 --- /dev/null +++ b/openrtb_ext/imp_kargo.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ImpExtKargo struct { + AdSlotID string `json:"adSlotID"` +} diff --git a/openrtb_ext/imp_kayzen.go b/openrtb_ext/imp_kayzen.go index 35dece64036..163e2446ee6 100644 --- a/openrtb_ext/imp_kayzen.go +++ b/openrtb_ext/imp_kayzen.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtKayzen defines the contract for bidrequest.imp[i].ext.kayzen +// ExtKayzen defines the contract for bidrequest.imp[i].ext.prebid.bidder.kayzen type ExtKayzen struct { Zone string `json:"zone"` Exchange string `json:"exchange"` diff --git a/openrtb_ext/imp_kubient.go b/openrtb_ext/imp_kubient.go index fafd2a0eb8f..59dd3d2aaab 100644 --- a/openrtb_ext/imp_kubient.go +++ b/openrtb_ext/imp_kubient.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpKubient defines the contract for bidrequest.imp[i].ext.kubient +// ExtImpKubient defines the contract for bidrequest.imp[i].ext.prebid.bidder.kubient type ExtImpKubient struct { ZoneID string `json:"zoneid"` } diff --git a/openrtb_ext/imp_lockerdome.go b/openrtb_ext/imp_lockerdome.go index 8f228f12458..5f733cf8db1 100644 --- a/openrtb_ext/imp_lockerdome.go +++ b/openrtb_ext/imp_lockerdome.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpLockerDome defines the contract for bidrequest.imp[i].ext.lockerdome +// ExtImpLockerDome defines the contract for bidrequest.imp[i].ext.prebid.bidder.lockerdome type ExtImpLockerDome struct { // LockerDome params AdUnitId string `json:"adUnitId"` diff --git a/openrtb_ext/imp_madvertise.go b/openrtb_ext/imp_madvertise.go index 30bd88dbf51..d944a6c053f 100644 --- a/openrtb_ext/imp_madvertise.go +++ b/openrtb_ext/imp_madvertise.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpMadvertise defines the contract for bidrequest.imp[i].ext.madvertise +// ExtImpMadvertise defines the contract for bidrequest.imp[i].ext.prebid.bidder.madvertise type ExtImpMadvertise struct { ZoneID string `json:"zoneId"` } diff --git a/openrtb_ext/imp_marsmedia.go b/openrtb_ext/imp_marsmedia.go index e07b6d7cc51..1d14937dd11 100644 --- a/openrtb_ext/imp_marsmedia.go +++ b/openrtb_ext/imp_marsmedia.go @@ -2,7 +2,7 @@ package openrtb_ext import "encoding/json" -// ExtImpMarsmedia defines the contract for bidrequest.imp[i].ext.marsmedia +// ExtImpMarsmedia defines the contract for bidrequest.imp[i].ext.prebid.bidder.marsmedia type ExtImpMarsmedia struct { ZoneID json.Number `json:"zoneId"` } diff --git a/openrtb_ext/imp_mgid.go b/openrtb_ext/imp_mgid.go index d96d83cb1e9..d2e9cb20e33 100644 --- a/openrtb_ext/imp_mgid.go +++ b/openrtb_ext/imp_mgid.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpMgid defines the contract for bidrequest.imp[i].ext.mgid +// ExtImpMgid defines the contract for bidrequest.imp[i].ext.prebid.bidder.mgid type ExtImpMgid struct { AccountId string `json:"accountId"` PlacementId string `json:"placementId,omitempty"` diff --git a/openrtb_ext/imp_mobilefuse.go b/openrtb_ext/imp_mobilefuse.go index ea53c5914f1..68dd3b1277e 100644 --- a/openrtb_ext/imp_mobilefuse.go +++ b/openrtb_ext/imp_mobilefuse.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpMobileFuse defines the contract for bidrequest.imp[i].ext.mobilefuse +// ExtImpMobileFuse defines the contract for bidrequest.imp[i].ext.prebid.bidder.mobilefuse type ExtImpMobileFuse struct { PlacementId int `json:"placement_id"` PublisherId int `json:"pub_id"` diff --git a/openrtb_ext/imp_nanointeractive.go b/openrtb_ext/imp_nanointeractive.go index 77e386237ac..b381fab8eb7 100644 --- a/openrtb_ext/imp_nanointeractive.go +++ b/openrtb_ext/imp_nanointeractive.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpNanoInteractive defines the contract for bidrequest.imp[i].ext.nanointeractive +// ExtImpNanoInteractive defines the contract for bidrequest.imp[i].ext.prebid.bidder.nanointeractive type ExtImpNanoInteractive struct { Pid string `json:"pid"` Nq []string `json:"nq,omitempty"` diff --git a/openrtb_ext/imp_openweb.go b/openrtb_ext/imp_openweb.go index 3d28265e698..fc3cbdacdd0 100644 --- a/openrtb_ext/imp_openweb.go +++ b/openrtb_ext/imp_openweb.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpOpenWeb defines the contract for bidrequest.imp[i].ext.openweb +// ExtImpOpenWeb defines the contract for bidrequest.imp[i].ext.prebid.bidder.openweb type ExtImpOpenWeb struct { SourceID int `json:"aid"` PlacementID int `json:"placementId,omitempty"` diff --git a/openrtb_ext/imp_openx.go b/openrtb_ext/imp_openx.go index 2625cb3802d..38bce75f17c 100644 --- a/openrtb_ext/imp_openx.go +++ b/openrtb_ext/imp_openx.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpOpenx defines the contract for bidrequest.imp[i].ext.openx +// ExtImpOpenx defines the contract for bidrequest.imp[i].ext.prebid.bidder.openx type ExtImpOpenx struct { Unit string `json:"unit"` Platform string `json:"platform"` diff --git a/openrtb_ext/imp_orbidder.go b/openrtb_ext/imp_orbidder.go index ad141bdbcdf..0f9795480fa 100644 --- a/openrtb_ext/imp_orbidder.go +++ b/openrtb_ext/imp_orbidder.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpOrbidder defines the contract for bidrequest.imp[i].ext.openx +// ExtImpOrbidder defines the contract for bidrequest.imp[i].ext.prebid.bidder.openx type ExtImpOrbidder struct { AccountId string `json:"accountId"` PlacementId string `json:"placementId"` diff --git a/openrtb_ext/imp_outbrain.go b/openrtb_ext/imp_outbrain.go index 634f29481c1..6cabc9756c0 100644 --- a/openrtb_ext/imp_outbrain.go +++ b/openrtb_ext/imp_outbrain.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpOutbrain defines the contract for bidrequest.imp[i].ext.outbrain +// ExtImpOutbrain defines the contract for bidrequest.imp[i].ext.prebid.bidder.outbrain type ExtImpOutbrain struct { Publisher ExtImpOutbrainPublisher `json:"publisher"` TagId string `json:"tagid"` diff --git a/openrtb_ext/imp_pubmatic.go b/openrtb_ext/imp_pubmatic.go index db96ca518c6..2a45b491d0a 100644 --- a/openrtb_ext/imp_pubmatic.go +++ b/openrtb_ext/imp_pubmatic.go @@ -2,7 +2,7 @@ package openrtb_ext import "encoding/json" -// ExtImpPubmatic defines the contract for bidrequest.imp[i].ext.pubmatic +// ExtImpPubmatic defines the contract for bidrequest.imp[i].ext.prebid.bidder.pubmatic // PublisherId is mandatory parameters, others are optional parameters // AdSlot is identifier for specific ad placement or ad tag // Keywords is bid specific parameter, @@ -18,7 +18,7 @@ type ExtImpPubmatic struct { Kadfloor string `json:"kadfloor,omitempty"` } -// ExtImpPubmaticKeyVal defines the contract for bidrequest.imp[i].ext.pubmatic.keywords[i] +// ExtImpPubmaticKeyVal defines the contract for bidrequest.imp[i].ext.prebid.bidder.pubmatic.keywords[i] type ExtImpPubmaticKeyVal struct { Key string `json:"key,omitempty"` Values []string `json:"value,omitempty"` diff --git a/openrtb_ext/imp_pulsepoint.go b/openrtb_ext/imp_pulsepoint.go index c168c80f1bb..a901592c45b 100644 --- a/openrtb_ext/imp_pulsepoint.go +++ b/openrtb_ext/imp_pulsepoint.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpPulsePoint defines the json spec for bidrequest.imp[i].ext.pulsepoint +// ExtImpPulsePoint defines the json spec for bidrequest.imp[i].ext.prebid.bidder.pulsepoint // PubId/TagId are mandatory params type ExtImpPulsePoint struct { diff --git a/openrtb_ext/imp_rhythmone.go b/openrtb_ext/imp_rhythmone.go index fb5dc199c77..526a2843b53 100644 --- a/openrtb_ext/imp_rhythmone.go +++ b/openrtb_ext/imp_rhythmone.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpRhythmone defines the contract for bidrequest.imp[i].ext.rhythmone +// ExtImpRhythmone defines the contract for bidrequest.imp[i].ext.prebid.bidder.rhythmone type ExtImpRhythmone struct { PlacementId string `json:"placementId"` Zone string `json:"zone"` diff --git a/openrtb_ext/imp_rubicon.go b/openrtb_ext/imp_rubicon.go index a09b18ae273..75ca6d50735 100644 --- a/openrtb_ext/imp_rubicon.go +++ b/openrtb_ext/imp_rubicon.go @@ -4,7 +4,7 @@ import ( "encoding/json" ) -// ExtImpRubicon defines the contract for bidrequest.imp[i].ext.rubicon +// ExtImpRubicon defines the contract for bidrequest.imp[i].ext.prebid.bidder.rubicon type ExtImpRubicon struct { AccountId json.Number `json:"accountId"` SiteId json.Number `json:"siteId"` @@ -16,7 +16,7 @@ type ExtImpRubicon struct { Debug impExtRubiconDebug `json:"debug,omitempty"` } -// rubiconVideoParams defines the contract for bidrequest.imp[i].ext.rubicon.video +// rubiconVideoParams defines the contract for bidrequest.imp[i].ext.prebid.bidder.rubicon.video type rubiconVideoParams struct { Language string `json:"language,omitempty"` PlayerHeight json.Number `json:"playerHeight,omitempty"` @@ -26,7 +26,7 @@ type rubiconVideoParams struct { SkipDelay int `json:"skipdelay,omitempty"` } -// rubiconVideoParams defines the contract for bidrequest.imp[i].ext.rubicon.debug +// rubiconVideoParams defines the contract for bidrequest.imp[i].ext.prebid.bidder.rubicon.debug type impExtRubiconDebug struct { CpmOverride float64 `json:"cpmoverride,omitempty"` } diff --git a/openrtb_ext/imp_seedingAlliance.go b/openrtb_ext/imp_seedingAlliance.go new file mode 100644 index 00000000000..759683ad6b3 --- /dev/null +++ b/openrtb_ext/imp_seedingAlliance.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ImpExtSeedingAlliance struct { + AdUnitID string `json:"adUnitID"` +} diff --git a/openrtb_ext/imp_silvermob.go b/openrtb_ext/imp_silvermob.go index 9b2465534ca..627a1835686 100644 --- a/openrtb_ext/imp_silvermob.go +++ b/openrtb_ext/imp_silvermob.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtSilverMob defines the contract for bidrequest.imp[i].ext.silvermob +// ExtSilverMob defines the contract for bidrequest.imp[i].ext.prebid.bidder.silvermob type ExtSilverMob struct { ZoneID string `json:"zoneid"` Host string `json:"host"` diff --git a/openrtb_ext/imp_smaato.go b/openrtb_ext/imp_smaato.go index 14dcb73bdf3..bb92b318416 100644 --- a/openrtb_ext/imp_smaato.go +++ b/openrtb_ext/imp_smaato.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpSmaato defines the contract for bidrequest.imp[i].ext.smaato +// ExtImpSmaato defines the contract for bidrequest.imp[i].ext.prebid.bidder.smaato // 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 diff --git a/openrtb_ext/imp_smartadserver.go b/openrtb_ext/imp_smartadserver.go index d542e0ffd27..6f6a5df60d5 100644 --- a/openrtb_ext/imp_smartadserver.go +++ b/openrtb_ext/imp_smartadserver.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpSmartadserver defines the contract for bidrequest.imp[i].ext.smartadserver +// ExtImpSmartadserver defines the contract for bidrequest.imp[i].ext.prebid.bidder.smartadserver type ExtImpSmartadserver struct { SiteID int `json:"siteId"` PageID int `json:"pageId"` diff --git a/openrtb_ext/imp_smartyads.go b/openrtb_ext/imp_smartyads.go index 54911373e61..e056ec6be42 100644 --- a/openrtb_ext/imp_smartyads.go +++ b/openrtb_ext/imp_smartyads.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtSmartyAds defines the contract for bidrequest.imp[i].ext.smartyads +// ExtSmartyAds defines the contract for bidrequest.imp[i].ext.prebid.bidder.smartyads type ExtSmartyAds struct { AccountID string `json:"accountid"` SourceID string `json:"sourceid"` diff --git a/openrtb_ext/imp_sspbc.go b/openrtb_ext/imp_sspbc.go new file mode 100644 index 00000000000..750694bcf64 --- /dev/null +++ b/openrtb_ext/imp_sspbc.go @@ -0,0 +1,7 @@ +package openrtb_ext + +type ExtImpSspbc struct { + SiteId string `json:"siteId"` + Id string `json:"id"` + IsTest int `json:"test"` +} diff --git a/openrtb_ext/imp_stroeercore.go b/openrtb_ext/imp_stroeercore.go new file mode 100644 index 00000000000..a4a232ddaca --- /dev/null +++ b/openrtb_ext/imp_stroeercore.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ExtImpStroeerCore struct { + Sid string `json:"sid,omitempty"` +} diff --git a/openrtb_ext/imp_suntContent.go b/openrtb_ext/imp_suntContent.go new file mode 100644 index 00000000000..5040df7e3b6 --- /dev/null +++ b/openrtb_ext/imp_suntContent.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ImpExtSuntContent struct { + AdUnitID string `json:"adUnitID"` +} diff --git a/openrtb_ext/imp_synacormedia.go b/openrtb_ext/imp_synacormedia.go index af48c7dfd01..2c62a6d3f24 100644 --- a/openrtb_ext/imp_synacormedia.go +++ b/openrtb_ext/imp_synacormedia.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpSynacormedia defines the contract for bidrequest.imp[i].ext.synacormedia +// ExtImpSynacormedia defines the contract for bidrequest.imp[i].ext.prebid.bidder.synacormedia type ExtImpSynacormedia struct { SeatId string `json:"seatId"` TagId string `json:"tagId"` diff --git a/openrtb_ext/imp_taboola.go b/openrtb_ext/imp_taboola.go new file mode 100644 index 00000000000..f20b4903ab0 --- /dev/null +++ b/openrtb_ext/imp_taboola.go @@ -0,0 +1,10 @@ +package openrtb_ext + +type ImpExtTaboola struct { + PublisherId string `json:"publisherId"` + PublisherDomain string `json:"publisherDomain"` + BidFloor float64 `json:"bidfloor"` + TagId string `json:"tagid"` + BCat []string `json:"bcat"` + BAdv []string `json:"badv"` +} diff --git a/openrtb_ext/imp_triplelift.go b/openrtb_ext/imp_triplelift.go index 77ac9afdaf7..34b054846fc 100644 --- a/openrtb_ext/imp_triplelift.go +++ b/openrtb_ext/imp_triplelift.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpTriplelift defines the contract for bidrequest.imp[i].ext.triplelift +// ExtImpTriplelift defines the contract for bidrequest.imp[i].ext.prebid.bidder.triplelift type ExtImpTriplelift struct { InvCode string `json:"inventoryCode"` Floor *float64 `json:"floor"` diff --git a/openrtb_ext/imp_ucfunnel.go b/openrtb_ext/imp_ucfunnel.go index 408c1e0a35e..9cc06b8527a 100644 --- a/openrtb_ext/imp_ucfunnel.go +++ b/openrtb_ext/imp_ucfunnel.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpUcfunnel defines the contract for bidrequest.imp[i].ext.ucfunnel +// ExtImpUcfunnel defines the contract for bidrequest.imp[i].ext.prebid.bidder.ucfunnel type ExtImpUcfunnel struct { AdUnitId string `json:"adunitid"` PartnerId string `json:"partnerid"` diff --git a/openrtb_ext/imp_unicorn.go b/openrtb_ext/imp_unicorn.go index ad75414caa5..e9f745a96c0 100644 --- a/openrtb_ext/imp_unicorn.go +++ b/openrtb_ext/imp_unicorn.go @@ -1,9 +1,9 @@ package openrtb_ext -// ExtImpUnicorn defines the contract for bidrequest.imp[i].ext.unicorn +// ExtImpUnicorn defines the contract for bidrequest.imp[i].ext.prebid.bidder.unicorn type ExtImpUnicorn struct { PlacementID string `json:"placementId,omitempty"` - PublisherID int `json:"publisherId,omitempty"` - MediaID string `json:"mediaId"` - AccountID int `json:"accountId"` + PublisherID string `json:"publisherId,omitempty"` + MediaID string `json:"mediaId,omitempty"` + AccountID int `json:"accountId,omitempty"` } diff --git a/openrtb_ext/imp_verizonmedia.go b/openrtb_ext/imp_verizonmedia.go index ca824a60c22..2ae8e5ea70a 100644 --- a/openrtb_ext/imp_verizonmedia.go +++ b/openrtb_ext/imp_verizonmedia.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpVerizonMedia defines the contract for bidrequest.imp[i].ext.verizonmedia +// ExtImpVerizonMedia defines the contract for bidrequest.imp[i].ext.prebid.bidder.verizonmedia type ExtImpVerizonMedia struct { Dcn string `json:"dcn"` Pos string `json:"pos"` diff --git a/openrtb_ext/imp_videobyte.go b/openrtb_ext/imp_videobyte.go index 9101e9f2d4a..898c1cacf1b 100644 --- a/openrtb_ext/imp_videobyte.go +++ b/openrtb_ext/imp_videobyte.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpVideoByte defines the contract for bidrequest.imp[i].ext.videobyte +// ExtImpVideoByte defines the contract for bidrequest.imp[i].ext.prebid.bidder.videobyte type ExtImpVideoByte struct { PublisherId string `json:"pubId"` PlacementId string `json:"placementId"` diff --git a/openrtb_ext/imp_vrtcal.go b/openrtb_ext/imp_vrtcal.go index 5ba38da5d77..7c3a78bb016 100644 --- a/openrtb_ext/imp_vrtcal.go +++ b/openrtb_ext/imp_vrtcal.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpVrtcal defines the contract for bidrequest.imp[i].ext.vrtcal +// ExtImpVrtcal defines the contract for bidrequest.imp[i].ext.prebid.bidder.vrtcal type ExtImpVrtcal struct { Just_an_unused_vrtcal_param string `json:"Just_an_unused_vrtcal_param"` } diff --git a/openrtb_ext/imp_yahoossp.go b/openrtb_ext/imp_yahoossp.go index e3183c865a8..bd84be13c3e 100644 --- a/openrtb_ext/imp_yahoossp.go +++ b/openrtb_ext/imp_yahoossp.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpYahooSSP defines the contract for bidrequest.imp[i].ext.yahoossp +// ExtImpYahooSSP defines the contract for bidrequest.imp[i].ext.prebid.bidder.yahoossp type ExtImpYahooSSP struct { Dcn string `json:"dcn"` Pos string `json:"pos"` diff --git a/openrtb_ext/imp_yeahmobi.go b/openrtb_ext/imp_yeahmobi.go index 6c1c045d705..16948d807c4 100644 --- a/openrtb_ext/imp_yeahmobi.go +++ b/openrtb_ext/imp_yeahmobi.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpYeahmobi defines the contract for bidrequest.imp[i].ext.yeahmobi +// ExtImpYeahmobi defines the contract for bidrequest.imp[i].ext.prebid.bidder.yeahmobi type ExtImpYeahmobi struct { PubId string `json:"pubId"` ZoneId string `json:"zoneId"` diff --git a/openrtb_ext/imp_yieldlab.go b/openrtb_ext/imp_yieldlab.go index 604b7e8ceab..266c9c567d4 100644 --- a/openrtb_ext/imp_yieldlab.go +++ b/openrtb_ext/imp_yieldlab.go @@ -1,10 +1,9 @@ package openrtb_ext -// ExtImpYieldlab defines the contract for bidrequest.imp[i].ext.yieldlab +// ExtImpYieldlab defines the contract for bidrequest.imp[i].ext.prebid.bidder.yieldlab type ExtImpYieldlab struct { AdslotID string `json:"adslotId"` SupplyID string `json:"supplyId"` - AdSize string `json:"adSize"` Targeting map[string]string `json:"targeting"` ExtId string `json:"extId"` } diff --git a/openrtb_ext/imp_yieldmo.go b/openrtb_ext/imp_yieldmo.go index 2a2080daf46..d8daa2f2fd7 100644 --- a/openrtb_ext/imp_yieldmo.go +++ b/openrtb_ext/imp_yieldmo.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpYieldmo defines the contract for bidrequest.imp[i].ext.yieldmo +// ExtImpYieldmo defines the contract for bidrequest.imp[i].ext.prebid.bidder.yieldmo type ExtImpYieldmo struct { PlacementId string `json:"placementId"` } diff --git a/openrtb_ext/imp_yieldone.go b/openrtb_ext/imp_yieldone.go index 6eee563b448..796f2fb1d54 100644 --- a/openrtb_ext/imp_yieldone.go +++ b/openrtb_ext/imp_yieldone.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpYieldone defines the contract for bidrequest.imp[i].ext.yieldone +// ExtImpYieldone defines the contract for bidrequest.imp[i].ext.prebid.bidder.yieldone type ExtImpYieldone struct { PlacementId string `json:"placementId"` } diff --git a/openrtb_ext/imp_zeroclickfraud.go b/openrtb_ext/imp_zeroclickfraud.go index ae82fcacd9a..a73f25e4cc7 100644 --- a/openrtb_ext/imp_zeroclickfraud.go +++ b/openrtb_ext/imp_zeroclickfraud.go @@ -1,6 +1,6 @@ package openrtb_ext -// ExtImpZeroClickFraud defines the contract for bidrequest.imp[i].ext.datablocks +// ExtImpZeroClickFraud defines the contract for bidrequest.imp[i].ext.prebid.bidder.datablocks type ExtImpZeroClickFraud struct { SourceId int `json:"sourceId"` Host string `json:"host"` diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index 3abda32e192..47b66134717 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -3,6 +3,8 @@ package openrtb_ext import ( "encoding/json" "errors" + + "github.com/prebid/openrtb/v17/openrtb2" ) // FirstPartyDataExtKey defines a field name within request.ext and request.imp.ext reserved for first party data. @@ -17,6 +19,9 @@ const SKAdNExtKey = "skadn" // GPIDKey defines the field name within request.ext reserved for the Global Placement ID (GPID), const GPIDKey = "gpid" +// TIDKey reserved for Per-Impression Transactions IDs for Multi-Impression Bid Requests. +const TIDKey = "tid" + // NativeExchangeSpecificLowerBound defines the lower threshold of exchange specific types for native ads. There is no upper bound. const NativeExchangeSpecificLowerBound = 500 @@ -24,8 +29,8 @@ const MaxDecimalFigures int = 15 // ExtRequest defines the contract for bidrequest.ext type ExtRequest struct { - Prebid ExtRequestPrebid `json:"prebid"` - SChain *ExtRequestPrebidSChainSChain `json:"schain,omitempty"` + Prebid ExtRequestPrebid `json:"prebid"` + SChain *openrtb2.SupplyChain `json:"schain,omitempty"` } // ExtRequestPrebid defines the contract for bidrequest.ext.prebid @@ -33,25 +38,40 @@ type ExtRequestPrebid struct { Aliases map[string]string `json:"aliases,omitempty"` AliasGVLIDs map[string]uint16 `json:"aliasgvlids,omitempty"` BidAdjustmentFactors map[string]float64 `json:"bidadjustmentfactors,omitempty"` + BidderConfigs []BidderConfig `json:"bidderconfig,omitempty"` + BidderParams json.RawMessage `json:"bidderparams,omitempty"` Cache *ExtRequestPrebidCache `json:"cache,omitempty"` Channel *ExtRequestPrebidChannel `json:"channel,omitempty"` + CurrencyConversions *ExtRequestCurrency `json:"currency,omitempty"` Data *ExtRequestPrebidData `json:"data,omitempty"` Debug bool `json:"debug,omitempty"` - Integration string `json:"integration,omitempty"` Events json.RawMessage `json:"events,omitempty"` + Experiment *Experiment `json:"experiment,omitempty"` + Integration string `json:"integration,omitempty"` + Passthrough json.RawMessage `json:"passthrough,omitempty"` SChains []*ExtRequestPrebidSChain `json:"schains,omitempty"` + Server *ExtRequestPrebidServer `json:"server,omitempty"` 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. // The array may contain a single sstar ('*') entry to represent all bidders. NoSale []string `json:"nosale,omitempty"` - CurrencyConversions *ExtRequestCurrency `json:"currency,omitempty"` - BidderConfigs []BidderConfig `json:"bidderconfig,omitempty"` + //AlternateBidderCodes is populated with host's AlternateBidderCodes config if not defined in request + AlternateBidderCodes *ExtAlternateBidderCodes `json:"alternatebiddercodes,omitempty"` +} + +// Experiment defines if experimental features are available for the request +type Experiment struct { + AdsCert *AdsCert `json:"adscert,omitempty"` +} + +// AdsCert defines if Call Sign feature is enabled for request +type AdsCert struct { + Enabled bool `json:"enabled,omitempty"` } type BidderConfig struct { @@ -76,27 +96,8 @@ type ExtRequestCurrency struct { // ExtRequestPrebid defines the contract for bidrequest.ext.prebid.schains type ExtRequestPrebidSChain struct { - Bidders []string `json:"bidders,omitempty"` - SChain ExtRequestPrebidSChainSChain `json:"schain"` -} - -// ExtRequestPrebidSChainSChain defines the contract for bidrequest.ext.prebid.schains[i].schain -type ExtRequestPrebidSChainSChain struct { - Complete int `json:"complete"` - Nodes []*ExtRequestPrebidSChainSChainNode `json:"nodes"` - Ver string `json:"ver"` - Ext json.RawMessage `json:"ext,omitempty"` -} - -// ExtRequestPrebidSChainSChainNode defines the contract for bidrequest.ext.prebid.schains[i].schain[i].nodes -type ExtRequestPrebidSChainSChainNode struct { - ASI string `json:"asi"` - SID string `json:"sid"` - RID string `json:"rid,omitempty"` - Name string `json:"name,omitempty"` - Domain string `json:"domain,omitempty"` - HP int `json:"hp"` - Ext json.RawMessage `json:"ext,omitempty"` + Bidders []string `json:"bidders,omitempty"` + SChain openrtb2.SupplyChain `json:"schain"` } // ExtRequestPrebidChannel defines the contract for bidrequest.ext.prebid.channel @@ -111,20 +112,10 @@ type ExtRequestPrebidCache struct { VastXML *ExtRequestPrebidCacheVAST `json:"vastxml"` } -// UnmarshalJSON prevents nil bids arguments. -func (ert *ExtRequestPrebidCache) UnmarshalJSON(b []byte) error { - type typesAlias ExtRequestPrebidCache // Prevents infinite UnmarshalJSON loops - var proxy typesAlias - if err := json.Unmarshal(b, &proxy); err != nil { - return err - } - - if proxy.Bids == nil && proxy.VastXML == nil { - return errors.New(`request.ext.prebid.cache requires one of the "bids" or "vastxml" properties`) - } - - *ert = ExtRequestPrebidCache(proxy) - return nil +type ExtRequestPrebidServer struct { + ExternalUrl string `json:"externalurl"` + GvlID int `json:"gvlid"` + DataCenter string `json:"datacenter"` } // ExtRequestPrebidCacheBids defines the contract for bidrequest.ext.prebid.cache.bids diff --git a/openrtb_ext/request_test.go b/openrtb_ext/request_test.go index 98a2e1645a0..979c2edb52c 100644 --- a/openrtb_ext/request_test.go +++ b/openrtb_ext/request_test.go @@ -69,38 +69,6 @@ const ext3 = `{ } }` -func TestCacheIllegal(t *testing.T) { - var bids ExtRequestPrebidCache - if err := json.Unmarshal([]byte(`{}`), &bids); err == nil { - t.Error("Unmarshal should fail when cache.bids is undefined.") - } - if err := json.Unmarshal([]byte(`{"bids":null}`), &bids); err == nil { - t.Error("Unmarshal should fail when cache.bids is null.") - } - if err := json.Unmarshal([]byte(`{"bids":true}`), &bids); err == nil { - t.Error("Unmarshal should fail when cache.bids is not an object.") - } -} - -func TestCacheBids(t *testing.T) { - var bids ExtRequestPrebidCache - assert.NoError(t, json.Unmarshal([]byte(`{"bids":{}}`), &bids)) - assert.NotNil(t, bids.Bids) - assert.Nil(t, bids.VastXML) -} - -func TestCacheVast(t *testing.T) { - var bids ExtRequestPrebidCache - assert.NoError(t, json.Unmarshal([]byte(`{"vastxml":{}}`), &bids)) - assert.Nil(t, bids.Bids) - assert.NotNil(t, bids.VastXML) -} - -func TestCacheNothing(t *testing.T) { - var bids ExtRequestPrebidCache - assert.Error(t, json.Unmarshal([]byte(`{}`), &bids)) -} - type granularityTestData struct { json []byte target PriceGranularity diff --git a/openrtb_ext/request_wrapper.go b/openrtb_ext/request_wrapper.go index dfc2a41fc44..63d16185fbe 100644 --- a/openrtb_ext/request_wrapper.go +++ b/openrtb_ext/request_wrapper.go @@ -4,7 +4,7 @@ import ( "encoding/json" "errors" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" ) // RequestWrapper wraps the OpenRTB request to provide a storage location for unmarshalled ext fields, so they @@ -29,16 +29,64 @@ import ( // 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 - sourceExt *SourceExt + impWrappers []*ImpWrapper + impWrappersAccessed bool + userExt *UserExt + deviceExt *DeviceExt + requestExt *RequestExt + appExt *AppExt + regExt *RegExt + siteExt *SiteExt + sourceExt *SourceExt +} + +const ( + jsonEmptyObjectLength = 2 + consentedProvidersSettingsStringKey = "ConsentedProvidersSettings" + consentedProvidersSettingsListKey = "consented_providers_settings" + consentKey = "consent" + ampKey = "amp" + eidsKey = "eids" + gdprKey = "gdpr" + prebidKey = "prebid" + schainKey = "schain" + us_privacyKey = "us_privacy" +) + +// LenImp returns the number of impressions without causing the creation of ImpWrapper objects. +func (rw *RequestWrapper) LenImp() int { + if rw.impWrappersAccessed { + return len(rw.impWrappers) + } + + return len(rw.Imp) +} + +func (rw *RequestWrapper) GetImp() []*ImpWrapper { + if rw.impWrappersAccessed { + return rw.impWrappers + } + + // There is minimal difference between nil and empty arrays in Go, but it matters + // for json encoding. In practice there will always be at least one impression, + // so this is an optimization for tests with (appropriately) incomplete requests. + if rw.Imp != nil { + rw.impWrappers = make([]*ImpWrapper, len(rw.Imp)) + for i := range rw.Imp { + rw.impWrappers[i] = &ImpWrapper{Imp: &rw.Imp[i]} + } + } + + rw.impWrappersAccessed = true + + return rw.impWrappers +} + +func (rw *RequestWrapper) SetImp(imps []*ImpWrapper) { + rw.impWrappers = imps + rw.impWrappersAccessed = true } func (rw *RequestWrapper) GetUserExt() (*UserExt, error) { @@ -121,9 +169,12 @@ func (rw *RequestWrapper) GetSourceExt() (*SourceExt, error) { func (rw *RequestWrapper) RebuildRequest() error { if rw.BidRequest == nil { - return errors.New("Requestwrapper Sync called on a nil BidRequest") + return errors.New("Requestwrapper RebuildRequest called on a nil BidRequest") } + if err := rw.rebuildImp(); err != nil { + return err + } if err := rw.rebuildUserExt(); err != nil { return err } @@ -149,98 +200,153 @@ func (rw *RequestWrapper) RebuildRequest() error { return nil } -func (rw *RequestWrapper) rebuildUserExt() error { - if rw.BidRequest.User == nil && rw.userExt != nil && rw.userExt.Dirty() { - rw.User = &openrtb2.User{} +func (rw *RequestWrapper) rebuildImp() error { + if !rw.impWrappersAccessed { + return nil } - if rw.userExt != nil && rw.userExt.Dirty() { - userJson, err := rw.userExt.marshal() - if err != nil { + + if rw.impWrappers == nil { + rw.Imp = nil + return nil + } + + rw.Imp = make([]openrtb2.Imp, len(rw.impWrappers)) + for i := range rw.impWrappers { + if err := rw.impWrappers[i].RebuildImp(); err != nil { return err } + rw.Imp[i] = *rw.impWrappers[i].Imp + } + + return nil +} + +func (rw *RequestWrapper) rebuildUserExt() error { + if rw.userExt == nil || !rw.userExt.Dirty() { + return nil + } + + userJson, err := rw.userExt.marshal() + if err != nil { + return err + } + + if userJson != nil && rw.User == nil { + rw.User = &openrtb2.User{Ext: userJson} + } else if rw.User != nil { 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() { + return nil } - if rw.deviceExt != nil && rw.deviceExt.Dirty() { - deviceJson, err := rw.deviceExt.marshal() - if err != nil { - return err - } + + deviceJson, err := rw.deviceExt.marshal() + if err != nil { + return err + } + + if deviceJson != nil && rw.Device == nil { + rw.Device = &openrtb2.Device{Ext: deviceJson} + } else if rw.Device != nil { 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 + if rw.requestExt == nil || !rw.requestExt.Dirty() { + return nil + } + + 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() { + return nil } - if rw.appExt != nil && rw.appExt.Dirty() { - appJson, err := rw.appExt.marshal() - if err != nil { - return err - } + + appJson, err := rw.appExt.marshal() + if err != nil { + return err + } + + if appJson != nil && rw.App == nil { + rw.App = &openrtb2.App{Ext: appJson} + } else if rw.App != nil { 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() { + return nil } - if rw.regExt != nil && rw.regExt.Dirty() { - regsJson, err := rw.regExt.marshal() - if err != nil { - return err - } + + regsJson, err := rw.regExt.marshal() + if err != nil { + return err + } + + if regsJson != nil && rw.Regs == nil { + rw.Regs = &openrtb2.Regs{Ext: regsJson} + } else if rw.Regs != nil { 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() { + return nil } - if rw.siteExt != nil && rw.siteExt.Dirty() { - siteJson, err := rw.siteExt.marshal() - if err != nil { - return err - } + + siteJson, err := rw.siteExt.marshal() + if err != nil { + return err + } + + if siteJson != nil && rw.Site == nil { + rw.Site = &openrtb2.Site{Ext: siteJson} + } else if rw.Site != nil { rw.Site.Ext = siteJson } + return nil } func (rw *RequestWrapper) rebuildSourceExt() error { - if rw.Source == nil && rw.sourceExt != nil && rw.sourceExt.Dirty() { - rw.Source = &openrtb2.Source{} + if rw.sourceExt == nil || !rw.sourceExt.Dirty() { + return nil } - if rw.sourceExt != nil && rw.sourceExt.Dirty() { - sourceJson, err := rw.sourceExt.marshal() - if err != nil { - return err - } + + sourceJson, err := rw.sourceExt.marshal() + if err != nil { + return err + } + + if sourceJson != nil && rw.Source == nil { + rw.Source = &openrtb2.Source{Ext: sourceJson} + } else if rw.Source != nil { rw.Source.Ext = sourceJson } + return nil } @@ -249,21 +355,27 @@ func (rw *RequestWrapper) rebuildSourceExt() error { // --------------------------------------------------------------- type UserExt struct { - ext map[string]json.RawMessage - extDirty bool - consent *string - consentDirty bool - prebid *ExtUserPrebid - prebidDirty bool - eids *[]ExtUserEid - eidsDirty bool + ext map[string]json.RawMessage + extDirty bool + consent *string + consentDirty bool + prebid *ExtUserPrebid + prebidDirty bool + eids *[]openrtb2.EID + eidsDirty bool + consentedProvidersSettingsIn *ConsentedProvidersSettingsIn + consentedProvidersSettingsInDirty bool + consentedProvidersSettingsOut *ConsentedProvidersSettingsOut + consentedProvidersSettingsOutDirty 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 } @@ -272,14 +384,14 @@ func (ue *UserExt) unmarshal(extJson json.RawMessage) error { return err } - consentJson, hasConsent := ue.ext["consent"] + consentJson, hasConsent := ue.ext[consentKey] if hasConsent { if err := json.Unmarshal(consentJson, &ue.consent); err != nil { return err } } - prebidJson, hasPrebid := ue.ext["prebid"] + prebidJson, hasPrebid := ue.ext[prebidKey] if hasPrebid { ue.prebid = &ExtUserPrebid{} if err := json.Unmarshal(prebidJson, ue.prebid); err != nil { @@ -287,53 +399,105 @@ func (ue *UserExt) unmarshal(extJson json.RawMessage) error { } } - eidsJson, hasEids := ue.ext["eids"] + eidsJson, hasEids := ue.ext[eidsKey] if hasEids { - ue.eids = &[]ExtUserEid{} + ue.eids = &[]openrtb2.EID{} if err := json.Unmarshal(eidsJson, ue.eids); err != nil { return err } } + if consentedProviderSettingsJson, hasCPSettings := ue.ext[consentedProvidersSettingsStringKey]; hasCPSettings { + ue.consentedProvidersSettingsIn = &ConsentedProvidersSettingsIn{} + if err := json.Unmarshal(consentedProviderSettingsJson, ue.consentedProvidersSettingsIn); err != nil { + return err + } + } + + if consentedProviderSettingsJson, hasCPSettings := ue.ext[consentedProvidersSettingsListKey]; hasCPSettings { + ue.consentedProvidersSettingsOut = &ConsentedProvidersSettingsOut{} + if err := json.Unmarshal(consentedProviderSettingsJson, ue.consentedProvidersSettingsOut); 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) + if ue.consent != nil && len(*ue.consent) > 0 { + consentJson, err := json.Marshal(ue.consent) + if err != nil { + return nil, err + } + ue.ext[consentKey] = json.RawMessage(consentJson) } else { - delete(ue.ext, "consent") + delete(ue.ext, consentKey) } 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) + if ue.prebid != nil { + prebidJson, err := json.Marshal(ue.prebid) + if err != nil { + return nil, err + } + if len(prebidJson) > jsonEmptyObjectLength { + ue.ext[prebidKey] = json.RawMessage(prebidJson) + } else { + delete(ue.ext, prebidKey) + } } else { - delete(ue.ext, "prebid") + delete(ue.ext, prebidKey) } ue.prebidDirty = false } + if ue.consentedProvidersSettingsInDirty { + if ue.consentedProvidersSettingsIn != nil { + cpSettingsJson, err := json.Marshal(ue.consentedProvidersSettingsIn) + if err != nil { + return nil, err + } + if len(cpSettingsJson) > jsonEmptyObjectLength { + ue.ext[consentedProvidersSettingsStringKey] = json.RawMessage(cpSettingsJson) + } else { + delete(ue.ext, consentedProvidersSettingsStringKey) + } + } else { + delete(ue.ext, consentedProvidersSettingsStringKey) + } + ue.consentedProvidersSettingsInDirty = false + } + + if ue.consentedProvidersSettingsOutDirty { + if ue.consentedProvidersSettingsOut != nil { + cpSettingsJson, err := json.Marshal(ue.consentedProvidersSettingsOut) + if err != nil { + return nil, err + } + if len(cpSettingsJson) > jsonEmptyObjectLength { + ue.ext[consentedProvidersSettingsListKey] = json.RawMessage(cpSettingsJson) + } else { + delete(ue.ext, consentedProvidersSettingsListKey) + } + } else { + delete(ue.ext, consentedProvidersSettingsListKey) + } + ue.consentedProvidersSettingsOutDirty = false + } + if ue.eidsDirty { - if len(*ue.eids) > 0 { + if ue.eids != nil && len(*ue.eids) > 0 { eidsJson, err := json.Marshal(ue.eids) if err != nil { return nil, err } - ue.ext["eids"] = json.RawMessage(eidsJson) + ue.ext[eidsKey] = json.RawMessage(eidsJson) } else { - delete(ue.ext, "eids") + delete(ue.ext, eidsKey) } ue.eidsDirty = false } @@ -343,11 +507,10 @@ func (ue *UserExt) marshal() (json.RawMessage, error) { return nil, nil } return json.Marshal(ue.ext) - } func (ue *UserExt) Dirty() bool { - return ue.extDirty || ue.eidsDirty || ue.prebidDirty || ue.consentDirty + return ue.extDirty || ue.eidsDirty || ue.prebidDirty || ue.consentDirty || ue.consentedProvidersSettingsInDirty || ue.consentedProvidersSettingsOutDirty } func (ue *UserExt) GetExt() map[string]json.RawMessage { @@ -376,6 +539,46 @@ func (ue *UserExt) SetConsent(consent *string) { ue.consentDirty = true } +// GetConsentedProvidersSettingsIn() returns a reference to a copy of ConsentedProvidersSettingsIn, a struct that +// has a string field formatted as a Google's Additional Consent string +func (ue *UserExt) GetConsentedProvidersSettingsIn() *ConsentedProvidersSettingsIn { + if ue.consentedProvidersSettingsIn == nil { + return nil + } + consentedProvidersSettingsIn := *ue.consentedProvidersSettingsIn + return &consentedProvidersSettingsIn +} + +// SetConsentedProvidersSettingsIn() sets ConsentedProvidersSettingsIn, a struct that +// has a string field formatted as a Google's Additional Consent string +func (ue *UserExt) SetConsentedProvidersSettingsIn(cpSettings *ConsentedProvidersSettingsIn) { + ue.consentedProvidersSettingsIn = cpSettings + ue.consentedProvidersSettingsInDirty = true +} + +// GetConsentedProvidersSettingsOut() returns a reference to a copy of ConsentedProvidersSettingsOut, a struct that +// has an int array field listing Google's Additional Consent string elements +func (ue *UserExt) GetConsentedProvidersSettingsOut() *ConsentedProvidersSettingsOut { + if ue.consentedProvidersSettingsOut == nil { + return nil + } + consentedProvidersSettingsOut := *ue.consentedProvidersSettingsOut + return &consentedProvidersSettingsOut +} + +// SetConsentedProvidersSettingsIn() sets ConsentedProvidersSettingsOut, a struct that +// has an int array field listing Google's Additional Consent string elements. This +// function overrides an existing ConsentedProvidersSettingsOut object, if any +func (ue *UserExt) SetConsentedProvidersSettingsOut(cpSettings *ConsentedProvidersSettingsOut) { + if cpSettings == nil { + return + } + + ue.consentedProvidersSettingsOut = cpSettings + ue.consentedProvidersSettingsOutDirty = true + return +} + func (ue *UserExt) GetPrebid() *ExtUserPrebid { if ue.prebid == nil { return nil @@ -389,7 +592,7 @@ func (ue *UserExt) SetPrebid(prebid *ExtUserPrebid) { ue.prebidDirty = true } -func (ue *UserExt) GetEid() *[]ExtUserEid { +func (ue *UserExt) GetEid() *[]openrtb2.EID { if ue.eids == nil { return nil } @@ -397,7 +600,7 @@ func (ue *UserExt) GetEid() *[]ExtUserEid { return &eids } -func (ue *UserExt) SetEid(eid *[]ExtUserEid) { +func (ue *UserExt) SetEid(eid *[]openrtb2.EID) { ue.eids = eid ue.eidsDirty = true } @@ -411,7 +614,7 @@ type RequestExt struct { extDirty bool prebid *ExtRequestPrebid prebidDirty bool - schain *ExtRequestPrebidSChainSChain // ORTB 2.4 location + schain *openrtb2.SupplyChain // ORTB 2.4 location schainDirty bool } @@ -419,54 +622,67 @@ 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 { + + if err := json.Unmarshal(extJson, &re.ext); err != nil { return err } - prebidJson, hasPrebid := re.ext["prebid"] + + prebidJson, hasPrebid := re.ext[prebidKey] if hasPrebid { re.prebid = &ExtRequestPrebid{} - err = json.Unmarshal(prebidJson, re.prebid) + if err := json.Unmarshal(prebidJson, re.prebid); err != nil { + return err + } } - schainJson, hasSChain := re.ext["schain"] + + schainJson, hasSChain := re.ext[schainKey] if hasSChain { - re.schain = &ExtRequestPrebidSChainSChain{} - err = json.Unmarshal(schainJson, re.schain) + re.schain = &openrtb2.SupplyChain{} + if err := json.Unmarshal(schainJson, re.schain); err != nil { + return err + } } - return err + return nil } 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) + if re.prebid != nil { + prebidJson, err := json.Marshal(re.prebid) + if err != nil { + return nil, err + } + if len(prebidJson) > jsonEmptyObjectLength { + re.ext[prebidKey] = json.RawMessage(prebidJson) + } else { + delete(re.ext, prebidKey) + } } else { - delete(re.ext, "prebid") + delete(re.ext, prebidKey) } re.prebidDirty = false } if re.schainDirty { - if re.schain == nil { - } - - schainJson, err := json.Marshal(re.schain) - if err != nil { - return nil, err - } - if len(schainJson) > 2 && re.schain != nil { - re.ext["schain"] = json.RawMessage(schainJson) + if re.schain != nil { + schainJson, err := json.Marshal(re.schain) + if err != nil { + return nil, err + } + if len(schainJson) > jsonEmptyObjectLength { + re.ext[schainKey] = json.RawMessage(schainJson) + } else { + delete(re.ext, schainKey) + } } else { - delete(re.ext, "schain") + delete(re.ext, schainKey) } re.schainDirty = false } @@ -511,7 +727,7 @@ func (re *RequestExt) SetPrebid(prebid *ExtRequestPrebid) { // These schain methods on the request.ext are only for ORTB 2.4 backwards compatibility and // should not be used for any other purposes. To access ORTB 2.5 schains, see source.ext.schain // or request.ext.prebid.schains. -func (re *RequestExt) GetSChain() *ExtRequestPrebidSChainSChain { +func (re *RequestExt) GetSChain() *openrtb2.SupplyChain { if re.schain == nil { return nil } @@ -519,7 +735,7 @@ func (re *RequestExt) GetSChain() *ExtRequestPrebidSChainSChain { return &schain } -func (re *RequestExt) SetSChain(schain *ExtRequestPrebidSChainSChain) { +func (re *RequestExt) SetSChain(schain *openrtb2.SupplyChain) { re.schain = schain re.schainDirty = true } @@ -543,33 +759,42 @@ 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 { + + if err := json.Unmarshal(extJson, &de.ext); err != nil { return err } - prebidJson, hasPrebid := de.ext["prebid"] + + prebidJson, hasPrebid := de.ext[prebidKey] if hasPrebid { de.prebid = &ExtDevicePrebid{} - err = json.Unmarshal(prebidJson, de.prebid) + if err := json.Unmarshal(prebidJson, de.prebid); err != nil { + return err + } } - return err + return nil } 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) + if de.prebid != nil { + prebidJson, err := json.Marshal(de.prebid) + if err != nil { + return nil, err + } + if len(prebidJson) > jsonEmptyObjectLength { + de.ext[prebidKey] = json.RawMessage(prebidJson) + } else { + delete(de.ext, prebidKey) + } } else { - delete(de.ext, "prebid") + delete(de.ext, prebidKey) } de.prebidDirty = false } @@ -626,33 +851,42 @@ 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 { + + if err := json.Unmarshal(extJson, &ae.ext); err != nil { return err } - prebidJson, hasPrebid := ae.ext["prebid"] + + prebidJson, hasPrebid := ae.ext[prebidKey] if hasPrebid { ae.prebid = &ExtAppPrebid{} - err = json.Unmarshal(prebidJson, ae.prebid) + if err := json.Unmarshal(prebidJson, ae.prebid); err != nil { + return err + } } - return err + return nil } 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) + if ae.prebid != nil { + prebidJson, err := json.Marshal(ae.prebid) + if err != nil { + return nil, err + } + if len(prebidJson) > jsonEmptyObjectLength { + ae.ext[prebidKey] = json.RawMessage(prebidJson) + } else { + delete(ae.ext, prebidKey) + } } else { - delete(ae.ext, "prebid") + delete(ae.ext, prebidKey) } ae.prebidDirty = false } @@ -701,6 +935,8 @@ func (ae *AppExt) SetPrebid(prebid *ExtAppPrebid) { type RegExt struct { ext map[string]json.RawMessage extDirty bool + gdpr *int8 + gdprDirty bool usPrivacy string usPrivacyDirty bool } @@ -709,32 +945,57 @@ 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 { + + if err := json.Unmarshal(extJson, &re.ext); err != nil { return err } - uspJson, hasUsp := re.ext["us_privacy"] + + gdprJson, hasGDPR := re.ext[gdprKey] + if hasGDPR { + if err := json.Unmarshal(gdprJson, &re.gdpr); err != nil { + return errors.New("gdpr must be an integer") + } + } + + uspJson, hasUsp := re.ext[us_privacyKey] if hasUsp { - err = json.Unmarshal(uspJson, &re.usPrivacy) + if err := json.Unmarshal(uspJson, &re.usPrivacy); err != nil { + return err + } } - return err + return nil } func (re *RegExt) marshal() (json.RawMessage, error) { + if re.gdprDirty { + if re.gdpr != nil { + rawjson, err := json.Marshal(re.gdpr) + if err != nil { + return nil, err + } + re.ext[gdprKey] = rawjson + } else { + delete(re.ext, gdprKey) + } + re.gdprDirty = false + } + 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 + re.ext[us_privacyKey] = rawjson } else { - delete(re.ext, "us_privacy") + delete(re.ext, us_privacyKey) } re.usPrivacyDirty = false } @@ -747,7 +1008,7 @@ func (re *RegExt) marshal() (json.RawMessage, error) { } func (re *RegExt) Dirty() bool { - return re.extDirty || re.usPrivacyDirty + return re.extDirty || re.gdprDirty || re.usPrivacyDirty } func (re *RegExt) GetExt() map[string]json.RawMessage { @@ -763,13 +1024,23 @@ func (re *RegExt) SetExt(ext map[string]json.RawMessage) { re.extDirty = true } +func (re *RegExt) GetGDPR() *int8 { + gdpr := re.gdpr + return gdpr +} + +func (re *RegExt) SetGDPR(gdpr *int8) { + re.gdpr = gdpr + re.gdprDirty = true +} + func (re *RegExt) GetUSPrivacy() string { uSPrivacy := re.usPrivacy return uSPrivacy } -func (re *RegExt) SetUSPrivacy(uSPrivacy string) { - re.usPrivacy = uSPrivacy +func (re *RegExt) SetUSPrivacy(usPrivacy string) { + re.usPrivacy = usPrivacy re.usPrivacyDirty = true } @@ -780,7 +1051,7 @@ func (re *RegExt) SetUSPrivacy(uSPrivacy string) { type SiteExt struct { ext map[string]json.RawMessage extDirty bool - amp int8 + amp *int8 ampDirty bool } @@ -788,35 +1059,37 @@ 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 { + + if err := json.Unmarshal(extJson, &se.ext); err != nil { return err } - AmpJson, hasAmp := se.ext["amp"] + + ampJson, hasAmp := se.ext[ampKey] 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`) + if err := json.Unmarshal(ampJson, &se.amp); err != nil { + return errors.New(`request.site.ext.amp must be either 1, 0, or undefined`) } } - return err + return nil } 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) + if se.amp != nil { + ampJson, err := json.Marshal(se.amp) + if err != nil { + return nil, err + } + se.ext[ampKey] = json.RawMessage(ampJson) } else { - delete(se.ext, "amp") + delete(se.ext, ampKey) } se.ampDirty = false } @@ -845,11 +1118,11 @@ func (se *SiteExt) SetExt(ext map[string]json.RawMessage) { se.extDirty = true } -func (se *SiteExt) GetAmp() int8 { +func (se *SiteExt) GetAmp() *int8 { return se.amp } -func (se *SiteExt) SetUSPrivacy(amp int8) { +func (se *SiteExt) SetAmp(amp *int8) { se.amp = amp se.ampDirty = true } @@ -861,7 +1134,7 @@ func (se *SiteExt) SetUSPrivacy(amp int8) { type SourceExt struct { ext map[string]json.RawMessage extDirty bool - schain *ExtRequestPrebidSChainSChain + schain *openrtb2.SupplyChain schainDirty bool } @@ -869,32 +1142,41 @@ func (se *SourceExt) 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 { + + if err := json.Unmarshal(extJson, &se.ext); err != nil { return err } - schainJson, hasSChain := se.ext["schain"] + + schainJson, hasSChain := se.ext[schainKey] if hasSChain { - err = json.Unmarshal(schainJson, &se.schain) + if err := json.Unmarshal(schainJson, &se.schain); err != nil { + return err + } } - return err + return nil } func (se *SourceExt) marshal() (json.RawMessage, error) { if se.schainDirty { - schainJson, err := json.Marshal(se.schain) - if err != nil { - return nil, err - } - if len(schainJson) > 2 { - se.ext["schain"] = json.RawMessage(schainJson) + if se.schain != nil { + schainJson, err := json.Marshal(se.schain) + if err != nil { + return nil, err + } + if len(schainJson) > jsonEmptyObjectLength { + se.ext[schainKey] = json.RawMessage(schainJson) + } else { + delete(se.ext, schainKey) + } } else { - delete(se.ext, "schain") + delete(se.ext, schainKey) } se.schainDirty = false } @@ -923,7 +1205,7 @@ func (se *SourceExt) SetExt(ext map[string]json.RawMessage) { se.extDirty = true } -func (se *SourceExt) GetSChain() *ExtRequestPrebidSChainSChain { +func (se *SourceExt) GetSChain() *openrtb2.SupplyChain { if se.schain == nil { return nil } @@ -931,7 +1213,156 @@ func (se *SourceExt) GetSChain() *ExtRequestPrebidSChainSChain { return &schain } -func (se *SourceExt) SetSChain(schain *ExtRequestPrebidSChainSChain) { +func (se *SourceExt) SetSChain(schain *openrtb2.SupplyChain) { se.schain = schain se.schainDirty = true } + +// ImpWrapper wraps an OpenRTB impression object to provide storage for unmarshalled ext fields, so they +// will not need to be unmarshalled multiple times. It is intended to use the ImpWrapper via the RequestWrapper +// and follow the same usage conventions. +type ImpWrapper struct { + *openrtb2.Imp + impExt *ImpExt +} + +func (w *ImpWrapper) GetImpExt() (*ImpExt, error) { + if w.impExt != nil { + return w.impExt, nil + } + w.impExt = &ImpExt{} + if w.Imp == nil || w.Ext == nil { + return w.impExt, w.impExt.unmarshal(json.RawMessage{}) + } + return w.impExt, w.impExt.unmarshal(w.Ext) +} + +func (w *ImpWrapper) RebuildImp() error { + if w.Imp == nil { + return errors.New("ImpWrapper RebuildImp called on a nil Imp") + } + + if err := w.rebuildImpExt(); err != nil { + return err + } + + return nil +} + +func (w *ImpWrapper) rebuildImpExt() error { + if w.impExt == nil || !w.impExt.Dirty() { + return nil + } + + impJson, err := w.impExt.marshal() + if err != nil { + return err + } + + w.Ext = impJson + + return nil +} + +// --------------------------------------------------------------- +// ImpExt provides an interface for imp.ext +// --------------------------------------------------------------- + +type ImpExt struct { + ext map[string]json.RawMessage + extDirty bool + prebid *ExtImpPrebid + prebidDirty bool +} + +func (e *ImpExt) unmarshal(extJson json.RawMessage) error { + if len(e.ext) != 0 || e.Dirty() { + return nil + } + + e.ext = make(map[string]json.RawMessage) + + if len(extJson) == 0 { + return nil + } + + if err := json.Unmarshal(extJson, &e.ext); err != nil { + return err + } + + prebidJson, hasPrebid := e.ext[prebidKey] + if hasPrebid { + e.prebid = &ExtImpPrebid{} + if err := json.Unmarshal(prebidJson, e.prebid); err != nil { + return err + } + } + + return nil +} + +func (e *ImpExt) marshal() (json.RawMessage, error) { + if e.prebidDirty { + if e.prebid != nil { + prebidJson, err := json.Marshal(e.prebid) + if err != nil { + return nil, err + } + if len(prebidJson) > jsonEmptyObjectLength { + e.ext[prebidKey] = json.RawMessage(prebidJson) + } else { + delete(e.ext, prebidKey) + } + } else { + delete(e.ext, prebidKey) + } + e.prebidDirty = false + } + + e.extDirty = false + if len(e.ext) == 0 { + return nil, nil + } + return json.Marshal(e.ext) +} + +func (e *ImpExt) Dirty() bool { + return e.extDirty || e.prebidDirty +} + +func (e *ImpExt) GetExt() map[string]json.RawMessage { + ext := make(map[string]json.RawMessage) + for k, v := range e.ext { + ext[k] = v + } + return ext +} + +func (e *ImpExt) SetExt(ext map[string]json.RawMessage) { + e.ext = ext + e.extDirty = true +} + +func (e *ImpExt) GetPrebid() *ExtImpPrebid { + if e.prebid == nil { + return nil + } + prebid := *e.prebid + return &prebid +} + +func (e *ImpExt) GetOrCreatePrebid() *ExtImpPrebid { + if e.prebid == nil { + e.prebid = &ExtImpPrebid{} + } + return e.GetPrebid() +} + +func (e *ImpExt) SetPrebid(prebid *ExtImpPrebid) { + e.prebid = prebid + e.prebidDirty = true +} + +func CreateImpExtForTesting(ext map[string]json.RawMessage, prebid *ExtImpPrebid) ImpExt { + return ImpExt{ext: ext, prebid: prebid} +} diff --git a/openrtb_ext/request_wrapper_test.go b/openrtb_ext/request_wrapper_test.go index 60ef1a33263..cb4aef21bed 100644 --- a/openrtb_ext/request_wrapper_test.go +++ b/openrtb_ext/request_wrapper_test.go @@ -4,11 +4,10 @@ import ( "encoding/json" "testing" + "github.com/prebid/openrtb/v17/openrtb2" "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{} @@ -17,27 +16,970 @@ func TestUserExt(t *testing.T) { assert.Nil(t, userExt.GetConsent(), "Empty UserExt should have nil consent") assert.Nil(t, userExt.GetEid(), "Empty UserExt should have nil eid") assert.Nil(t, userExt.GetPrebid(), "Empty UserExt should have nil prebid") + assert.Nil(t, userExt.GetConsentedProvidersSettingsIn(), "Empty UserExt should have nil consentedProvidersSettings") newConsent := "NewConsent" userExt.SetConsent(&newConsent) assert.Equal(t, "NewConsent", *userExt.GetConsent(), "UserExt consent is incorrect") - newEid := []ExtUserEid{{}} + eid := openrtb2.EID{Source: "source", UIDs: []openrtb2.UID{{ID: "id"}}} + newEid := []openrtb2.EID{eid} userExt.SetEid(&newEid) - assert.Equal(t, []ExtUserEid{{}}, *userExt.GetEid(), "UserExt eid is incorrect") + assert.Equal(t, []openrtb2.EID{eid}, *userExt.GetEid(), "UserExt eid is incorrect") buyerIDs := map[string]string{"buyer": "id"} newPrebid := ExtUserPrebid{BuyerUIDs: buyerIDs} userExt.SetPrebid(&newPrebid) - assert.Equal(t, ExtUserPrebid{BuyerUIDs: buyerIDs}, *userExt.GetPrebid(), "UserExt prebid is icorrect") + assert.Equal(t, ExtUserPrebid{BuyerUIDs: buyerIDs}, *userExt.GetPrebid(), "UserExt prebid is incorrect") + consentedProvidersSettings := &ConsentedProvidersSettingsIn{ConsentedProvidersString: "1~X.X.X"} + userExt.SetConsentedProvidersSettingsIn(consentedProvidersSettings) + assert.Equal(t, &ConsentedProvidersSettingsIn{ConsentedProvidersString: "1~X.X.X"}, userExt.GetConsentedProvidersSettingsIn(), "UserExt consentedProvidersSettings is incorrect") assert.Equal(t, true, userExt.Dirty(), "UserExt should be dirty after field updates") + cpsIn := userExt.GetConsentedProvidersSettingsIn() + assert.Equal(t, "1~X.X.X", cpsIn.ConsentedProvidersString, "UserExt consentedProviders is incorrect") + + consentedProvidersString := "1~1.35.41.101" + cpsIn.ConsentedProvidersString = consentedProvidersString + userExt.SetConsentedProvidersSettingsIn(cpsIn) + cpsIn = userExt.GetConsentedProvidersSettingsIn() + assert.Equal(t, "1~1.35.41.101", cpsIn.ConsentedProvidersString, "UserExt consentedProviders is incorrect") + + cpsOut := &ConsentedProvidersSettingsOut{} + //cpsOut.ConsentedProvidersList = make([]int, 0, 1) + cpsOut.ConsentedProvidersList = append(cpsOut.ConsentedProvidersList, ParseConsentedProvidersString(consentedProvidersString)...) + assert.Len(t, cpsOut.ConsentedProvidersList, 4, "UserExt consentedProvidersList is incorrect") + userExt.SetConsentedProvidersSettingsOut(cpsOut) updatedUserExt, err := userExt.marshal() assert.Nil(t, err, "Marshalling UserExt after updating should not cause an error") - expectedUserExt := json.RawMessage(`{"consent":"NewConsent","prebid":{"buyeruids":{"buyer":"id"}},"eids":[{"source":""}]}`) - assert.JSONEq(t, string(updatedUserExt), string(expectedUserExt), "Marshalled UserExt is incorrect") + expectedUserExt := json.RawMessage(`{"consent":"NewConsent","prebid":{"buyeruids":{"buyer":"id"}},"consented_providers_settings":{"consented_providers":[1,35,41,101]},"ConsentedProvidersSettings":{"consented_providers":"1~1.35.41.101"},"eids":[{"source":"source","uids":[{"id":"id"}]}]}`) + assert.JSONEq(t, string(expectedUserExt), string(updatedUserExt), "Marshalled UserExt is incorrect") assert.Equal(t, false, userExt.Dirty(), "UserExt should not be dirty after marshalling") } + +func TestRebuildImp(t *testing.T) { + var ( + prebid = &ExtImpPrebid{IsRewardedInventory: openrtb2.Int8Ptr(1)} + prebidJson = json.RawMessage(`{"prebid":{"is_rewarded_inventory":1}}`) + ) + + testCases := []struct { + description string + request openrtb2.BidRequest + requestImpWrapper []*ImpWrapper + expectedRequest openrtb2.BidRequest + expectedError string + }{ + { + description: "Empty - Never Accessed", + request: openrtb2.BidRequest{}, + requestImpWrapper: nil, + expectedRequest: openrtb2.BidRequest{}, + }, + { + description: "One - Never Accessed", + request: openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "1"}}}, + requestImpWrapper: nil, + expectedRequest: openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "1"}}}, + }, + { + description: "One - Accessed - Dirty", + request: openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "1"}}}, + requestImpWrapper: []*ImpWrapper{{Imp: &openrtb2.Imp{ID: "2"}, impExt: &ImpExt{prebid: prebid, prebidDirty: true}}}, + expectedRequest: openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "2", Ext: prebidJson}}}, + }, + { + description: "One - Accessed - Error", + request: openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "1"}}}, + requestImpWrapper: []*ImpWrapper{{Imp: nil, impExt: &ImpExt{}}}, + expectedError: "ImpWrapper RebuildImp called on a nil Imp", + }, + { + description: "Many - Accessed - Dirty / Not Dirty", + request: openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "1"}, {ID: "2"}}}, + requestImpWrapper: []*ImpWrapper{{Imp: &openrtb2.Imp{ID: "1"}, impExt: &ImpExt{}}, {Imp: &openrtb2.Imp{ID: "2"}, impExt: &ImpExt{prebid: prebid, prebidDirty: true}}}, + expectedRequest: openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "1"}, {ID: "2", Ext: prebidJson}}}, + }, + } + + for _, test := range testCases { + // create required filed in the test loop to keep test declaration easier to read + for _, w := range test.requestImpWrapper { + w.impExt.ext = make(map[string]json.RawMessage) + } + + w := RequestWrapper{BidRequest: &test.request, impWrappers: test.requestImpWrapper, impWrappersAccessed: test.requestImpWrapper != nil} + err := w.RebuildRequest() + + if test.expectedError != "" { + assert.EqualError(t, err, test.expectedError, test.description) + } else { + assert.NoError(t, err, test.description) + assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description) + } + } +} + +func TestRebuildUserExt(t *testing.T) { + strA := "a" + strB := "b" + + type testCase struct { + description string + request openrtb2.BidRequest + requestUserExtWrapper UserExt + expectedRequest openrtb2.BidRequest + } + testGroups := []struct { + groupDesc string + tests []testCase + }{ + { + groupDesc: "Consent string tests", + tests: []testCase{ + // Nil req.User + { + description: "Nil req.User - UserExt Not Dirty", + request: openrtb2.BidRequest{}, + requestUserExtWrapper: UserExt{}, + expectedRequest: openrtb2.BidRequest{}, + }, + { + description: "Nil req.User - Dirty UserExt", + request: openrtb2.BidRequest{}, + requestUserExtWrapper: UserExt{consent: &strA, consentDirty: true}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"a"}`)}}, + }, + { + description: "Nil req.User - Dirty UserExt but consent is nil - No Change", + request: openrtb2.BidRequest{}, + requestUserExtWrapper: UserExt{consent: nil, consentDirty: true}, + expectedRequest: openrtb2.BidRequest{}, + }, + // Nil req.User.Ext + { + description: "Nil req.User.Ext - Not Dirty - No Change", + request: openrtb2.BidRequest{User: &openrtb2.User{}}, + requestUserExtWrapper: UserExt{}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{}}, + }, + { + description: "Nil req.User.Ext - Dirty with valid consent string - Expect consent string to be added", + request: openrtb2.BidRequest{User: &openrtb2.User{}}, + requestUserExtWrapper: UserExt{consent: &strB, consentDirty: true}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"b"}`)}}, + }, + { + description: "Nil req.User.Ext - Dirty UserExt with nil consent string- No Change", + request: openrtb2.BidRequest{User: &openrtb2.User{}}, + requestUserExtWrapper: UserExt{consent: nil, consentDirty: true}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{}}, + }, + // Not-nil req.User.Ext + { + description: "Populated - Not Dirty - No Change", + request: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"a"}`)}}, + requestUserExtWrapper: UserExt{}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"a"}`)}}, + }, + { + description: "Populated - Dirty - Consent string overriden", + request: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"a"}`)}}, + requestUserExtWrapper: UserExt{consent: &strB, consentDirty: true}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"b"}`)}}, + }, + { + description: "Populated - Dirty but consent string is equal to the one already set - No Change", + request: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"a"}`)}}, + requestUserExtWrapper: UserExt{consent: &strA, consentDirty: true}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"a"}`)}}, + }, + { + description: "Populated - Dirty UserExt with nil consent string - Cleared", + request: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"a"}`)}}, + requestUserExtWrapper: UserExt{consent: nil, consentDirty: true}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{}}, + }, + }, + }, + { + groupDesc: "Consented provider settings string form", + tests: []testCase{ + // Nil req.User + { + description: "Nil req.User - Dirty UserExt with nil consentedProviderSettings - No Change", + request: openrtb2.BidRequest{}, + requestUserExtWrapper: UserExt{}, + expectedRequest: openrtb2.BidRequest{}, + }, + { + description: "Nil req.User - Dirty UserExt with empty consentedProviderSettings - No Change", + request: openrtb2.BidRequest{}, + requestUserExtWrapper: UserExt{consentedProvidersSettingsIn: &ConsentedProvidersSettingsIn{}, consentedProvidersSettingsInDirty: true}, + expectedRequest: openrtb2.BidRequest{}, + }, + { + description: "Nil req.User - Dirty UserExt with populated consentedProviderSettings - ConsentedProvidersSettings are added", + request: openrtb2.BidRequest{}, + requestUserExtWrapper: UserExt{consentedProvidersSettingsIn: &ConsentedProvidersSettingsIn{ConsentedProvidersString: "ConsentedProvidersString"}, consentedProvidersSettingsInDirty: true}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"ConsentedProvidersSettings":{"consented_providers":"ConsentedProvidersString"}}`)}}, + }, + // Nil req.User.Ext + { + description: "Nil req.User.Ext - Dirty UserExt with nil consentedProviderSettings - No Change", + request: openrtb2.BidRequest{User: &openrtb2.User{}}, + requestUserExtWrapper: UserExt{consentedProvidersSettingsIn: nil, consentedProvidersSettingsInDirty: true}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{}}, + }, + { + description: "Nil req.User.Ext - Dirty UserExt with empty consentedProviderSettings - No Change", + request: openrtb2.BidRequest{User: &openrtb2.User{}}, + requestUserExtWrapper: UserExt{consentedProvidersSettingsIn: &ConsentedProvidersSettingsIn{}, consentedProvidersSettingsInDirty: true}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{}}, + }, + { + description: "Nil req.User.Ext - Dirty UserExt with populated consentedProviderSettings - ConsentedProvidersSettings are added", + request: openrtb2.BidRequest{User: &openrtb2.User{}}, + requestUserExtWrapper: UserExt{consentedProvidersSettingsIn: &ConsentedProvidersSettingsIn{ConsentedProvidersString: "ConsentedProvidersString"}, consentedProvidersSettingsInDirty: true}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"ConsentedProvidersSettings":{"consented_providers":"ConsentedProvidersString"}}`)}}, + }, + // Not-nil req.User.Ext + { + description: "Populated req.User.Ext - Not Dirty UserExt - No Change", + request: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"ConsentedProvidersSettings":{"consented_providers":"1~X.X.X.X"}}`)}}, + requestUserExtWrapper: UserExt{}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"ConsentedProvidersSettings":{"consented_providers":"1~X.X.X.X"}}`)}}, + }, + { + description: "Populated req.User.Ext - Dirty UserExt with nil consentedProviderSettings - Populated req.User.Ext gets overriden with nil User.Ext", + request: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"ConsentedProvidersSettings":{"consented_providers":"1~X.X.X.X"}}`)}}, + requestUserExtWrapper: UserExt{consentedProvidersSettingsIn: nil, consentedProvidersSettingsInDirty: true}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{}}, + }, + { + description: "Populated req.User.Ext - Dirty UserExt with empty consentedProviderSettings - Populated req.User.Ext gets overriden with nil User.Ext", + request: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"1~X.X.X.X":{"consented_providers":"1~X.X.X.X"}}`)}}, + requestUserExtWrapper: UserExt{consentedProvidersSettingsIn: &ConsentedProvidersSettingsIn{}, consentedProvidersSettingsInDirty: true}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{}}, + }, + { + description: "Populated req.User.Ext - Dirty UserExt with populated consentedProviderSettings - ConsentedProvidersSettings are overriden", + request: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"ConsentedProvidersSettings":{"consented_providers":"1~X.X.X.X"}}`)}}, + requestUserExtWrapper: UserExt{consentedProvidersSettingsIn: &ConsentedProvidersSettingsIn{ConsentedProvidersString: "1~1.35.41.101"}, consentedProvidersSettingsInDirty: true}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"ConsentedProvidersSettings":{"consented_providers":"1~1.35.41.101"}}`)}}, + }, + }, + }, + { + groupDesc: "Consented provider settings array", + tests: []testCase{ + // Nil req.User + { + description: "Nil req.User - Dirty UserExt with nil consentedProviderSettings - No Change", + request: openrtb2.BidRequest{}, + requestUserExtWrapper: UserExt{}, + expectedRequest: openrtb2.BidRequest{}, + }, + { + description: "Nil req.User - Dirty UserExt with empty consentedProviderSettings - No Change", + request: openrtb2.BidRequest{}, + requestUserExtWrapper: UserExt{consentedProvidersSettingsOut: &ConsentedProvidersSettingsOut{}, consentedProvidersSettingsOutDirty: true}, + expectedRequest: openrtb2.BidRequest{}, + }, + { + description: "Nil req.User - Dirty UserExt with populated consentedProviderSettings - ConsentedProvidersSettings are added", + request: openrtb2.BidRequest{}, + requestUserExtWrapper: UserExt{consentedProvidersSettingsOut: &ConsentedProvidersSettingsOut{ConsentedProvidersList: []int{1, 35, 41, 101}}, consentedProvidersSettingsOutDirty: true}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consented_providers_settings":{"consented_providers":[1,35,41,101]}}`)}}, + }, + // Nil req.User.Ext + { + description: "Nil req.User.Ext - Dirty UserExt with nil consentedProviderSettings - No Change", + request: openrtb2.BidRequest{User: &openrtb2.User{}}, + requestUserExtWrapper: UserExt{consentedProvidersSettingsOut: nil, consentedProvidersSettingsOutDirty: true}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{}}, + }, + { + description: "Nil req.User.Ext - Dirty UserExt with empty consentedProviderSettings - No Change", + request: openrtb2.BidRequest{User: &openrtb2.User{}}, + requestUserExtWrapper: UserExt{consentedProvidersSettingsOut: &ConsentedProvidersSettingsOut{}, consentedProvidersSettingsOutDirty: true}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{}}, + }, + { + description: "Nil req.User.Ext - Dirty UserExt with populated consentedProviderSettings - ConsentedProvidersSettings are added", + request: openrtb2.BidRequest{User: &openrtb2.User{}}, + requestUserExtWrapper: UserExt{consentedProvidersSettingsOut: &ConsentedProvidersSettingsOut{ConsentedProvidersList: []int{1, 35, 41, 101}}, consentedProvidersSettingsOutDirty: true}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consented_providers_settings":{"consented_providers":[1,35,41,101]}}`)}}, + }, + // Not-nil req.User.Ext + { + description: "Populated req.User.Ext - Not Dirty UserExt - No Change", + request: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consented_providers_settings":{"consented_providers":[1,35,41,101]}}`)}}, + requestUserExtWrapper: UserExt{}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consented_providers_settings":{"consented_providers":[1,35,41,101]}}`)}}, + }, + { + description: "Populated req.User.Ext - Dirty UserExt with nil consentedProviderSettingsOut - Populated req.User.Ext gets overriden with nil User.Ext", + request: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consented_providers_settings":{"consented_providers":[1,35,41,101]}}`)}}, + requestUserExtWrapper: UserExt{consentedProvidersSettingsOut: nil, consentedProvidersSettingsOutDirty: true}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{}}, + }, + { + description: "Populated req.User.Ext - Dirty UserExt with empty consentedProviderSettingsOut - Populated req.User.Ext gets overriden with nil User.Ext", + request: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consented_providers_settings":{"consented_providers":[1,35,41,101]}}`)}}, + requestUserExtWrapper: UserExt{consentedProvidersSettingsOut: &ConsentedProvidersSettingsOut{}, consentedProvidersSettingsOutDirty: true}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{}}, + }, + { + description: "Populated req.User.Ext - Dirty UserExt with populated consentedProviderSettingsOut - consented_providers list elements are overriden", + request: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consented_providers_settings":{"consented_providers":[1,35,41,101]}}`)}}, + requestUserExtWrapper: UserExt{consentedProvidersSettingsOut: &ConsentedProvidersSettingsOut{ConsentedProvidersList: []int{35, 36, 240}}, consentedProvidersSettingsOutDirty: true}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consented_providers_settings":{"consented_providers":[35,36,240]}}`)}}, + }, + }, + }, + } + + for _, group := range testGroups { + for _, test := range group.tests { + // create required filed in the test loop to keep test declaration easier to read + test.requestUserExtWrapper.ext = make(map[string]json.RawMessage) + + w := RequestWrapper{BidRequest: &test.request, userExt: &test.requestUserExtWrapper} + w.RebuildRequest() + assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description) + } + } +} + +func TestUserExtUnmarshal(t *testing.T) { + type testInput struct { + userExt *UserExt + extJson json.RawMessage + } + testCases := []struct { + desc string + in testInput + expectError bool + }{ + { + desc: "UserExt.ext is not nil, don't expect error", + in: testInput{ + userExt: &UserExt{ + ext: map[string]json.RawMessage{ + "eids": json.RawMessage(`[{"source":"value"}]`), + }, + }, + extJson: json.RawMessage(`{"prebid":{"buyeruids":{"elem1":"value1"}}}`), + }, + expectError: false, + }, + { + desc: "UserExt.ext is dirty, don't expect error", + in: testInput{ + userExt: &UserExt{extDirty: true}, + extJson: json.RawMessage(`{"prebid":{"buyeruids":{"elem1":"value1"}}}`), + }, + expectError: false, + }, + // Eids + { + desc: "Has eids and it is valid JSON", + in: testInput{ + userExt: &UserExt{}, + extJson: json.RawMessage(`{"eids":[{"source":"value"}]}`), + }, + expectError: false, + }, + { + desc: "Has malformed eids expect error", + in: testInput{ + userExt: &UserExt{}, + extJson: json.RawMessage(`{"eids":123}`), + }, + expectError: true, + }, + // prebid + { + desc: "Has prebid and it is valid JSON", + in: testInput{ + userExt: &UserExt{}, + extJson: json.RawMessage(`{"prebid":{"buyeruids":{"elem1":"value1"}}}`), + }, + expectError: false, + }, + { + desc: "Has malformed prebid expect error", + in: testInput{ + userExt: &UserExt{}, + extJson: json.RawMessage(`{"prebid":{"buyeruids":123}}`), + }, + expectError: true, + }, + // ConsentedProvidersSettings + { + desc: "Has ConsentedProvidersSettings and it is valid JSON", + in: testInput{ + userExt: &UserExt{}, + extJson: json.RawMessage(`{"ConsentedProvidersSettings":{"consented_providers":"ConsentedProvidersString"}}`), + }, + expectError: false, + }, + { + desc: "Has malformed ConsentedProvidersSettings expect error", + in: testInput{ + userExt: &UserExt{}, + extJson: json.RawMessage(`{"ConsentedProvidersSettings":{"consented_providers":123}}`), + }, + expectError: true, + }, + // consented_providers_settings + { + desc: "Has consented_providers_settings and it is valid JSON", + in: testInput{ + userExt: &UserExt{}, + extJson: json.RawMessage(`{"consented_providers_settings":{"consented_providers":[2,25]}}`), + }, + expectError: false, + }, + { + desc: "Has malformed consented_providers_settings expect error", + in: testInput{ + userExt: &UserExt{}, + extJson: json.RawMessage(`{"consented_providers_settings":{"consented_providers":123}}`), + }, + expectError: true, + }, + } + for _, tc := range testCases { + err := tc.in.userExt.unmarshal(tc.in.extJson) + + if tc.expectError { + assert.Error(t, err, tc.desc) + } else { + assert.NoError(t, err, tc.desc) + } + } +} + +func TestRebuildDeviceExt(t *testing.T) { + prebidContent1 := ExtDevicePrebid{Interstitial: &ExtDeviceInt{MinWidthPerc: 1}} + prebidContent2 := ExtDevicePrebid{Interstitial: &ExtDeviceInt{MinWidthPerc: 2}} + + testCases := []struct { + description string + request openrtb2.BidRequest + requestDeviceExtWrapper DeviceExt + expectedRequest openrtb2.BidRequest + }{ + { + description: "Nil - Not Dirty", + request: openrtb2.BidRequest{}, + requestDeviceExtWrapper: DeviceExt{}, + expectedRequest: openrtb2.BidRequest{}, + }, + { + description: "Nil - Dirty", + request: openrtb2.BidRequest{}, + requestDeviceExtWrapper: DeviceExt{prebid: &prebidContent1, prebidDirty: true}, + expectedRequest: openrtb2.BidRequest{Device: &openrtb2.Device{Ext: json.RawMessage(`{"prebid":{"interstitial":{"minwidthperc":1,"minheightperc":0}}}`)}}, + }, + { + description: "Nil - Dirty - No Change", + request: openrtb2.BidRequest{}, + requestDeviceExtWrapper: DeviceExt{prebid: nil, prebidDirty: true}, + expectedRequest: openrtb2.BidRequest{}, + }, + { + description: "Empty - Not Dirty", + request: openrtb2.BidRequest{Device: &openrtb2.Device{}}, + requestDeviceExtWrapper: DeviceExt{}, + expectedRequest: openrtb2.BidRequest{Device: &openrtb2.Device{}}, + }, + { + description: "Empty - Dirty", + request: openrtb2.BidRequest{Device: &openrtb2.Device{}}, + requestDeviceExtWrapper: DeviceExt{prebid: &prebidContent1, prebidDirty: true}, + expectedRequest: openrtb2.BidRequest{Device: &openrtb2.Device{Ext: json.RawMessage(`{"prebid":{"interstitial":{"minwidthperc":1,"minheightperc":0}}}`)}}, + }, + { + description: "Empty - Dirty - No Change", + request: openrtb2.BidRequest{Device: &openrtb2.Device{}}, + requestDeviceExtWrapper: DeviceExt{prebid: nil, prebidDirty: true}, + expectedRequest: openrtb2.BidRequest{Device: &openrtb2.Device{}}, + }, + { + description: "Populated - Not Dirty", + request: openrtb2.BidRequest{Device: &openrtb2.Device{Ext: json.RawMessage(`{"prebid":{"interstitial":{"minwidthperc":1,"minheightperc":0}}}`)}}, + requestDeviceExtWrapper: DeviceExt{}, + expectedRequest: openrtb2.BidRequest{Device: &openrtb2.Device{Ext: json.RawMessage(`{"prebid":{"interstitial":{"minwidthperc":1,"minheightperc":0}}}`)}}, + }, + { + description: "Populated - Dirty", + request: openrtb2.BidRequest{Device: &openrtb2.Device{Ext: json.RawMessage(`{"prebid":{"interstitial":{"minwidthperc":1,"minheightperc":0}}}`)}}, + requestDeviceExtWrapper: DeviceExt{prebid: &prebidContent2, prebidDirty: true}, + expectedRequest: openrtb2.BidRequest{Device: &openrtb2.Device{Ext: json.RawMessage(`{"prebid":{"interstitial":{"minwidthperc":2,"minheightperc":0}}}`)}}, + }, + { + description: "Populated - Dirty - No Change", + request: openrtb2.BidRequest{Device: &openrtb2.Device{Ext: json.RawMessage(`{"prebid":{"interstitial":{"minwidthperc":1,"minheightperc":0}}}`)}}, + requestDeviceExtWrapper: DeviceExt{prebid: &prebidContent1, prebidDirty: true}, + expectedRequest: openrtb2.BidRequest{Device: &openrtb2.Device{Ext: json.RawMessage(`{"prebid":{"interstitial":{"minwidthperc":1,"minheightperc":0}}}`)}}, + }, + { + description: "Populated - Dirty - Cleared", + request: openrtb2.BidRequest{Device: &openrtb2.Device{Ext: json.RawMessage(`{"prebid":{"interstitial":{"minwidthperc":1,"minheightperc":0}}}`)}}, + requestDeviceExtWrapper: DeviceExt{prebid: nil, prebidDirty: true}, + expectedRequest: openrtb2.BidRequest{Device: &openrtb2.Device{}}, + }, + } + + for _, test := range testCases { + // create required filed in the test loop to keep test declaration easier to read + test.requestDeviceExtWrapper.ext = make(map[string]json.RawMessage) + + w := RequestWrapper{BidRequest: &test.request, deviceExt: &test.requestDeviceExtWrapper} + w.RebuildRequest() + assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description) + } +} + +func TestRebuildRequestExt(t *testing.T) { + prebidContent1 := ExtRequestPrebid{Integration: "1"} + prebidContent2 := ExtRequestPrebid{Integration: "2"} + + testCases := []struct { + description string + request openrtb2.BidRequest + requestRequestExtWrapper RequestExt + expectedRequest openrtb2.BidRequest + }{ + { + description: "Empty - Not Dirty", + request: openrtb2.BidRequest{}, + requestRequestExtWrapper: RequestExt{}, + expectedRequest: openrtb2.BidRequest{}, + }, + { + description: "Empty - Dirty", + request: openrtb2.BidRequest{}, + requestRequestExtWrapper: RequestExt{prebid: &prebidContent1, prebidDirty: true}, + expectedRequest: openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"integration":"1"}}`)}, + }, + { + description: "Empty - Dirty - No Change", + request: openrtb2.BidRequest{}, + requestRequestExtWrapper: RequestExt{prebid: nil, prebidDirty: true}, + expectedRequest: openrtb2.BidRequest{}, + }, + { + description: "Populated - Not Dirty", + request: openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"integration":"1"}}`)}, + requestRequestExtWrapper: RequestExt{}, + expectedRequest: openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"integration":"1"}}`)}, + }, + { + description: "Populated - Dirty", + request: openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"integration":"1"}}`)}, + requestRequestExtWrapper: RequestExt{prebid: &prebidContent2, prebidDirty: true}, + expectedRequest: openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"integration":"2"}}`)}, + }, + { + description: "Populated - Dirty - No Change", + request: openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"integration":"1"}}`)}, + requestRequestExtWrapper: RequestExt{prebid: &prebidContent1, prebidDirty: true}, + expectedRequest: openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"integration":"1"}}`)}, + }, + { + description: "Populated - Dirty - Cleared", + request: openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"integration":"1"}}`)}, + requestRequestExtWrapper: RequestExt{prebid: nil, prebidDirty: true}, + expectedRequest: openrtb2.BidRequest{}, + }, + } + + for _, test := range testCases { + // create required filed in the test loop to keep test declaration easier to read + test.requestRequestExtWrapper.ext = make(map[string]json.RawMessage) + + w := RequestWrapper{BidRequest: &test.request, requestExt: &test.requestRequestExtWrapper} + w.RebuildRequest() + assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description) + } +} + +func TestRebuildAppExt(t *testing.T) { + prebidContent1 := ExtAppPrebid{Source: "1"} + prebidContent2 := ExtAppPrebid{Source: "2"} + + testCases := []struct { + description string + request openrtb2.BidRequest + requestAppExtWrapper AppExt + expectedRequest openrtb2.BidRequest + }{ + { + description: "Nil - Not Dirty", + request: openrtb2.BidRequest{}, + requestAppExtWrapper: AppExt{}, + expectedRequest: openrtb2.BidRequest{}, + }, + { + description: "Nil - Dirty", + request: openrtb2.BidRequest{}, + requestAppExtWrapper: AppExt{prebid: &prebidContent1, prebidDirty: true}, + expectedRequest: openrtb2.BidRequest{App: &openrtb2.App{Ext: json.RawMessage(`{"prebid":{"source":"1"}}`)}}, + }, + { + description: "Nil - Dirty - No Change", + request: openrtb2.BidRequest{}, + requestAppExtWrapper: AppExt{prebid: nil, prebidDirty: true}, + expectedRequest: openrtb2.BidRequest{}, + }, + { + description: "Empty - Not Dirty", + request: openrtb2.BidRequest{App: &openrtb2.App{}}, + requestAppExtWrapper: AppExt{}, + expectedRequest: openrtb2.BidRequest{App: &openrtb2.App{}}, + }, + { + description: "Empty - Dirty", + request: openrtb2.BidRequest{App: &openrtb2.App{}}, + requestAppExtWrapper: AppExt{prebid: &prebidContent1, prebidDirty: true}, + expectedRequest: openrtb2.BidRequest{App: &openrtb2.App{Ext: json.RawMessage(`{"prebid":{"source":"1"}}`)}}, + }, + { + description: "Empty - Dirty - No Change", + request: openrtb2.BidRequest{App: &openrtb2.App{}}, + requestAppExtWrapper: AppExt{prebid: nil, prebidDirty: true}, + expectedRequest: openrtb2.BidRequest{App: &openrtb2.App{}}, + }, + { + description: "Populated - Not Dirty", + request: openrtb2.BidRequest{App: &openrtb2.App{Ext: json.RawMessage(`{"prebid":{"source":"1"}}`)}}, + requestAppExtWrapper: AppExt{}, + expectedRequest: openrtb2.BidRequest{App: &openrtb2.App{Ext: json.RawMessage(`{"prebid":{"source":"1"}}`)}}, + }, + { + description: "Populated - Dirty", + request: openrtb2.BidRequest{App: &openrtb2.App{Ext: json.RawMessage(`{"prebid":{"source":"1"}}`)}}, + requestAppExtWrapper: AppExt{prebid: &prebidContent2, prebidDirty: true}, + expectedRequest: openrtb2.BidRequest{App: &openrtb2.App{Ext: json.RawMessage(`{"prebid":{"source":"2"}}`)}}, + }, + { + description: "Populated - Dirty - No Change", + request: openrtb2.BidRequest{App: &openrtb2.App{Ext: json.RawMessage(`{"prebid":{"source":"1"}}`)}}, + requestAppExtWrapper: AppExt{prebid: &prebidContent1, prebidDirty: true}, + expectedRequest: openrtb2.BidRequest{App: &openrtb2.App{Ext: json.RawMessage(`{"prebid":{"source":"1"}}`)}}, + }, + { + description: "Populated - Dirty - Cleared", + request: openrtb2.BidRequest{App: &openrtb2.App{Ext: json.RawMessage(`{"prebid":{"source":"1"}}`)}}, + requestAppExtWrapper: AppExt{prebid: nil, prebidDirty: true}, + expectedRequest: openrtb2.BidRequest{App: &openrtb2.App{}}, + }, + } + + for _, test := range testCases { + // create required filed in the test loop to keep test declaration easier to read + test.requestAppExtWrapper.ext = make(map[string]json.RawMessage) + + w := RequestWrapper{BidRequest: &test.request, appExt: &test.requestAppExtWrapper} + w.RebuildRequest() + assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description) + } +} + +func TestRebuildSiteExt(t *testing.T) { + int1 := int8(1) + int2 := int8(2) + + testCases := []struct { + description string + request openrtb2.BidRequest + requestSiteExtWrapper SiteExt + expectedRequest openrtb2.BidRequest + }{ + { + description: "Nil - Not Dirty", + request: openrtb2.BidRequest{}, + requestSiteExtWrapper: SiteExt{}, + expectedRequest: openrtb2.BidRequest{}, + }, + { + description: "Nil - Dirty", + request: openrtb2.BidRequest{}, + requestSiteExtWrapper: SiteExt{amp: &int1, ampDirty: true}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)}}, + }, + { + description: "Nil - Dirty - No Change", + request: openrtb2.BidRequest{}, + requestSiteExtWrapper: SiteExt{amp: nil, ampDirty: true}, + expectedRequest: openrtb2.BidRequest{}, + }, + { + description: "Empty - Not Dirty", + request: openrtb2.BidRequest{Site: &openrtb2.Site{}}, + requestSiteExtWrapper: SiteExt{}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{}}, + }, + { + description: "Empty - Dirty", + request: openrtb2.BidRequest{Site: &openrtb2.Site{}}, + requestSiteExtWrapper: SiteExt{amp: &int1, ampDirty: true}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)}}, + }, + { + description: "Empty - Dirty - No Change", + request: openrtb2.BidRequest{Site: &openrtb2.Site{}}, + requestSiteExtWrapper: SiteExt{amp: nil, ampDirty: true}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{}}, + }, + { + description: "Populated - Not Dirty", + request: openrtb2.BidRequest{Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)}}, + requestSiteExtWrapper: SiteExt{}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)}}, + }, + { + description: "Populated - Dirty", + request: openrtb2.BidRequest{Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)}}, + requestSiteExtWrapper: SiteExt{amp: &int2, ampDirty: true}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":2}`)}}, + }, + { + description: "Populated - Dirty - No Change", + request: openrtb2.BidRequest{Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)}}, + requestSiteExtWrapper: SiteExt{amp: &int1, ampDirty: true}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)}}, + }, + { + description: "Populated - Dirty - Cleared", + request: openrtb2.BidRequest{Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)}}, + requestSiteExtWrapper: SiteExt{amp: nil, ampDirty: true}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{}}, + }, + } + + for _, test := range testCases { + // create required filed in the test loop to keep test declaration easier to read + test.requestSiteExtWrapper.ext = make(map[string]json.RawMessage) + + w := RequestWrapper{BidRequest: &test.request, siteExt: &test.requestSiteExtWrapper} + w.RebuildRequest() + assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description) + } +} + +func TestRebuildSourceExt(t *testing.T) { + schainContent1 := openrtb2.SupplyChain{Ver: "1"} + schainContent2 := openrtb2.SupplyChain{Ver: "2"} + + testCases := []struct { + description string + request openrtb2.BidRequest + requestSourceExtWrapper SourceExt + expectedRequest openrtb2.BidRequest + }{ + { + description: "Nil - Not Dirty", + request: openrtb2.BidRequest{}, + requestSourceExtWrapper: SourceExt{}, + expectedRequest: openrtb2.BidRequest{}, + }, + { + description: "Nil - Dirty", + request: openrtb2.BidRequest{}, + requestSourceExtWrapper: SourceExt{schain: &schainContent1, schainDirty: true}, + expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: json.RawMessage(`{"schain":{"complete":0,"nodes":null,"ver":"1"}}`)}}, + }, + { + description: "Nil - Dirty - No Change", + request: openrtb2.BidRequest{}, + requestSourceExtWrapper: SourceExt{schain: nil, schainDirty: true}, + expectedRequest: openrtb2.BidRequest{}, + }, + { + description: "Empty - Not Dirty", + request: openrtb2.BidRequest{Source: &openrtb2.Source{}}, + requestSourceExtWrapper: SourceExt{}, + expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{}}, + }, + { + description: "Empty - Dirty", + request: openrtb2.BidRequest{Source: &openrtb2.Source{}}, + requestSourceExtWrapper: SourceExt{schain: &schainContent1, schainDirty: true}, + expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: json.RawMessage(`{"schain":{"complete":0,"nodes":null,"ver":"1"}}`)}}, + }, + { + description: "Empty - Dirty - No Change", + request: openrtb2.BidRequest{Source: &openrtb2.Source{}}, + requestSourceExtWrapper: SourceExt{schain: nil, schainDirty: true}, + expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{}}, + }, + { + description: "Populated - Not Dirty", + request: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: json.RawMessage(`{"schain":{"complete":0,"nodes":null,"ver":"1"}}`)}}, + requestSourceExtWrapper: SourceExt{}, + expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: json.RawMessage(`{"schain":{"complete":0,"nodes":null,"ver":"1"}}`)}}, + }, + { + description: "Populated - Dirty", + request: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: json.RawMessage(`{"schain":{"complete":0,"nodes":null,"ver":"1"}}`)}}, + requestSourceExtWrapper: SourceExt{schain: &schainContent2, schainDirty: true}, + expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: json.RawMessage(`{"schain":{"complete":0,"nodes":null,"ver":"2"}}`)}}, + }, + { + description: "Populated - Dirty - No Change", + request: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: json.RawMessage(`{"schain":{"complete":0,"nodes":null,"ver":"1"}}`)}}, + requestSourceExtWrapper: SourceExt{schain: &schainContent1, schainDirty: true}, + expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: json.RawMessage(`{"schain":{"complete":0,"nodes":null,"ver":"1"}}`)}}, + }, + { + description: "Populated - Dirty - Cleared", + request: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: json.RawMessage(`{"schain":{"complete":0,"nodes":null,"ver":"1"}}`)}}, + requestSourceExtWrapper: SourceExt{schain: nil, schainDirty: true}, + expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{}}, + }, + } + + for _, test := range testCases { + // create required filed in the test loop to keep test declaration easier to read + test.requestSourceExtWrapper.ext = make(map[string]json.RawMessage) + + w := RequestWrapper{BidRequest: &test.request, sourceExt: &test.requestSourceExtWrapper} + w.RebuildRequest() + assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description) + } +} + +func TestImpWrapperRebuildImp(t *testing.T) { + var ( + isRewardedInventoryOne int8 = 1 + isRewardedInventoryTwo int8 = 2 + ) + + testCases := []struct { + description string + imp openrtb2.Imp + impExtWrapper ImpExt + expectedImp openrtb2.Imp + }{ + { + description: "Empty - Not Dirty", + imp: openrtb2.Imp{}, + impExtWrapper: ImpExt{}, + expectedImp: openrtb2.Imp{}, + }, + { + description: "Empty - Dirty", + imp: openrtb2.Imp{}, + impExtWrapper: ImpExt{prebid: &ExtImpPrebid{IsRewardedInventory: &isRewardedInventoryOne}, prebidDirty: true}, + expectedImp: openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{"is_rewarded_inventory":1}}`)}, + }, + { + description: "Empty - Dirty - No Change", + imp: openrtb2.Imp{}, + impExtWrapper: ImpExt{prebid: nil, prebidDirty: true}, + expectedImp: openrtb2.Imp{}, + }, + { + description: "Populated - Not Dirty", + imp: openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{"is_rewarded_inventory":1}}`)}, + impExtWrapper: ImpExt{}, + expectedImp: openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{"is_rewarded_inventory":1}}`)}, + }, + { + description: "Populated - Dirty", + imp: openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{"is_rewarded_inventory":1}}`)}, + impExtWrapper: ImpExt{prebid: &ExtImpPrebid{IsRewardedInventory: &isRewardedInventoryTwo}, prebidDirty: true}, + expectedImp: openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{"is_rewarded_inventory":2}}`)}, + }, + { + description: "Populated - Dirty - No Change", + imp: openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{"is_rewarded_inventory":1}}`)}, + impExtWrapper: ImpExt{prebid: &ExtImpPrebid{IsRewardedInventory: &isRewardedInventoryOne}, prebidDirty: true}, + expectedImp: openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{"is_rewarded_inventory":1}}`)}, + }, + { + description: "Populated - Dirty - Cleared", + imp: openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{"is_rewarded_inventory":1}}`)}, + impExtWrapper: ImpExt{prebid: nil, prebidDirty: true}, + expectedImp: openrtb2.Imp{}, + }, + { + description: "Populated - Dirty - Empty Prebid Object", + imp: openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{"is_rewarded_inventory":1}}`)}, + impExtWrapper: ImpExt{prebid: &ExtImpPrebid{IsRewardedInventory: nil}, prebidDirty: true}, + expectedImp: openrtb2.Imp{}, + }, + } + + for _, test := range testCases { + // create required filed in the test loop to keep test declaration easier to read + test.impExtWrapper.ext = make(map[string]json.RawMessage) + + w := &ImpWrapper{Imp: &test.imp, impExt: &test.impExtWrapper} + w.RebuildImp() + assert.Equal(t, test.expectedImp, *w.Imp, test.description) + } +} + +func TestImpWrapperGetImpExt(t *testing.T) { + var isRewardedInventoryOne int8 = 1 + + testCases := []struct { + description string + givenWrapper ImpWrapper + expectedImpExt ImpExt + expectedError string + }{ + { + description: "Empty", + givenWrapper: ImpWrapper{}, + expectedImpExt: ImpExt{ext: make(map[string]json.RawMessage)}, + }, + { + description: "Populated - Ext", + givenWrapper: ImpWrapper{Imp: &openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{"is_rewarded_inventory":1},"other":42}`)}}, + expectedImpExt: ImpExt{ + ext: map[string]json.RawMessage{ + "prebid": json.RawMessage(`{"is_rewarded_inventory":1}`), + "other": json.RawMessage(`42`), + }, + prebid: &ExtImpPrebid{IsRewardedInventory: &isRewardedInventoryOne}, + }, + }, + { + description: "Already Retrieved - Dirty - Not Unmarshalled Again", + givenWrapper: ImpWrapper{ + Imp: &openrtb2.Imp{Ext: json.RawMessage(`{"will":"be ignored"}`)}, + impExt: &ImpExt{ext: map[string]json.RawMessage{"foo": json.RawMessage("bar")}}}, + expectedImpExt: ImpExt{ext: map[string]json.RawMessage{"foo": json.RawMessage("bar")}}, + }, + { + description: "Error - Ext", + givenWrapper: ImpWrapper{Imp: &openrtb2.Imp{Ext: json.RawMessage(`malformed`)}}, + expectedError: "invalid character 'm' looking for beginning of value", + }, + { + description: "Error - Ext - Prebid", + givenWrapper: ImpWrapper{Imp: &openrtb2.Imp{Ext: json.RawMessage(`{"prebid":malformed}`)}}, + expectedError: "invalid character 'm' looking for beginning of value", + }, + } + + for _, test := range testCases { + impExt, err := test.givenWrapper.GetImpExt() + if test.expectedError != "" { + assert.EqualError(t, err, test.expectedError, test.description) + } else { + assert.NoError(t, err, test.description) + assert.Equal(t, test.expectedImpExt, *impExt, test.description) + } + } +} diff --git a/openrtb_ext/response.go b/openrtb_ext/response.go index 044bc0bd60b..b20e741c18c 100644 --- a/openrtb_ext/response.go +++ b/openrtb_ext/response.go @@ -39,7 +39,8 @@ type ExtResponseSyncData struct { // ExtResponsePrebid defines the contract for bidresponse.ext.prebid type ExtResponsePrebid struct { - AuctionTimestamp int64 `json:"auctiontimestamp,omitempty"` + AuctionTimestamp int64 `json:"auctiontimestamp,omitempty"` + Passthrough json.RawMessage `json:"passthrough,omitempty"` } // ExtUserSync defines the contract for bidresponse.ext.usersync.{bidder}.syncs[i] diff --git a/openrtb_ext/source.go b/openrtb_ext/source.go index 70b291b11fc..2c983f0b214 100644 --- a/openrtb_ext/source.go +++ b/openrtb_ext/source.go @@ -1,6 +1,8 @@ package openrtb_ext +import "github.com/prebid/openrtb/v17/openrtb2" + // ExtSource defines the contract for bidrequest.source.ext type ExtSource struct { - SChain *ExtRequestPrebidSChainSChain `json:"schain"` + SChain *openrtb2.SupplyChain `json:"schain"` } diff --git a/openrtb_ext/user.go b/openrtb_ext/user.go index d5e6ae678cc..0f2f2b03188 100644 --- a/openrtb_ext/user.go +++ b/openrtb_ext/user.go @@ -1,6 +1,11 @@ package openrtb_ext -import "encoding/json" +import ( + "strconv" + "strings" + + "github.com/prebid/openrtb/v17/openrtb2" +) // ExtUser defines the contract for bidrequest.user.ext type ExtUser struct { @@ -8,9 +13,13 @@ type ExtUser struct { // https://iabtechlab.com/wp-content/uploads/2018/02/OpenRTB_Advisory_GDPR_2018-02.pdf Consent string `json:"consent,omitempty"` + ConsentedProvidersSettings *ConsentedProvidersSettingsIn `json:"ConsentedProvidersSettings,omitempty"` + + ConsentedProvidersSettingsParsed *ConsentedProvidersSettingsOut `json:"consented_providers_settings,omitempty"` + Prebid *ExtUserPrebid `json:"prebid,omitempty"` - Eids []ExtUserEid `json:"eids,omitempty"` + Eids []openrtb2.EID `json:"eids,omitempty"` } // ExtUserPrebid defines the contract for bidrequest.user.ext.prebid @@ -18,19 +27,36 @@ type ExtUserPrebid struct { BuyerUIDs map[string]string `json:"buyeruids,omitempty"` } -// 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. -type ExtUserEid struct { - Source string `json:"source"` - ID string `json:"id,omitempty"` - Uids []ExtUserEidUid `json:"uids,omitempty"` - Ext json.RawMessage `json:"ext,omitempty"` +type ConsentedProvidersSettingsIn struct { + ConsentedProvidersString string `json:"consented_providers,omitempty"` +} + +type ConsentedProvidersSettingsOut struct { + ConsentedProvidersList []int `json:"consented_providers,omitempty"` } -// ExtUserEidUid defines the contract for bidrequest.user.ext.eids[i].uids[j] -type ExtUserEidUid struct { - ID string `json:"id"` - Atype int `json:"atype,omitempty"` - Ext json.RawMessage `json:"ext,omitempty"` +// ParseConsentedProvidersString takes a string formatted as Google's Additional Consent format and returns a list with its +// elements. For instance, the following string "1~1.35.41.101" would result in []int{1, 35, 41, 101} +func ParseConsentedProvidersString(cps string) []int { + // Additional Consent format version is separated from elements by the '~' character + parts := strings.Split(cps, "~") + if len(parts) != 2 { + return nil + } + + // Split the individual elements + providerStringList := strings.Split(parts[1], ".") + if len(providerStringList) == 0 { + return nil + } + + // Convert to ints and add to int array + var consentedProviders []int + for _, providerStr := range providerStringList { + if providerInt, err := strconv.Atoi(providerStr); err == nil { + consentedProviders = append(consentedProviders, providerInt) + } + } + + return consentedProviders } diff --git a/pbs/usersync.go b/pbs/usersync.go index d5043b8c13f..748581af759 100644 --- a/pbs/usersync.go +++ b/pbs/usersync.go @@ -32,6 +32,7 @@ type googleResponse struct { func (deps *UserSyncDeps) VerifyRecaptcha(response string) error { ts := &http.Transport{ + Proxy: http.ProxyFromEnvironment, TLSClientConfig: &tls.Config{RootCAs: ssl.GetRootCAPool()}, } diff --git a/prebid_cache_client/client.go b/prebid_cache_client/client.go index a24a139ea1d..872420001ea 100644 --- a/prebid_cache_client/client.go +++ b/prebid_cache_client/client.go @@ -6,7 +6,7 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" + "io" "net/http" "strconv" "strings" @@ -118,7 +118,7 @@ func (c *clientImpl) PutJson(ctx context.Context, values []Cacheable) (uuids []s defer anResp.Body.Close() c.metrics.RecordPrebidCacheRequestTime(true, elapsedTime) - responseBody, err := ioutil.ReadAll(anResp.Body) + responseBody, err := io.ReadAll(anResp.Body) if anResp.StatusCode != 200 { logError(&errs, "Prebid Cache call to %s returned %d: %s", c.putUrl, anResp.StatusCode, responseBody) return uuidsToReturn, errs diff --git a/privacy/ccpa/consentwriter.go b/privacy/ccpa/consentwriter.go index 451f2b40238..5ed1ddb0246 100644 --- a/privacy/ccpa/consentwriter.go +++ b/privacy/ccpa/consentwriter.go @@ -1,7 +1,7 @@ package ccpa import ( - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" ) @@ -17,10 +17,15 @@ func (c ConsentWriter) Write(req *openrtb2.BidRequest) error { return nil } reqWrap := &openrtb_ext.RequestWrapper{BidRequest: req} - if regsExt, err := reqWrap.GetRegExt(); err == nil { - regsExt.SetUSPrivacy(c.Consent) - } else { - return err + + // Set consent string in USPrivacy + if c.Consent != "" { + if regsExt, err := reqWrap.GetRegExt(); err == nil { + regsExt.SetUSPrivacy(c.Consent) + } else { + return err + } } + return reqWrap.RebuildRequest() } diff --git a/privacy/ccpa/consentwriter_test.go b/privacy/ccpa/consentwriter_test.go index 28dfd41785e..072f17ed2af 100644 --- a/privacy/ccpa/consentwriter_test.go +++ b/privacy/ccpa/consentwriter_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/privacy/ccpa/policy.go b/privacy/ccpa/policy.go index 39322317df5..6576e2b7984 100644 --- a/privacy/ccpa/policy.go +++ b/privacy/ccpa/policy.go @@ -3,7 +3,9 @@ package ccpa import ( "fmt" - "github.com/mxmCherry/openrtb/v15/openrtb2" + gpplib "github.com/prebid/go-gpp" + gppConstants "github.com/prebid/go-gpp/constants" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" ) @@ -14,21 +16,34 @@ type Policy struct { } // ReadFromRequestWrapper extracts the CCPA regulatory information from an OpenRTB bid request. -func ReadFromRequestWrapper(req *openrtb_ext.RequestWrapper) (Policy, error) { +func ReadFromRequestWrapper(req *openrtb_ext.RequestWrapper, gpp gpplib.GppContainer) (Policy, error) { var consent string var noSaleBidders []string - - if req == nil { - return Policy{}, nil + if req.BidRequest != nil && req.BidRequest.Regs != nil { + for _, s := range req.BidRequest.Regs.GPPSID { + if s == int8(gppConstants.SectionUSPV1) { + for i, id := range gpp.SectionTypes { + if id == gppConstants.SectionUSPV1 { + consent = gpp.Sections[i].GetValue() + } + } + } + } } - // Read consent from request.regs.ext - regsExt, err := req.GetRegExt() - if err != nil { - return Policy{}, fmt.Errorf("error reading request.regs.ext: %s", err) - } - if regsExt != nil { - consent = regsExt.GetUSPrivacy() + if consent == "" { + if req == nil { + return Policy{}, nil + } + + // Read consent from request.regs.ext + 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 reqExt, err := req.GetRequestExt() @@ -44,7 +59,12 @@ func ReadFromRequestWrapper(req *openrtb_ext.RequestWrapper) (Policy, error) { } func ReadFromRequest(req *openrtb2.BidRequest) (Policy, error) { - return ReadFromRequestWrapper(&openrtb_ext.RequestWrapper{BidRequest: req}) + var gpp gpplib.GppContainer + if req != nil && req.Regs != nil && len(req.Regs.GPP) > 0 { + gpp, _ = gpplib.Parse(req.Regs.GPP) + } + + return ReadFromRequestWrapper(&openrtb_ext.RequestWrapper{BidRequest: req}, gpp) } // Write mutates an OpenRTB bid request with the CCPA regulatory information. diff --git a/privacy/ccpa/policy_test.go b/privacy/ccpa/policy_test.go index ca6d0f8acf2..1e87305ea92 100644 --- a/privacy/ccpa/policy_test.go +++ b/privacy/ccpa/policy_test.go @@ -4,15 +4,18 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" + gpplib "github.com/prebid/go-gpp" + gppConstants "github.com/prebid/go-gpp/constants" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) -func TestReadFromRequest(t *testing.T) { +func TestReadFromRequestWrapper(t *testing.T) { testCases := []struct { description string request *openrtb2.BidRequest + giveGPP gpplib.GppContainer expectedPolicy Policy expectedError bool }{ @@ -153,13 +156,153 @@ func TestReadFromRequest(t *testing.T) { Consent: "1YYY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"", }, }, + { + description: "GPP Success", + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{GPP: "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~present", + GPPSID: []int8{6}}, + Ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), + }, + giveGPP: gpplib.GppContainer{Version: 1, SectionTypes: []gppConstants.SectionID{6}, Sections: []gpplib.Section{&upsv1Section}}, + expectedPolicy: Policy{ + Consent: "present", + NoSaleBidders: []string{"a", "b"}, + }, + }, + { + description: "GPP Success, has Regs.ext", + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{GPP: "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~present", + GPPSID: []int8{6}, + Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + Ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), + }, + giveGPP: gpplib.GppContainer{Version: 1, SectionTypes: []gppConstants.SectionID{6}, Sections: []gpplib.Section{&upsv1Section}}, + expectedPolicy: Policy{ + Consent: "present", + NoSaleBidders: []string{"a", "b"}, + }, + }, + { + description: "GPP Success, no USPV1", + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{GPP: "DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", + GPPSID: []int8{6}}, + }, + giveGPP: gpplib.GppContainer{Version: 1, SectionTypes: []gppConstants.SectionID{2}, Sections: []gpplib.Section{&tcf1Section}}, + expectedPolicy: Policy{ + Consent: "", + }, + }, } for _, test := range testCases { - 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) + t.Run(test.description, func(t *testing.T) { + reqWrapper := &openrtb_ext.RequestWrapper{BidRequest: test.request} + result, err := ReadFromRequestWrapper(reqWrapper, test.giveGPP) + assertError(t, test.expectedError, err, test.description) + assert.Equal(t, test.expectedPolicy, result) + }) + } +} + +func TestReadFromRequest(t *testing.T) { + testCases := []struct { + description string + request *openrtb2.BidRequest + expectedPolicy Policy + expectedError bool + }{ + { + description: "Success", + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + Ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), + }, + expectedPolicy: Policy{ + Consent: "ABC", + NoSaleBidders: []string{"a", "b"}, + }, + }, + { + description: "Nil Request", + request: nil, + expectedPolicy: Policy{ + Consent: "", + NoSaleBidders: nil, + }, + }, + { + description: "Nil Regs", + request: &openrtb2.BidRequest{ + Regs: nil, + Ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), + }, + expectedPolicy: Policy{ + Consent: "", + NoSaleBidders: []string{"a", "b"}, + }, + }, + { + description: "GPP Success", + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{GPP: "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + GPPSID: []int8{6}}, + Ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), + }, + expectedPolicy: Policy{ + Consent: "1YNN", + NoSaleBidders: []string{"a", "b"}, + }, + }, + { + description: "GPP Success, has Regs.ext", + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{GPP: "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + GPPSID: []int8{6}, + Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + Ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), + }, + expectedPolicy: Policy{ + Consent: "1YNN", + NoSaleBidders: []string{"a", "b"}, + }, + }, + { + description: "GPP Success, no USPV1", + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{GPP: "DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", + GPPSID: []int8{6}}}, + expectedPolicy: Policy{ + Consent: "", + }, + }, + { + description: "GPP Success, no signal", + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{GPP: "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + GPPSID: []int8{}}}, + expectedPolicy: Policy{ + Consent: "", + }, + }, + { + description: "GPP Success, wrong signal", + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{GPP: "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + GPPSID: []int8{2}}}, + expectedPolicy: Policy{ + Consent: "", + }, + }, + } + + for _, test := range testCases { + t.Run(test.description, func(t *testing.T) { + result, err := ReadFromRequest(test.request) + assertError(t, test.expectedError, err, test.description) + assert.Equal(t, test.expectedPolicy, result) + }) } } @@ -298,7 +441,7 @@ func TestBuildRegsClear(t *testing.T) { { description: "Nil Regs", regs: nil, - expected: &openrtb2.Regs{Ext: nil}, + expected: nil, }, { description: "Nil Regs.Ext", @@ -654,3 +797,19 @@ func assertError(t *testing.T, expectError bool, err error, description string) assert.NoError(t, err, description) } } + +var upsv1Section mockGPPSection = mockGPPSection{sectionID: 6, value: "present"} +var tcf1Section mockGPPSection = mockGPPSection{sectionID: 2, value: "BOS2bx5OS2bx5ABABBAAABoAAAAAFA"} + +type mockGPPSection struct { + sectionID gppConstants.SectionID + value string +} + +func (ms mockGPPSection) GetID() gppConstants.SectionID { + return ms.sectionID +} + +func (ms mockGPPSection) GetValue() string { + return ms.value +} diff --git a/privacy/enforcement.go b/privacy/enforcement.go index ab2f64a691b..75601784020 100644 --- a/privacy/enforcement.go +++ b/privacy/enforcement.go @@ -1,6 +1,6 @@ package privacy -import "github.com/mxmCherry/openrtb/v15/openrtb2" +import "github.com/prebid/openrtb/v17/openrtb2" // Enforcement represents the privacy policies to enforce for an OpenRTB bid request. type Enforcement struct { diff --git a/privacy/enforcement_test.go b/privacy/enforcement_test.go index 61899e4d60e..9e333222f73 100644 --- a/privacy/enforcement_test.go +++ b/privacy/enforcement_test.go @@ -3,7 +3,7 @@ package privacy import ( "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) diff --git a/privacy/gdpr/consentwriter.go b/privacy/gdpr/consentwriter.go index ca784b7a5c1..569dbeb19b9 100644 --- a/privacy/gdpr/consentwriter.go +++ b/privacy/gdpr/consentwriter.go @@ -1,43 +1,42 @@ package gdpr import ( - "encoding/json" - - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" ) // ConsentWriter implements the PolicyWriter interface for GDPR TCF. type ConsentWriter struct { - Consent string + Consent string + RegExtGDPR *int8 } // Write mutates an OpenRTB bid request with the GDPR TCF consent. func (c ConsentWriter) Write(req *openrtb2.BidRequest) error { - if c.Consent == "" { + if req == nil { return nil } + reqWrap := &openrtb_ext.RequestWrapper{BidRequest: req} - if req.User == nil { - req.User = &openrtb2.User{} + if c.RegExtGDPR != nil { + if regsExt, err := reqWrap.GetRegExt(); err == nil { + regsExt.SetGDPR(c.RegExtGDPR) + } else { + return err + } } - if req.User.Ext == nil { - ext, err := json.Marshal(openrtb_ext.ExtUser{Consent: c.Consent}) - if err == nil { - req.User.Ext = ext + if c.Consent != "" { + if userExt, err := reqWrap.GetUserExt(); err == nil { + userExt.SetConsent(&c.Consent) + } else { + return err } - return err } - var extMap map[string]interface{} - err := json.Unmarshal(req.User.Ext, &extMap) - if err == nil { - extMap["consent"] = c.Consent - ext, err := json.Marshal(extMap) - if err == nil { - req.User.Ext = ext - } + if err := reqWrap.RebuildRequest(); err != nil { + return err } - return err + + return nil } diff --git a/privacy/gdpr/consentwriter_test.go b/privacy/gdpr/consentwriter_test.go index 5753442fa01..28e91ba0c26 100644 --- a/privacy/gdpr/consentwriter_test.go +++ b/privacy/gdpr/consentwriter_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/stretchr/testify/assert" ) @@ -88,7 +88,7 @@ func TestConsentWriter(t *testing.T) { } for _, test := range testCases { - writer := ConsentWriter{test.consent} + writer := ConsentWriter{test.consent, nil} err := writer.Write(test.request) if test.expectedError { diff --git a/privacy/lmt/ios.go b/privacy/lmt/ios.go index 55e1764c8c2..a3f052ba2b1 100644 --- a/privacy/lmt/ios.go +++ b/privacy/lmt/ios.go @@ -3,7 +3,7 @@ package lmt import ( "strings" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/util/iosutil" ) diff --git a/privacy/lmt/ios_test.go b/privacy/lmt/ios_test.go index af72cf08f23..a75f0c28ed1 100644 --- a/privacy/lmt/ios_test.go +++ b/privacy/lmt/ios_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/util/iosutil" "github.com/stretchr/testify/assert" ) diff --git a/privacy/lmt/policy.go b/privacy/lmt/policy.go index 0f2829254a2..74e22633d57 100644 --- a/privacy/lmt/policy.go +++ b/privacy/lmt/policy.go @@ -1,6 +1,6 @@ package lmt -import "github.com/mxmCherry/openrtb/v15/openrtb2" +import "github.com/prebid/openrtb/v17/openrtb2" const ( trackingUnrestricted = 0 diff --git a/privacy/lmt/policy_test.go b/privacy/lmt/policy_test.go index f475d2fb702..960dea80678 100644 --- a/privacy/lmt/policy_test.go +++ b/privacy/lmt/policy_test.go @@ -3,7 +3,7 @@ package lmt import ( "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/stretchr/testify/assert" ) diff --git a/privacy/scrubber.go b/privacy/scrubber.go index e07ebd0581b..69acf999ff3 100644 --- a/privacy/scrubber.go +++ b/privacy/scrubber.go @@ -4,7 +4,7 @@ import ( "encoding/json" "strings" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" ) // ScrubStrategyIPV4 defines the approach to scrub PII from an IPV4 address. diff --git a/privacy/scrubber_test.go b/privacy/scrubber_test.go index 13ac1f5c411..450d181201d 100644 --- a/privacy/scrubber_test.go +++ b/privacy/scrubber_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/stretchr/testify/assert" ) diff --git a/privacy/writer.go b/privacy/writer.go index 0f04a52f292..8f3f768fd5c 100644 --- a/privacy/writer.go +++ b/privacy/writer.go @@ -1,6 +1,6 @@ package privacy -import "github.com/mxmCherry/openrtb/v15/openrtb2" +import "github.com/prebid/openrtb/v17/openrtb2" // PolicyWriter mutates an OpenRTB bid request with a policy's regulatory information. type PolicyWriter interface { diff --git a/privacy/writer_test.go b/privacy/writer_test.go index f5b02387124..7b8be38aa59 100644 --- a/privacy/writer_test.go +++ b/privacy/writer_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/stretchr/testify/assert" ) diff --git a/router/router.go b/router/router.go index 10b231a09ef..81658728e34 100644 --- a/router/router.go +++ b/router/router.go @@ -5,9 +5,8 @@ import ( "crypto/tls" "encoding/json" "fmt" - "io/ioutil" "net/http" - "path/filepath" + "os" "strings" "time" @@ -20,9 +19,12 @@ import ( "github.com/prebid/prebid-server/endpoints/openrtb2" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/exchange" + "github.com/prebid/prebid-server/experiment/adscert" "github.com/prebid/prebid-server/gdpr" + "github.com/prebid/prebid-server/hooks" "github.com/prebid/prebid-server/metrics" metricsConf "github.com/prebid/prebid-server/metrics/config" + "github.com/prebid/prebid-server/modules" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/pbs" pbc "github.com/prebid/prebid-server/prebid_cache_client" @@ -30,10 +32,10 @@ import ( "github.com/prebid/prebid-server/server/ssl" 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/go-sql-driver/mysql" "github.com/golang/glog" "github.com/julienschmidt/httprouter" _ "github.com/lib/pq" @@ -43,16 +45,16 @@ import ( // 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: // -// { -// "a": { ... content from the file a.json ... }, -// "b": { ... content from the file b.json ... } -// } +// { +// "a": { ... content from the file a.json ... }, +// "b": { ... content from the file b.json ... } +// } // // This function stores the file contents in memory, and should not be used on large directories. // If the root directory, or any of the files in it, cannot be read, then the program will exit. func NewJsonDirectoryServer(schemaDirectory string, validator openrtb_ext.BidderParamValidator, aliases map[string]string) httprouter.Handle { // Slurp the files into memory first, since they're small and it minimizes request latency. - files, err := ioutil.ReadDir(schemaDirectory) + files, err := os.ReadDir(schemaDirectory) if err != nil { glog.Fatalf("Failed to read directory %s: %v", schemaDirectory, err) } @@ -113,7 +115,6 @@ type Router struct { func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *Router, err error) { const schemaDirectory = "./static/bidder-params" - const infoDirectory = "./static/bidder-info" r = &Router{ Router: httprouter.New(), @@ -130,6 +131,7 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R generalHttpClient := &http.Client{ Transport: &http.Transport{ + Proxy: http.ProxyFromEnvironment, MaxConnsPerHost: cfg.Client.MaxConnsPerHost, MaxIdleConns: cfg.Client.MaxIdleConns, MaxIdleConnsPerHost: cfg.Client.MaxIdleConnsPerHost, @@ -140,6 +142,7 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R cacheHttpClient := &http.Client{ Transport: &http.Transport{ + Proxy: http.ProxyFromEnvironment, MaxConnsPerHost: cfg.CacheClient.MaxConnsPerHost, MaxIdleConns: cfg.CacheClient.MaxIdleConns, MaxIdleConnsPerHost: cfg.CacheClient.MaxIdleConnsPerHost, @@ -147,21 +150,11 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R }, } - 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 { + if err := checkSupportedUserSyncEndpoints(cfg.BidderInfos); err != nil { return nil, err } - if err := checkSupportedUserSyncEndpoints(bidderInfos); err != nil { - return nil, err - } - - syncersByBidder, errs := usersync.BuildSyncers(cfg, bidderInfos) + syncersByBidder, errs := usersync.BuildSyncers(cfg, cfg.BidderInfos) if len(errs) > 0 { return nil, errortypes.NewAggregateError("user sync", errs) } @@ -175,8 +168,13 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R syncerKeys = append(syncerKeys, k) } + repo, moduleStageNames, err := modules.NewBuilder().Build(cfg.Hooks.Modules, generalHttpClient) + if err != nil { + glog.Fatalf("Failed to init hook modules: %v", err) + } + // Metrics engine - r.MetricsEngine = metricsConf.NewMetricsEngine(cfg, openrtb_ext.CoreBidderNames(), syncerKeys) + r.MetricsEngine = metricsConf.NewMetricsEngine(cfg, openrtb_ext.CoreBidderNames(), syncerKeys, moduleStageNames) shutdown, fetcher, ampFetcher, accounts, categoriesFetcher, videoFetcher, storedRespFetcher := storedRequestsConf.NewStoredRequests(cfg, r.MetricsEngine, generalHttpClient, r.Router) // todo(zachbadgett): better shutdown r.Shutdown = shutdown @@ -188,34 +186,40 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R glog.Fatalf("Failed to create the bidder params validator. %v", err) } - activeBidders := exchange.GetActiveBidders(bidderInfos) - disabledBidders := exchange.GetDisabledBiddersErrorMessages(bidderInfos) + activeBidders := exchange.GetActiveBidders(cfg.BidderInfos) + disabledBidders := exchange.GetDisabledBiddersErrorMessages(cfg.BidderInfos) defaultAliases, defReqJSON := readDefaultRequest(cfg.DefReqConfig) if err := validateDefaultAliases(defaultAliases); err != nil { return nil, err } - gvlVendorIDs := bidderInfos.ToGVLVendorIDMap() + gvlVendorIDs := cfg.BidderInfos.ToGVLVendorIDMap() vendorListFetcher := gdpr.NewVendorListFetcher(context.Background(), cfg.GDPR, generalHttpClient, gdpr.VendorListURLMaker) - gdprPerms := gdpr.NewPermissions(cfg.GDPR, &cfg.GDPR.TCF2, gvlVendorIDs, vendorListFetcher) + gdprPermsBuilder := gdpr.NewPermissionsBuilder(cfg.GDPR, gvlVendorIDs, vendorListFetcher) + tcf2CfgBuilder := gdpr.NewTCF2Config cacheClient := pbc.NewClient(cacheHttpClient, &cfg.CacheURL, &cfg.ExtCacheURL, r.MetricsEngine) - adapters, adaptersErrs := exchange.BuildAdapters(generalHttpClient, cfg, bidderInfos, r.MetricsEngine) + adapters, adaptersErrs := exchange.BuildAdapters(generalHttpClient, cfg, cfg.BidderInfos, r.MetricsEngine) if len(adaptersErrs) > 0 { errs := errortypes.NewAggregateError("Failed to initialize adapters", adaptersErrs) return nil, errs } + adsCertSigner, err := adscert.NewAdCertsSigner(cfg.Experiment.AdCerts) + if err != nil { + glog.Fatalf("Failed to create ads cert signer: %v", err) + } - theExchange := exchange.NewExchange(adapters, cacheClient, cfg, syncersByBidder, r.MetricsEngine, bidderInfos, vendorListFetcher, rateConvertor, categoriesFetcher) + planBuilder := hooks.NewExecutionPlanBuilder(cfg.Hooks, repo) + theExchange := exchange.NewExchange(adapters, cacheClient, cfg, syncersByBidder, r.MetricsEngine, cfg.BidderInfos, gdprPermsBuilder, tcf2CfgBuilder, rateConvertor, categoriesFetcher, adsCertSigner) var uuidGenerator uuidutil.UUIDRandomGenerator - openrtbEndpoint, err := openrtb2.NewEndpoint(uuidGenerator, theExchange, paramsValidator, fetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBidders, storedRespFetcher) + openrtbEndpoint, err := openrtb2.NewEndpoint(uuidGenerator, theExchange, paramsValidator, fetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBidders, storedRespFetcher, planBuilder) if err != nil { glog.Fatalf("Failed to create the openrtb2 endpoint handler. %v", err) } - ampEndpoint, err := openrtb2.NewAmpEndpoint(uuidGenerator, theExchange, paramsValidator, ampFetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBidders, storedRespFetcher) + ampEndpoint, err := openrtb2.NewAmpEndpoint(uuidGenerator, theExchange, paramsValidator, ampFetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBidders, storedRespFetcher, planBuilder) if err != nil { glog.Fatalf("Failed to create the amp endpoint handler. %v", err) } @@ -233,10 +237,10 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R 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("/info/bidders", infoEndpoints.NewBiddersEndpoint(cfg.BidderInfos, defaultAliases)) + r.GET("/info/bidders/:bidderName", infoEndpoints.NewBiddersDetailEndpoint(cfg.BidderInfos, defaultAliases)) r.GET("/bidders/params", NewJsonDirectoryServer(schemaDirectory, paramsValidator, defaultAliases)) - r.POST("/cookie_sync", endpoints.NewCookieSyncEndpoint(syncersByBidder, cfg, gdprPerms, r.MetricsEngine, pbsAnalytics, accounts, activeBidders).Handle) + r.POST("/cookie_sync", endpoints.NewCookieSyncEndpoint(syncersByBidder, cfg, gdprPermsBuilder, tcf2CfgBuilder, r.MetricsEngine, pbsAnalytics, accounts, activeBidders).Handle) r.GET("/status", endpoints.NewStatusEndpoint(cfg.StatusResponse)) r.GET("/", serveIndex) r.Handler("GET", "/version", endpoints.NewVersionEndpoint(version.Ver, version.Rev)) @@ -244,7 +248,7 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R // vtrack endpoint if cfg.VTrack.Enabled { - vtrackEndpoint := events.NewVTrackEndpoint(cfg, accounts, cacheClient, bidderInfos) + vtrackEndpoint := events.NewVTrackEndpoint(cfg, accounts, cacheClient, cfg.BidderInfos) r.POST("/vtrack", vtrackEndpoint) } @@ -258,7 +262,7 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R RecaptchaSecret: cfg.RecaptchaSecret, } - r.GET("/setuid", endpoints.NewSetUIDEndpoint(cfg.HostCookie, syncersByBidder, gdprPerms, pbsAnalytics, r.MetricsEngine)) + r.GET("/setuid", endpoints.NewSetUIDEndpoint(cfg, syncersByBidder, gdprPermsBuilder, tcf2CfgBuilder, pbsAnalytics, accounts, r.MetricsEngine)) r.GET("/getuids", endpoints.NewGetUIDsEndpoint(cfg.HostCookie)) r.POST("/optout", userSyncDeps.OptOut) r.GET("/optout", userSyncDeps.OptOut) @@ -266,64 +270,6 @@ 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 { - // 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 - // 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 { @@ -391,7 +337,7 @@ func readDefaultRequest(defReqConfig config.DefReqConfig) (map[string]string, [] if len(defReqConfig.FileSystem.FileName) == 0 { return aliases, []byte{} } - defReqJSON, err := ioutil.ReadFile(defReqConfig.FileSystem.FileName) + defReqJSON, err := os.ReadFile(defReqConfig.FileSystem.FileName) if err != nil { glog.Fatalf("error reading aliases from file %s: %v", defReqConfig.FileSystem.FileName, err) return aliases, []byte{} diff --git a/router/router_test.go b/router/router_test.go index b4ceaff16a9..2b4ff7fd3e7 100644 --- a/router/router_test.go +++ b/router/router_test.go @@ -2,9 +2,9 @@ package router import ( "encoding/json" - "io/ioutil" "net/http" "net/http/httptest" + "os" "testing" "github.com/prebid/prebid-server/config" @@ -47,7 +47,7 @@ func TestNewJsonDirectoryServer(t *testing.T) { json.Unmarshal(recorder.Body.Bytes(), &data) // Make sure that every adapter has a json schema by the same name associated with it. - adapterFiles, err := ioutil.ReadDir(adapterDirectory) + adapterFiles, err := os.ReadDir(adapterDirectory) if err != nil { t.Fatalf("Failed to open the adapters directory: %v", err) } @@ -61,89 +61,6 @@ func TestNewJsonDirectoryServer(t *testing.T) { ensureHasKey(t, data, "aliastest") } -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"}}}, - }, - { - // 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"}}}}, - 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"} diff --git a/schain/schain.go b/schain/schain.go index 785c7e24d7f..2ce946b326a 100644 --- a/schain/schain.go +++ b/schain/schain.go @@ -3,12 +3,14 @@ package schain import ( "fmt" + "github.com/prebid/openrtb/v17/openrtb2" + "github.com/prebid/prebid-server/openrtb_ext" ) // BidderToPrebidSChains organizes the ORTB 2.5 multiple root schain nodes into a map of schain nodes by bidder -func BidderToPrebidSChains(sChains []*openrtb_ext.ExtRequestPrebidSChain) (map[string]*openrtb_ext.ExtRequestPrebidSChainSChain, error) { - bidderToSChains := make(map[string]*openrtb_ext.ExtRequestPrebidSChainSChain) +func BidderToPrebidSChains(sChains []*openrtb_ext.ExtRequestPrebidSChain) (map[string]*openrtb2.SupplyChain, error) { + bidderToSChains := make(map[string]*openrtb2.SupplyChain) for _, schainWrapper := range sChains { for _, bidder := range schainWrapper.Bidders { diff --git a/schain/schain_test.go b/schain/schain_test.go index 22903d69e85..757fbf2280e 100644 --- a/schain/schain_test.go +++ b/schain/schain_test.go @@ -3,6 +3,7 @@ package schain import ( "testing" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -13,16 +14,16 @@ func TestBidderToPrebidChains(t *testing.T) { SChains: []*openrtb_ext.ExtRequestPrebidSChain{ { Bidders: []string{"Bidder1", "Bidder2"}, - SChain: openrtb_ext.ExtRequestPrebidSChainSChain{ + SChain: openrtb2.SupplyChain{ Complete: 1, - Nodes: []*openrtb_ext.ExtRequestPrebidSChainSChainNode{ + Nodes: []openrtb2.SupplyChainNode{ { ASI: "asi1", SID: "sid1", Name: "name1", RID: "rid1", Domain: "domain1", - HP: 1, + HP: openrtb2.Int8Ptr(1), }, { ASI: "asi2", @@ -30,7 +31,7 @@ func TestBidderToPrebidChains(t *testing.T) { Name: "name2", RID: "rid2", Domain: "domain2", - HP: 2, + HP: openrtb2.Int8Ptr(2), }, }, Ver: "version1", @@ -38,7 +39,7 @@ func TestBidderToPrebidChains(t *testing.T) { }, { Bidders: []string{"Bidder3", "Bidder4"}, - SChain: openrtb_ext.ExtRequestPrebidSChainSChain{}, + SChain: openrtb2.SupplyChain{}, }, }, }, @@ -60,11 +61,11 @@ func TestBidderToPrebidChainsDiscardMultipleChainsForBidder(t *testing.T) { SChains: []*openrtb_ext.ExtRequestPrebidSChain{ { Bidders: []string{"Bidder1"}, - SChain: openrtb_ext.ExtRequestPrebidSChainSChain{}, + SChain: openrtb2.SupplyChain{}, }, { Bidders: []string{"Bidder1", "Bidder2"}, - SChain: openrtb_ext.ExtRequestPrebidSChainSChain{}, + SChain: openrtb2.SupplyChain{}, }, }, }, diff --git a/schain/schainwriter.go b/schain/schainwriter.go index bf2f564017f..a6bb2530d6a 100644 --- a/schain/schainwriter.go +++ b/schain/schainwriter.go @@ -3,14 +3,14 @@ package schain import ( "encoding/json" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" ) // NewSChainWriter creates an ORTB 2.5 schain writer instance -func NewSChainWriter(reqExt *openrtb_ext.ExtRequest) (*SChainWriter, error) { +func NewSChainWriter(reqExt *openrtb_ext.ExtRequest, hostSChainNode *openrtb2.SupplyChainNode) (*SChainWriter, error) { if !extPrebidSChainExists(reqExt) { - return &SChainWriter{}, nil + return &SChainWriter{hostSChainNode: hostSChainNode}, nil } sChainsByBidder, err := BidderToPrebidSChains(reqExt.Prebid.SChains) @@ -20,6 +20,7 @@ func NewSChainWriter(reqExt *openrtb_ext.ExtRequest) (*SChainWriter, error) { writer := SChainWriter{ sChainsByBidder: sChainsByBidder, + hostSChainNode: hostSChainNode, } return &writer, nil } @@ -27,7 +28,8 @@ func NewSChainWriter(reqExt *openrtb_ext.ExtRequest) (*SChainWriter, error) { // SChainWriter is used to write the appropriate schain for a particular bidder defined in the ORTB 2.5 multi-schain // location (req.ext.prebid.schain) to the ORTB 2.5 location (req.source.ext) type SChainWriter struct { - sChainsByBidder map[string]*openrtb_ext.ExtRequestPrebidSChainSChain + sChainsByBidder map[string]*openrtb2.SupplyChain + hostSChainNode *openrtb2.SupplyChainNode } // Write selects an schain from the multi-schain ORTB 2.5 location (req.ext.prebid.schains) for the specified bidder @@ -35,44 +37,47 @@ type SChainWriter struct { // location and no wildcard schain exists, the request is not modified. func (w SChainWriter) Write(req *openrtb2.BidRequest, bidder string) { const sChainWildCard = "*" - var selectedSChain *openrtb_ext.ExtRequestPrebidSChainSChain + var selectedSChain *openrtb2.SupplyChain wildCardSChain := w.sChainsByBidder[sChainWildCard] bidderSChain := w.sChainsByBidder[bidder] // source should not be modified - if bidderSChain == nil && wildCardSChain == nil { + if bidderSChain == nil && wildCardSChain == nil && w.hostSChainNode == nil { return } + selectedSChain = &openrtb2.SupplyChain{Ver: "1.0"} + if bidderSChain != nil { selectedSChain = bidderSChain - } else { + } else if wildCardSChain != nil { selectedSChain = wildCardSChain } + schain := openrtb_ext.ExtRequestPrebidSChain{ + SChain: *selectedSChain, + } + if req.Source == nil { req.Source = &openrtb2.Source{} } else { sourceCopy := *req.Source req.Source = &sourceCopy } - schain := openrtb_ext.ExtRequestPrebidSChain{ - SChain: *selectedSChain, + + if w.hostSChainNode != nil { + schain.SChain.Nodes = append(schain.SChain.Nodes, *w.hostSChainNode) } + sourceExt, err := json.Marshal(schain) if err == nil { req.Source.Ext = sourceExt } + } // extPrebidSChainExists checks if an schain exists in the ORTB 2.5 req.ext.prebid.schain location func extPrebidSChainExists(reqExt *openrtb_ext.ExtRequest) bool { - if reqExt == nil { - return false - } - if reqExt.Prebid.SChains == nil { - return false - } - return true + return reqExt != nil && reqExt.Prebid.SChains != nil } diff --git a/schain/schainwriter_test.go b/schain/schainwriter_test.go index 92dc550d5e6..078a103712f 100644 --- a/schain/schainwriter_test.go +++ b/schain/schainwriter_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -15,21 +15,25 @@ func TestSChainWriter(t *testing.T) { const seller2SChain string = `"schain":{"complete":2,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":2}],"ver":"2.0"}` const seller3SChain string = `"schain":{"complete":3,"nodes":[{"asi":"directseller3.com","sid":"00003","rid":"BidRequest3","hp":3}],"ver":"3.0"}` const sellerWildCardSChain string = `"schain":{"complete":1,"nodes":[{"asi":"wildcard1.com","sid":"wildcard1","rid":"WildcardReq1","hp":1}],"ver":"1.0"}` + const hostNode string = `{"asi":"pbshostcompany.com","sid":"00001","rid":"BidRequest","hp":1}` + const seller1Node string = `{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}` tests := []struct { - description string - giveRequest openrtb2.BidRequest - giveBidder string - wantRequest openrtb2.BidRequest - wantError bool + description string + giveRequest openrtb2.BidRequest + giveBidder string + giveHostSChain *openrtb2.SupplyChainNode + wantRequest openrtb2.BidRequest + wantError bool }{ { - description: "nil source and nil ext.prebid.schains", + description: "nil source, nil ext.prebid.schains and empty host schain", giveRequest: openrtb2.BidRequest{ Ext: nil, Source: nil, }, - giveBidder: "appnexus", + giveBidder: "appnexus", + giveHostSChain: nil, wantRequest: openrtb2.BidRequest{ Ext: nil, Source: nil, @@ -152,6 +156,40 @@ func TestSChainWriter(t *testing.T) { }, wantError: true, }, + { + description: "Schain in request, host schain defined, source.ext for bidder request should update with appended host schain", + giveRequest: openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["testbidder"],"schain":{"complete":1,"nodes":[` + seller1Node + `],"ver":"1.0"}}]}}`), + Source: nil, + }, + giveBidder: "testbidder", + giveHostSChain: &openrtb2.SupplyChainNode{ + ASI: "pbshostcompany.com", SID: "00001", RID: "BidRequest", HP: openrtb2.Int8Ptr(1), + }, + wantRequest: openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["testbidder"],"schain":{"complete":1,"nodes":[` + seller1Node + `],"ver":"1.0"}}]}}`), + Source: &openrtb2.Source{ + Ext: json.RawMessage(`{"schain":{"complete":1,"nodes":[` + seller1Node + `,` + hostNode + `],"ver":"1.0"}}`), + }, + }, + }, + { + description: "No Schain in request, host schain defined, source.ext for bidder request should have just the host schain", + giveRequest: openrtb2.BidRequest{ + Ext: nil, + Source: nil, + }, + giveBidder: "testbidder", + giveHostSChain: &openrtb2.SupplyChainNode{ + ASI: "pbshostcompany.com", SID: "00001", RID: "BidRequest", HP: openrtb2.Int8Ptr(1), + }, + wantRequest: openrtb2.BidRequest{ + Ext: nil, + Source: &openrtb2.Source{ + Ext: json.RawMessage(`{"schain":{"complete":0,"nodes":[` + hostNode + `],"ver":"1.0"}}`), + }, + }, + }, } for _, tt := range tests { @@ -165,7 +203,7 @@ func TestSChainWriter(t *testing.T) { } } - writer, err := NewSChainWriter(reqExt) + writer, err := NewSChainWriter(reqExt, tt.giveHostSChain) if tt.wantError { assert.NotNil(t, err) diff --git a/server/listener_test.go b/server/listener_test.go index 750d0ed8dda..d10a3bdfbf9 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{}, nil) + me := metrics.NewMetrics(reg, nil, config.DisabledMetrics{}, nil, nil) var listener net.Listener = &mockListener{ listenSuccess: allowAccept, diff --git a/server/ssl/ssl.go b/server/ssl/ssl.go index a424cd9f54b..f206fb7beed 100644 --- a/server/ssl/ssl.go +++ b/server/ssl/ssl.go @@ -3,7 +3,7 @@ package ssl import ( "crypto/x509" "fmt" - "io/ioutil" + "os" ) // from https://medium.com/@kelseyhightower/optimizing-docker-images-for-static-binaries-b5696e26eb07 @@ -27,7 +27,7 @@ func AppendPEMFileToRootCAPool(certPool *x509.CertPool, pemFileName string) (*x5 } if pemFileName != "" { //read file and place it's contents in `pemCerts` - pemCerts, err := ioutil.ReadFile(pemFileName) + pemCerts, err := os.ReadFile(pemFileName) if err != nil { return certPool, fmt.Errorf("Failed to read file %s: %v", pemFileName, err) } diff --git a/static/bidder-info/33across.yaml b/static/bidder-info/33across.yaml index bdda5a7e5a6..902db6b362b 100644 --- a/static/bidder-info/33across.yaml +++ b/static/bidder-info/33across.yaml @@ -1,3 +1,4 @@ +endpoint: "https://ssc.33across.com/api/v1/s2s" maintainer: email: "headerbidding@33across.com" gvlVendorID: 58 diff --git a/static/bidder-info/aax.yaml b/static/bidder-info/aax.yaml index e08cb559770..a7dee8a70b0 100644 --- a/static/bidder-info/aax.yaml +++ b/static/bidder-info/aax.yaml @@ -1,3 +1,5 @@ +endpoint: "https://prebid.aaxads.com/rtb/pb/aax-prebid" +extra_info: "https://aax.golang.pbs.com" maintainer: email: product@aax.media gvlVendorID: 720 @@ -16,4 +18,4 @@ capabilities: userSync: redirect: url: https://c.aaxads.com/aacxc.php?fv=1&wbsh=psa&ryvlg=setstatuscode&bidder=aax&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}} - userMacro: <vsid> \ No newline at end of file + userMacro: <vsid> diff --git a/static/bidder-info/aceex.yaml b/static/bidder-info/aceex.yaml index a94d37528d1..b845d648101 100644 --- a/static/bidder-info/aceex.yaml +++ b/static/bidder-info/aceex.yaml @@ -1,3 +1,4 @@ +endpoint: "http://bl-us.aceex.io/?uqhash={{.AccountID}}" maintainer: email: "tech@aceex.io" capabilities: diff --git a/static/bidder-info/acuityads.yaml b/static/bidder-info/acuityads.yaml index c806294d644..3efbd189cd7 100644 --- a/static/bidder-info/acuityads.yaml +++ b/static/bidder-info/acuityads.yaml @@ -1,3 +1,4 @@ +endpoint: "http://{{.Host}}.admanmedia.com/bid?token={{.AccountID}}" maintainer: email: "integrations@acuityads.com" gvlVendorID: 231 @@ -15,4 +16,4 @@ capabilities: 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 + userMacro: "[UID]" diff --git a/static/bidder-info/adf.yaml b/static/bidder-info/adf.yaml index 2e312bff788..2310f346c25 100644 --- a/static/bidder-info/adf.yaml +++ b/static/bidder-info/adf.yaml @@ -1,3 +1,4 @@ +endpoint: "https://adx.adform.net/adx/openrtb" maintainer: email: "scope.sspp@adform.com" gvlVendorID: 50 diff --git a/static/bidder-info/adform.yaml b/static/bidder-info/adform.yaml index 2e312bff788..2310f346c25 100644 --- a/static/bidder-info/adform.yaml +++ b/static/bidder-info/adform.yaml @@ -1,3 +1,4 @@ +endpoint: "https://adx.adform.net/adx/openrtb" maintainer: email: "scope.sspp@adform.com" gvlVendorID: 50 diff --git a/static/bidder-info/adgeneration.yaml b/static/bidder-info/adgeneration.yaml index 55f653143dd..a750dc6ee80 100644 --- a/static/bidder-info/adgeneration.yaml +++ b/static/bidder-info/adgeneration.yaml @@ -1,3 +1,4 @@ +endpoint: "https://d.socdm.com/adsv/v1" maintainer: email: "ssp-ope@supership.jp" capabilities: @@ -7,4 +8,3 @@ capabilities: site: mediaTypes: - banner - diff --git a/static/bidder-info/adhese.yaml b/static/bidder-info/adhese.yaml index 25058e82409..532c114de5b 100644 --- a/static/bidder-info/adhese.yaml +++ b/static/bidder-info/adhese.yaml @@ -1,3 +1,4 @@ +endpoint: "https://ads-{{.AccountID}}.adhese.com/json" maintainer: email: info@adhese.com gvlVendorID: 553 diff --git a/static/bidder-info/adkernel.yaml b/static/bidder-info/adkernel.yaml index 8d9096b271c..864ca71a088 100644 --- a/static/bidder-info/adkernel.yaml +++ b/static/bidder-info/adkernel.yaml @@ -1,3 +1,4 @@ +endpoint: "https://pbs.adksrv.com/hb?zone={{.ZoneID}}" maintainer: email: "prebid-dev@adkernel.com" gvlVendorID: 14 @@ -12,4 +13,5 @@ capabilities: 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 + userMacro: "{UID}" +endpointCompression: "GZIP" \ No newline at end of file diff --git a/static/bidder-info/adkernelAdn.yaml b/static/bidder-info/adkernelAdn.yaml index e401cb83923..eb4c06b6cc8 100644 --- a/static/bidder-info/adkernelAdn.yaml +++ b/static/bidder-info/adkernelAdn.yaml @@ -1,3 +1,4 @@ +endpoint: "https://pbs2.adksrv.com/rtbpub?account={{.PublisherID}}" maintainer: email: "prebid-dev@adkernel.com" gvlVendorID: 14 @@ -13,3 +14,4 @@ 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 d7869fdc536..ad49f8fbe61 100644 --- a/static/bidder-info/adman.yaml +++ b/static/bidder-info/adman.yaml @@ -1,3 +1,4 @@ +endpoint: "http://pub.admanmedia.com/?c=o&m=ortb" maintainer: email: "prebid@admanmedia.com" gvlVendorID: 149 @@ -14,3 +15,4 @@ 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 5cb07ef5579..596c2271a83 100644 --- a/static/bidder-info/admixer.yaml +++ b/static/bidder-info/admixer.yaml @@ -1,3 +1,4 @@ +endpoint: "http://inv-nets.admixer.net/pbs.aspx" maintainer: email: "prebid@admixer.net" gvlVendorID: 511 diff --git a/static/bidder-info/adnuntius.yaml b/static/bidder-info/adnuntius.yaml index 86a0192cd62..0e1d86dd286 100644 --- a/static/bidder-info/adnuntius.yaml +++ b/static/bidder-info/adnuntius.yaml @@ -1,3 +1,5 @@ +endpoint: "https://ads.adnuntius.delivery/i" +extra_info: "https://europe.delivery.adnuntius.com/i" maintainer: email: hello@adnuntius.com gvlVendorID: 855 diff --git a/static/bidder-info/adocean.yaml b/static/bidder-info/adocean.yaml index 680e7496725..5001309593e 100644 --- a/static/bidder-info/adocean.yaml +++ b/static/bidder-info/adocean.yaml @@ -1,3 +1,4 @@ +endpoint: "https://{{.Host}}" maintainer: email: "aoteam@gemius.com" gvlVendorID: 328 @@ -12,4 +13,4 @@ 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 + - redirect diff --git a/static/bidder-info/adoppler.yaml b/static/bidder-info/adoppler.yaml index 1b10103923e..3db2c1f998d 100644 --- a/static/bidder-info/adoppler.yaml +++ b/static/bidder-info/adoppler.yaml @@ -1,3 +1,4 @@ +endpoint: "http://{{.AccountID}}.trustedmarketplace.io/ads/processHeaderBid/{{.AdUnit}}" maintainer: email: pbs@adoppler.com capabilities: diff --git a/static/bidder-info/adot.yaml b/static/bidder-info/adot.yaml index cd2cd3c4f73..4f51bbc9457 100644 --- a/static/bidder-info/adot.yaml +++ b/static/bidder-info/adot.yaml @@ -1,3 +1,4 @@ +endpoint: "https://dsp.adotmob.com/headerbidding{PUBLISHER_PATH}/bidrequest" maintainer: email: "admin@we-are-adot.com" gvlVendorID: 272 @@ -16,4 +17,4 @@ capabilities: 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 + userMacro: "{amob_user_id}" diff --git a/static/bidder-info/adpone.yaml b/static/bidder-info/adpone.yaml index c5fb83fb39c..cfa49dc7361 100644 --- a/static/bidder-info/adpone.yaml +++ b/static/bidder-info/adpone.yaml @@ -1,3 +1,4 @@ +endpoint: "http://rtb.adpone.com/bid-request?src=prebid_server" maintainer: email: "tech@adpone.com" gvlVendorID: 799 @@ -11,4 +12,4 @@ capabilities: userSync: redirect: url: "https://usersync.adpone.com/csync?redir={{.RedirectURL}}" - userMacro: "{uid}" \ No newline at end of file + userMacro: "{uid}" diff --git a/static/bidder-info/adprime.yaml b/static/bidder-info/adprime.yaml index 4e5ff75fb71..2aab94c6b9d 100644 --- a/static/bidder-info/adprime.yaml +++ b/static/bidder-info/adprime.yaml @@ -1,3 +1,4 @@ +endpoint: "http://delta.adprime.com/pserver" maintainer: email: "prebid@adprime.com" capabilities: @@ -10,4 +11,4 @@ capabilities: mediaTypes: - banner - video - - native + - native diff --git a/static/bidder-info/adrino.yaml b/static/bidder-info/adrino.yaml new file mode 100644 index 00000000000..a9d7f77afaf --- /dev/null +++ b/static/bidder-info/adrino.yaml @@ -0,0 +1,8 @@ +endpoint: "https://prd-prebid-bidder.adrino.io/openrtb/bid" +maintainer: + email: dev@adrino.pl +gvlVendorID: 1072 +capabilities: + site: + mediaTypes: + - native diff --git a/static/bidder-info/adtarget.yaml b/static/bidder-info/adtarget.yaml index 9ef7e259d22..446959d109c 100644 --- a/static/bidder-info/adtarget.yaml +++ b/static/bidder-info/adtarget.yaml @@ -1,3 +1,4 @@ +endpoint: "http://ghb.console.adtarget.com.tr/pbs/ortb" maintainer: email: "kamil@adtarget.com.tr" gvlVendorID: 779 @@ -14,4 +15,4 @@ 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 + - iframe diff --git a/static/bidder-info/adtelligent.yaml b/static/bidder-info/adtelligent.yaml index f8ce434d481..0b73f065426 100644 --- a/static/bidder-info/adtelligent.yaml +++ b/static/bidder-info/adtelligent.yaml @@ -1,3 +1,4 @@ +endpoint: "http://ghb.adtelligent.com/pbs/ortb" maintainer: email: "hb@adtelligent.com" gvlVendorID: 410 @@ -14,4 +15,4 @@ 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 + - iframe diff --git a/static/bidder-info/adtrgtme.yaml b/static/bidder-info/adtrgtme.yaml new file mode 100644 index 00000000000..3c509c0a007 --- /dev/null +++ b/static/bidder-info/adtrgtme.yaml @@ -0,0 +1,10 @@ +endpoint: "https://z.cdn.adtarget.market/ssp" +maintainer: + email: "info@adtarget.me" +capabilities: + app: + mediaTypes: + - banner + site: + mediaTypes: + - banner diff --git a/static/bidder-info/advangelists.yaml b/static/bidder-info/advangelists.yaml index 35495d4d97f..0ffa16eafb1 100644 --- a/static/bidder-info/advangelists.yaml +++ b/static/bidder-info/advangelists.yaml @@ -1,3 +1,4 @@ +endpoint: "http://nep.advangelists.com/xp/get?pubid={{.PublisherID}}" maintainer: email: "prebid@advangelists.com" capabilities: diff --git a/static/bidder-info/adview.yaml b/static/bidder-info/adview.yaml index 077bce62b45..53158cf49e2 100644 --- a/static/bidder-info/adview.yaml +++ b/static/bidder-info/adview.yaml @@ -1,3 +1,4 @@ +endpoint: "https://bid.adview.com/agent/thirdAdxService/{{.AccountID}}" maintainer: email: "partner@adview.com" gvlVendorID: 1022 diff --git a/static/bidder-info/adxcg.yaml b/static/bidder-info/adxcg.yaml index 14c7c8bb572..fa37ff5defd 100644 --- a/static/bidder-info/adxcg.yaml +++ b/static/bidder-info/adxcg.yaml @@ -1,3 +1,4 @@ +disabled: true maintainer: email: "info@adxcg.com" capabilities: diff --git a/static/bidder-info/adyoulike.yaml b/static/bidder-info/adyoulike.yaml index 59711ff03a3..d1ddae74173 100644 --- a/static/bidder-info/adyoulike.yaml +++ b/static/bidder-info/adyoulike.yaml @@ -1,3 +1,4 @@ +endpoint: "https://broker.omnitagjs.com/broker/bid?partnerId=19340f4f097d16f41f34fc0274981ca4" maintainer: email: "core@adyoulike.com" gvlVendorID: 259 diff --git a/static/bidder-info/aja.yaml b/static/bidder-info/aja.yaml index 1d18dd7e75d..db0c1cad200 100644 --- a/static/bidder-info/aja.yaml +++ b/static/bidder-info/aja.yaml @@ -1,3 +1,4 @@ +endpoint: "https://ad.as.amanad.adtdp.com/v1/bid/4" maintainer: email: "dev@aja-kk.co.jp" capabilities: diff --git a/static/bidder-info/algorix.yaml b/static/bidder-info/algorix.yaml index 75ab4c09123..9e6c3738f68 100644 --- a/static/bidder-info/algorix.yaml +++ b/static/bidder-info/algorix.yaml @@ -1,3 +1,4 @@ +endpoint: "https://{{.Host}}.svr-algorix.com/rtb/sa?sid={{.SourceId}}&token={{.AccountID}}" maintainer: email: "prebid@algorix.co" capabilities: diff --git a/static/bidder-info/amx.yaml b/static/bidder-info/amx.yaml index ae2942c460b..3ede0d0875f 100644 --- a/static/bidder-info/amx.yaml +++ b/static/bidder-info/amx.yaml @@ -1,3 +1,4 @@ +endpoint: "http://pbs.amxrtb.com/auction/openrtb" maintainer: email: "prebid@amxrtb.com" gvlVendorID: 737 @@ -16,3 +17,4 @@ userSync: redirect: url: "https://prebid.a-mo.net/cchain/0?gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&cb={{.RedirectURL}}" userMacro: "" + diff --git a/static/bidder-info/apacdex.yaml b/static/bidder-info/apacdex.yaml index 5ef240219fe..590737ac28b 100644 --- a/static/bidder-info/apacdex.yaml +++ b/static/bidder-info/apacdex.yaml @@ -1,3 +1,4 @@ +endpoint: "http://useast.quantumdex.io/auction/pbs" maintainer: email: "support@apacdex.com" modifyingVastXmlAllowed: false diff --git a/static/bidder-info/applogy.yaml b/static/bidder-info/applogy.yaml index bb908c94e70..62a70a17f28 100644 --- a/static/bidder-info/applogy.yaml +++ b/static/bidder-info/applogy.yaml @@ -1,3 +1,4 @@ +endpoint: "http://rtb.applogy.com/v1/prebid" maintainer: email: work@applogy.com capabilities: diff --git a/static/bidder-info/appnexus.yaml b/static/bidder-info/appnexus.yaml index 41838159bbe..06afe454f89 100644 --- a/static/bidder-info/appnexus.yaml +++ b/static/bidder-info/appnexus.yaml @@ -1,3 +1,5 @@ +endpoint: "http://ib.adnxs.com/openrtb2" +platform_id: "5" maintainer: email: "prebid-server@xandr.com" gvlVendorID: 32 diff --git a/static/bidder-info/appush.yaml b/static/bidder-info/appush.yaml new file mode 100644 index 00000000000..68bdad480b8 --- /dev/null +++ b/static/bidder-info/appush.yaml @@ -0,0 +1,16 @@ +endpoint: "http://hb.appush.com/pserver" +maintainer: + email: "support@appush.com" +gvlVendorID: 879 +capabilities: + site: + mediaTypes: + - banner + - video + - native + + app: + mediaTypes: + - banner + - video + - native \ No newline at end of file diff --git a/static/bidder-info/audienceNetwork.yaml b/static/bidder-info/audienceNetwork.yaml index 5e567318a89..10117ce708b 100644 --- a/static/bidder-info/audienceNetwork.yaml +++ b/static/bidder-info/audienceNetwork.yaml @@ -1,3 +1,5 @@ +endpoint: "https://an.facebook.com/placementbid.ortb" +disabled: true maintainer: email: "none" capabilities: @@ -9,4 +11,4 @@ capabilities: userSync: # facebook's audience network supports user syncing, but requires configuration by the host. supports: - - redirect \ No newline at end of file + - redirect diff --git a/static/bidder-info/automatad.yaml b/static/bidder-info/automatad.yaml new file mode 100644 index 00000000000..a8b1427d908 --- /dev/null +++ b/static/bidder-info/automatad.yaml @@ -0,0 +1,10 @@ +endpoint: "https://s2s.atmtd.com" +maintainer: + email: "tech@automatad.com" +capabilities: + app: + mediaTypes: + - banner + site: + mediaTypes: + - banner diff --git a/static/bidder-info/avocet.yaml b/static/bidder-info/avocet.yaml index ad8ca157a75..c2c4bb91d76 100644 --- a/static/bidder-info/avocet.yaml +++ b/static/bidder-info/avocet.yaml @@ -1,3 +1,4 @@ +disabled: true maintainer: email: "developers@avocet.io" gvlVendorID: 63 diff --git a/static/bidder-info/axonix.yaml b/static/bidder-info/axonix.yaml index 3c73501d9cc..8bb68a4b77a 100644 --- a/static/bidder-info/axonix.yaml +++ b/static/bidder-info/axonix.yaml @@ -1,3 +1,4 @@ +endpoint: "https://openrtb-us-east-1.axonix.com/supply/prebid-server/{{.AccountID}}" maintainer: email: support.axonix@emodoinc.com gvlVendorID: 678 diff --git a/static/bidder-info/beachfront.yaml b/static/bidder-info/beachfront.yaml index 64e04dc6eec..c98416ffe48 100644 --- a/static/bidder-info/beachfront.yaml +++ b/static/bidder-info/beachfront.yaml @@ -1,3 +1,5 @@ +endpoint: "https://display.bfmio.com/prebid_display" +extra_info: "{\"video_endpoint\":\"https://reachms.bfmio.com/bid.json?exchange_id\"}" maintainer: email: "prebid@beachfront.com" gvlVendorID: 335 @@ -14,3 +16,4 @@ 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 d804a29818d..3fa1838d03d 100644 --- a/static/bidder-info/beintoo.yaml +++ b/static/bidder-info/beintoo.yaml @@ -1,3 +1,4 @@ +endpoint: "https://ib.beintoo.com/um" maintainer: email: "adops@beintoo.com" gvlVendorID: 618 diff --git a/static/bidder-info/between.yaml b/static/bidder-info/between.yaml index 5649c328d62..cf5ce25ba7c 100644 --- a/static/bidder-info/between.yaml +++ b/static/bidder-info/between.yaml @@ -1,3 +1,4 @@ +endpoint: "http://{{.Host}}.betweendigital.com/openrtb_bid?sspId={{.PublisherID}}" maintainer: email: "buying@betweenx.com" gvlVendorID: 724 @@ -12,3 +13,4 @@ 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/beyondmedia.yaml b/static/bidder-info/beyondmedia.yaml new file mode 100644 index 00000000000..4fb782b740c --- /dev/null +++ b/static/bidder-info/beyondmedia.yaml @@ -0,0 +1,15 @@ +endpoint: "http://backend.andbeyond.media/pserver" +maintainer: + email: "sysengg@andbeyond.media" +capabilities: + site: + mediaTypes: + - banner + - video + - native + + app: + mediaTypes: + - banner + - video + - native diff --git a/static/bidder-info/bidmachine.yaml b/static/bidder-info/bidmachine.yaml index 6868125b6e6..9f3e1da0d16 100644 --- a/static/bidder-info/bidmachine.yaml +++ b/static/bidder-info/bidmachine.yaml @@ -1,3 +1,4 @@ +endpoint: "https://{{.Host}}.bidmachine.io" maintainer: email: hi@bidmachine.io gvlVendorID: 736 @@ -5,4 +6,4 @@ capabilities: app: mediaTypes: - banner - - video \ No newline at end of file + - video diff --git a/static/bidder-info/bidmyadz.yaml b/static/bidder-info/bidmyadz.yaml index 41cf4ecaa0a..a83947c5127 100644 --- a/static/bidder-info/bidmyadz.yaml +++ b/static/bidder-info/bidmyadz.yaml @@ -1,3 +1,4 @@ +endpoint: "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc" maintainer: email: "contact@bidmyadz.com" capabilities: diff --git a/static/bidder-info/bidscube.yaml b/static/bidder-info/bidscube.yaml index 046eb6224d7..631433e4724 100644 --- a/static/bidder-info/bidscube.yaml +++ b/static/bidder-info/bidscube.yaml @@ -1,3 +1,4 @@ +endpoint: "http://supply.bidscube.com/?c=o&m=rtb" maintainer: email: "support@bidscube.com" modifyingVastXmlAllowed: true @@ -12,3 +13,4 @@ capabilities: - banner - video - native + diff --git a/static/bidder-info/bidstack.yaml b/static/bidder-info/bidstack.yaml new file mode 100644 index 00000000000..894d4fe2503 --- /dev/null +++ b/static/bidder-info/bidstack.yaml @@ -0,0 +1,10 @@ +endpoint: "https://server.bidstack.com/openrtb" +maintainer: + email: tech@bidstack.com +endpointCompression: gzip +gvlVendorID: 462 +modifyingVastXmlAllowed: true +capabilities: + app: + mediaTypes: + - video diff --git a/static/bidder-info/bizzclick.yaml b/static/bidder-info/bizzclick.yaml index 48921c391b3..28ff0d44285 100644 --- a/static/bidder-info/bizzclick.yaml +++ b/static/bidder-info/bizzclick.yaml @@ -1,3 +1,4 @@ +endpoint: "http://us-e-node1.bizzclick.com/bid?rtb_seat_id={{.SourceId}}&secret_key={{.AccountID}}" maintainer: email: "support@bizzclick.com" capabilities: diff --git a/static/bidder-info/bliink.yaml b/static/bidder-info/bliink.yaml index 648f14db8c6..8afacff11ab 100644 --- a/static/bidder-info/bliink.yaml +++ b/static/bidder-info/bliink.yaml @@ -1,3 +1,4 @@ +endpoint: "http://engine.bliink.io/bid" maintainer: email: "support@bliink.io" gvlVendorID: 658 @@ -16,4 +17,4 @@ capabilities: userSync: redirect: url: "https://cookiesync.api.bliink.io/getuid?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url={{.RedirectURL}}" - userMacro: "$UID" \ No newline at end of file + userMacro: "$UID" diff --git a/static/bidder-info/yssp.yaml b/static/bidder-info/blue.yaml similarity index 55% rename from static/bidder-info/yssp.yaml rename to static/bidder-info/blue.yaml index a1a20e0c9d0..26d184005e9 100644 --- a/static/bidder-info/yssp.yaml +++ b/static/bidder-info/blue.yaml @@ -1,6 +1,7 @@ +endpoint: "https://prebid-us-east-1.getblue.io/?src=prebid" maintainer: - email: "hb-fe-tech@oath.com" -gvlVendorID: 25 + email: "prebid@getblue.io" +gvlVendorID: 620 capabilities: app: mediaTypes: @@ -9,7 +10,7 @@ capabilities: mediaTypes: - banner userSync: - # yssp supports user syncing, but requires configuration by the host. contact this + # blue 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 diff --git a/static/bidder-info/bmtm.yaml b/static/bidder-info/bmtm.yaml index bc7d2aed806..2992d0fbc7b 100644 --- a/static/bidder-info/bmtm.yaml +++ b/static/bidder-info/bmtm.yaml @@ -1,3 +1,4 @@ +endpoint: "https://one.elitebidder.com/api/pbs" maintainer: email: dev@brightmountainmedia.com modifyingVastXmlAllowed: false @@ -10,4 +11,4 @@ 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 + - redirect diff --git a/static/bidder-info/boldwin.yaml b/static/bidder-info/boldwin.yaml new file mode 100644 index 00000000000..39c1caf34f8 --- /dev/null +++ b/static/bidder-info/boldwin.yaml @@ -0,0 +1,15 @@ +endpoint: "http://ssp.videowalldirect.com/pserver" +maintainer: + email: "wls_demo_box@smartyads.com" +capabilities: + site: + mediaTypes: + - banner + - video + - native + + app: + mediaTypes: + - banner + - video + - native diff --git a/static/bidder-info/brightroll.yaml b/static/bidder-info/brightroll.yaml index db822bc4f7b..196344e9f25 100644 --- a/static/bidder-info/brightroll.yaml +++ b/static/bidder-info/brightroll.yaml @@ -1,3 +1,4 @@ +endpoint: "http://east-bid.ybp.yahoo.com/bid/appnexuspbs" maintainer: email: "dsp-supply-prebid@verizonmedia.com" gvlVendorID: 25 diff --git a/static/bidder-info/ccx.yaml b/static/bidder-info/ccx.yaml new file mode 100644 index 00000000000..132a9d19da7 --- /dev/null +++ b/static/bidder-info/ccx.yaml @@ -0,0 +1,14 @@ +endpoint: "https://delivery.clickonometrics.pl/ortb/prebid/bid" +maintainer: + email: "it@clickonometrics.pl" +gvlVendorID: 773 +capabilities: + site: + mediaTypes: + - banner + - video +userSync: + # Clickonometrics 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 diff --git a/static/bidder-info/coinzilla.yaml b/static/bidder-info/coinzilla.yaml index dc63d32cd23..4d6ce4381d3 100644 --- a/static/bidder-info/coinzilla.yaml +++ b/static/bidder-info/coinzilla.yaml @@ -1,3 +1,4 @@ +endpoint: "http://request-global.czilladx.com/serve/prebid-server.php" maintainer: email: "technical@sevio.com" capabilities: diff --git a/static/bidder-info/colossus.yaml b/static/bidder-info/colossus.yaml index 11189c62cf3..c19a2326f42 100644 --- a/static/bidder-info/colossus.yaml +++ b/static/bidder-info/colossus.yaml @@ -1,3 +1,4 @@ +endpoint: "http://colossusssp.com/?c=o&m=rtb" maintainer: email: "support@huddledmasses.com" capabilities: @@ -12,4 +13,4 @@ capabilities: 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 + userMacro: "[UID]" diff --git a/static/bidder-info/compass.yaml b/static/bidder-info/compass.yaml index 5944b27eb5a..c25c4be0862 100644 --- a/static/bidder-info/compass.yaml +++ b/static/bidder-info/compass.yaml @@ -1,3 +1,4 @@ +endpoint: "http://sa-lb.deliverimp.com/pserver" maintainer: email: "sa-support@brightcom.com" gvlVendorID: 883 diff --git a/static/bidder-info/connectad.yaml b/static/bidder-info/connectad.yaml index 63cf05427de..d9bf283c1ac 100644 --- a/static/bidder-info/connectad.yaml +++ b/static/bidder-info/connectad.yaml @@ -1,3 +1,4 @@ +endpoint: "http://bidder.connectad.io/API?src=pbs" maintainer: email: "support@connectad.io" gvlVendorID: 138 diff --git a/static/bidder-info/consumable.yaml b/static/bidder-info/consumable.yaml index 5200c738200..e1c4fc9b986 100644 --- a/static/bidder-info/consumable.yaml +++ b/static/bidder-info/consumable.yaml @@ -1,5 +1,6 @@ +endpoint: "https://e.serverbid.com/api/v2" maintainer: - email: "naffis@consumable.com" + email: "prebid@consumable.com" gvlVendorID: 591 capabilities: app: diff --git a/static/bidder-info/conversant.yaml b/static/bidder-info/conversant.yaml index 1d298f8b582..da003b25c83 100644 --- a/static/bidder-info/conversant.yaml +++ b/static/bidder-info/conversant.yaml @@ -1,3 +1,4 @@ +endpoint: "http://api.hb.ad.cpe.dotomi.com/cvx/server/hb/ortb/25" maintainer: email: "CNVR_PublisherIntegration@conversantmedia.com" gvlVendorID: 24 diff --git a/static/bidder-info/cpmstar.yaml b/static/bidder-info/cpmstar.yaml index 46a7451bd1c..7c54ba53ca2 100644 --- a/static/bidder-info/cpmstar.yaml +++ b/static/bidder-info/cpmstar.yaml @@ -1,3 +1,4 @@ +endpoint: "https://server.cpmstar.com/openrtbbidrq.aspx" maintainer: email: "prebid@cpmstar.com" capabilities: @@ -12,4 +13,4 @@ capabilities: 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 + userMacro: "$UID" diff --git a/static/bidder-info/criteo.yaml b/static/bidder-info/criteo.yaml index 5eb7eb37512..a522a14468d 100644 --- a/static/bidder-info/criteo.yaml +++ b/static/bidder-info/criteo.yaml @@ -1,3 +1,4 @@ +endpoint: "https://bidder.criteo.com/cdb?profileId=230" maintainer: email: "prebid@criteo.com" gvlVendorID: 91 @@ -12,4 +13,4 @@ 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 + - redirect diff --git a/static/bidder-info/datablocks.yaml b/static/bidder-info/datablocks.yaml index 415dbc3546e..418b0cabc1e 100644 --- a/static/bidder-info/datablocks.yaml +++ b/static/bidder-info/datablocks.yaml @@ -1,3 +1,4 @@ +endpoint: "http://{{.Host}}/openrtb2?sid={{.SourceId}}" maintainer: email: "prebid@datablocks.net" capabilities: diff --git a/static/bidder-info/decenterads.yaml b/static/bidder-info/decenterads.yaml index c9b349cd78a..4324bbcac86 100644 --- a/static/bidder-info/decenterads.yaml +++ b/static/bidder-info/decenterads.yaml @@ -1,3 +1,4 @@ +endpoint: "http://supply.decenterads.com/?c=o&m=rtb" maintainer: email: "support@decenterads.com" modifyingVastXmlAllowed: true diff --git a/static/bidder-info/deepintent.yaml b/static/bidder-info/deepintent.yaml index 10ef458f133..95aba4207a1 100644 --- a/static/bidder-info/deepintent.yaml +++ b/static/bidder-info/deepintent.yaml @@ -1,3 +1,4 @@ +endpoint: "https://prebid.deepintent.com/prebid" maintainer: email: "prebiddev@deepintent.com" gvlVendorID: 541 @@ -12,3 +13,4 @@ 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/dianomi.yaml b/static/bidder-info/dianomi.yaml new file mode 100644 index 00000000000..1129e3c659c --- /dev/null +++ b/static/bidder-info/dianomi.yaml @@ -0,0 +1,23 @@ +endpoint: "https://www-prebid.dianomi.com/cgi-bin/smartads_prebid.pl" +disabled: true +maintainer: + email: 'prebid-maintainer@dianomi.com' +gvlVendorID: 885 +capabilities: + app: + mediaTypes: + - banner + - native + - video + site: + mediaTypes: + - banner + - native + - video +userSync: + iframe: + url: 'https://www-prebid.dianomi.com/prebid/usersync/index.html?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}' + userMacro: '$UID' + redirect: + url: 'https://data.dianomi.com/frontend/usync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}' + userMacro: '$UID' diff --git a/static/bidder-info/dmx.yaml b/static/bidder-info/dmx.yaml index fa31886d229..d88d54e38c5 100644 --- a/static/bidder-info/dmx.yaml +++ b/static/bidder-info/dmx.yaml @@ -1,3 +1,4 @@ +endpoint: "https://dmx-direct.districtm.io/b/v2" maintainer: email: "prebid@districtm.net" gvlVendorID: 144 @@ -14,4 +15,4 @@ 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 + - redirect diff --git a/static/bidder-info/e_volution.yaml b/static/bidder-info/e_volution.yaml index ddfef3dff36..b9d5532ca04 100644 --- a/static/bidder-info/e_volution.yaml +++ b/static/bidder-info/e_volution.yaml @@ -1,3 +1,4 @@ +endpoint: "http://service.e-volution.ai/pbserver" maintainer: email: "admin@e-volution.ai" gvlVendorID: 957 @@ -16,3 +17,4 @@ 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 a4cbd095934..0d8aa7f3a6d 100644 --- a/static/bidder-info/emx_digital.yaml +++ b/static/bidder-info/emx_digital.yaml @@ -1,3 +1,4 @@ +endpoint: "https://hb.emxdgt.com" maintainer: email: "adops@emxdigital.com" gvlVendorID: 183 diff --git a/static/bidder-info/engagebdr.yaml b/static/bidder-info/engagebdr.yaml index 0f9cc12fd89..8218040c605 100644 --- a/static/bidder-info/engagebdr.yaml +++ b/static/bidder-info/engagebdr.yaml @@ -1,3 +1,4 @@ +endpoint: "http://dsp.bnmla.com/hb" maintainer: email: "tech@engagebdr.com" capabilities: diff --git a/static/bidder-info/engagebdr_ortb.yaml b/static/bidder-info/engagebdr_ortb.yaml index fd2074f9d63..fa6e5bffb6d 100644 --- a/static/bidder-info/engagebdr_ortb.yaml +++ b/static/bidder-info/engagebdr_ortb.yaml @@ -1,6 +1,6 @@ +endpoint: "https://dsp.bnmla.com/bid?sspid=1000204" maintainer: email: "na@playwire.com" -gvlVendorID: 62 capabilities: app: mediaTypes: diff --git a/static/bidder-info/eplanning.yaml b/static/bidder-info/eplanning.yaml index 960244446aa..614d0f3b4c6 100644 --- a/static/bidder-info/eplanning.yaml +++ b/static/bidder-info/eplanning.yaml @@ -1,3 +1,4 @@ +endpoint: "http://rtb.e-planning.net/pbs/1" maintainer: email: "producto@e-planning.net" gvlVendorID: 90 diff --git a/static/bidder-info/epom.yaml b/static/bidder-info/epom.yaml index 991944ea35f..dc3b9908bca 100644 --- a/static/bidder-info/epom.yaml +++ b/static/bidder-info/epom.yaml @@ -1,3 +1,5 @@ +endpoint: "https://an.epom.com/ortb" +disabled: true maintainer: email: "support@epom.com" modifyingVastXmlAllowed: true diff --git a/static/bidder-info/freewheel-ssp.yaml b/static/bidder-info/freewheel-ssp.yaml new file mode 100644 index 00000000000..f013445a820 --- /dev/null +++ b/static/bidder-info/freewheel-ssp.yaml @@ -0,0 +1,16 @@ +endpoint: "https://ads.stickyadstv.com/openrtb/dsp" +maintainer: + email: prebid-maintainer@freewheel.com +gvlVendorID: 285 +modifyingVastXmlAllowed: true +capabilities: + app: + mediaTypes: + - video + site: + mediaTypes: + - video +userSync: + iframe: + url: "https://ads.stickyadstv.com/pbs-user-sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}}" + userMacro: "{viewerid}" diff --git a/static/bidder-info/freewheelssp.yaml b/static/bidder-info/freewheelssp.yaml new file mode 100644 index 00000000000..5cef0de13b3 --- /dev/null +++ b/static/bidder-info/freewheelssp.yaml @@ -0,0 +1,16 @@ +endpoint: "https://ads.stickyadstv.com/openrtb/dsp" +maintainer: + email: prebid-maintainer@freewheel.com +gvlVendorID: 285 +modifyingVastXmlAllowed: true +capabilities: + app: + mediaTypes: + - video + site: + mediaTypes: + - video +userSync: + iframe: + url: "https://ads.stickyadstv.com/pbs-user-sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}}" + userMacro: "{viewerid}" \ No newline at end of file diff --git a/static/bidder-info/gamma.yaml b/static/bidder-info/gamma.yaml index 4a79fafb0fd..7563dd40765 100644 --- a/static/bidder-info/gamma.yaml +++ b/static/bidder-info/gamma.yaml @@ -1,3 +1,4 @@ +endpoint: "https://hb.gammaplatform.com/adx/request/" maintainer: email: "support@gammassp.com" capabilities: @@ -9,4 +10,4 @@ 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 + - iframe diff --git a/static/bidder-info/gamoshi.yaml b/static/bidder-info/gamoshi.yaml index c72d1770082..231d3a86f44 100644 --- a/static/bidder-info/gamoshi.yaml +++ b/static/bidder-info/gamoshi.yaml @@ -1,3 +1,4 @@ +endpoint: "https://rtb.gamoshi.io" maintainer: email: "dev@gamoshi.com" gvlVendorID: 644 diff --git a/static/bidder-info/grid.yaml b/static/bidder-info/grid.yaml index b6c13bc7000..bf1832c9590 100644 --- a/static/bidder-info/grid.yaml +++ b/static/bidder-info/grid.yaml @@ -1,3 +1,4 @@ +endpoint: "https://grid.bidswitch.net/sp_bid?sp=prebid" maintainer: email: "grid-tech@themediagrid.com" gvlVendorID: 686 @@ -6,10 +7,12 @@ capabilities: mediaTypes: - banner - video + - native site: mediaTypes: - banner - video + - native userSync: redirect: url: "https://x.bidswitch.net/check_uuid/{{.RedirectURL}}?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}" diff --git a/static/bidder-info/groupm.yaml b/static/bidder-info/groupm.yaml index 9e487ad50a8..189527d5471 100644 --- a/static/bidder-info/groupm.yaml +++ b/static/bidder-info/groupm.yaml @@ -1,3 +1,4 @@ +endpoint: "https://hbopenbid.pubmatic.com/translator?source=prebid-server" maintainer: email: "header-bidding@pubmatic.com" gvlVendorID: 98 @@ -16,3 +17,4 @@ userSync: userMacro: "" # groupm appends the user id to end of the redirect url and does not utilize a macro + diff --git a/static/bidder-info/gumgum.yaml b/static/bidder-info/gumgum.yaml index 34b6f47d39e..f7e782e40df 100644 --- a/static/bidder-info/gumgum.yaml +++ b/static/bidder-info/gumgum.yaml @@ -1,3 +1,4 @@ +endpoint: "https://g2.gumgum.com/providers/prbds2s/bid" maintainer: email: "prebid@gumgum.com" gvlVendorID: 61 diff --git a/static/bidder-info/gumgum_ortb.yaml b/static/bidder-info/gumgum_ortb.yaml index fd2074f9d63..05b26f3f1b4 100644 --- a/static/bidder-info/gumgum_ortb.yaml +++ b/static/bidder-info/gumgum_ortb.yaml @@ -1,6 +1,6 @@ +endpoint: "https://g2.gumgum.com/zones/8ylgv2wd/bid" maintainer: email: "na@playwire.com" -gvlVendorID: 62 capabilities: app: mediaTypes: diff --git a/static/bidder-info/huaweiads.yaml b/static/bidder-info/huaweiads.yaml index a77bf4e0a74..8fe35b4e012 100644 --- a/static/bidder-info/huaweiads.yaml +++ b/static/bidder-info/huaweiads.yaml @@ -1,3 +1,5 @@ +endpoint: "https://acd.op.hicloud.com/ppsadx/getResult" +disabled: true maintainer: email: hwads@huawei.com gvlVendorID: 856 @@ -6,4 +8,5 @@ capabilities: app: mediaTypes: - banner - - native \ No newline at end of file + - native + - video diff --git a/static/bidder-info/impactify.yaml b/static/bidder-info/impactify.yaml index c0ca61ae4b6..ec2cb7276f2 100644 --- a/static/bidder-info/impactify.yaml +++ b/static/bidder-info/impactify.yaml @@ -1,3 +1,4 @@ +endpoint: "https://sonic.impactify.media/bidder" maintainer: email: "support@impactify.io" gvlVendorID: 606 diff --git a/static/bidder-info/improvedigital.yaml b/static/bidder-info/improvedigital.yaml index 649246abf09..4a990e24929 100644 --- a/static/bidder-info/improvedigital.yaml +++ b/static/bidder-info/improvedigital.yaml @@ -1,3 +1,4 @@ +endpoint: "http://ad.360yield.com/pbs" maintainer: email: "hb@azerion.com" gvlVendorID: 253 @@ -16,3 +17,4 @@ userSync: redirect: url: "https://ad.360yield.com/server_match?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}}" userMacro: "{PUB_USER_ID}" +endpointCompression: "GZIP" \ No newline at end of file diff --git a/static/bidder-info/infytv.yaml b/static/bidder-info/infytv.yaml new file mode 100644 index 00000000000..a262f3a6720 --- /dev/null +++ b/static/bidder-info/infytv.yaml @@ -0,0 +1,10 @@ +endpoint: "https://nxs.infy.tv/pbs/openrtb" +maintainer: + email: "tech+hb@infy.tv" +capabilities: + app: + mediaTypes: + - video + site: + mediaTypes: + - video diff --git a/static/bidder-info/inmobi.yaml b/static/bidder-info/inmobi.yaml index 43040658964..ad3562de64b 100644 --- a/static/bidder-info/inmobi.yaml +++ b/static/bidder-info/inmobi.yaml @@ -1,3 +1,4 @@ +endpoint: "https://api.w.inmobi.com/showad/openrtb/bidder/prebid" maintainer: email: "technology-irv@inmobi.com" gvlVendorID: 333 @@ -15,3 +16,4 @@ 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/interactiveoffers.yaml b/static/bidder-info/interactiveoffers.yaml index 70e810f5fda..040d9c02eb6 100644 --- a/static/bidder-info/interactiveoffers.yaml +++ b/static/bidder-info/interactiveoffers.yaml @@ -1,3 +1,4 @@ +endpoint: "https://prebid-server.ioadx.com/bidRequest/?partnerId={{.AccountID}}" maintainer: email: "dev@interactiveoffers.com" capabilities: diff --git a/static/bidder-info/invibes.yaml b/static/bidder-info/invibes.yaml index 6b7cd6a1f3c..2e004f19958 100644 --- a/static/bidder-info/invibes.yaml +++ b/static/bidder-info/invibes.yaml @@ -1,3 +1,4 @@ +endpoint: "https://{{.ZoneID}}.videostep.com/bid/ServerBidAdContent" maintainer: email: "system_operations@invibes.com" gvlVendorID: 436 @@ -9,4 +10,5 @@ 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 + - iframe +endpointCompression: "GZIP" \ No newline at end of file diff --git a/static/bidder-info/iqzone.yaml b/static/bidder-info/iqzone.yaml index 254caa8c6e3..3465cfabcb0 100644 --- a/static/bidder-info/iqzone.yaml +++ b/static/bidder-info/iqzone.yaml @@ -1,3 +1,4 @@ +endpoint: "http://smartssp-us-east.iqzone.com/pserver" maintainer: email: "smartssp@iqzone.com" capabilities: @@ -12,4 +13,4 @@ capabilities: - banner - video - native - + diff --git a/static/bidder-info/ix.yaml b/static/bidder-info/ix.yaml index 8c46525caf0..9ac7dba32dc 100644 --- a/static/bidder-info/ix.yaml +++ b/static/bidder-info/ix.yaml @@ -1,3 +1,4 @@ +disabled: true maintainer: email: "pdu-supply-prebid@indexexchange.com" gvlVendorID: 10 @@ -18,4 +19,6 @@ userSync: redirect: url: "https://ssum.casalemedia.com/usermatchredir?s=194962&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb={{.RedirectURL}}" userMacro: "" + iframe: + url: "https://ssum-sec.casalemedia.com/usermatch?s=184674&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb={{.RedirectURL}}" # ix appends the user id to end of the redirect url and does not utilize a macro diff --git a/static/bidder-info/janet.yaml b/static/bidder-info/janet.yaml index a6e98f02c96..0a3abb53af4 100644 --- a/static/bidder-info/janet.yaml +++ b/static/bidder-info/janet.yaml @@ -1,3 +1,4 @@ +endpoint: "http://ghb.bidder.jmgads.com/pbs/ortb" maintainer: email: "info@thejmg.com" capabilities: diff --git a/static/bidder-info/jixie.yaml b/static/bidder-info/jixie.yaml index 0553f1f7393..88edd7877a8 100644 --- a/static/bidder-info/jixie.yaml +++ b/static/bidder-info/jixie.yaml @@ -1,3 +1,4 @@ +endpoint: "https://hb.jixie.io/v2/hbsvrpost" maintainer: email: contact@jixie.io modifyingVastXmlAllowed: true diff --git a/static/bidder-info/kargo.yaml b/static/bidder-info/kargo.yaml new file mode 100644 index 00000000000..e73eabe9cdf --- /dev/null +++ b/static/bidder-info/kargo.yaml @@ -0,0 +1,16 @@ +endpoint: "https://krk.kargo.com/api/v1/openrtb" +maintainer: + email: "kraken@kargo.com" +gvlVendorID: 972 +modifyingVastXmlAllowed: true +capabilities: + site: + mediaTypes: + - banner + - video + - native +userSync: + redirect: + url: "https://crb.kargo.com/api/v1/dsync/PrebidServer?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}}" + userMacro: "$UID" +endpointCompression: "GZIP" \ No newline at end of file diff --git a/static/bidder-info/kayzen.yaml b/static/bidder-info/kayzen.yaml index 45fd43c5a11..760bd3c3a4f 100644 --- a/static/bidder-info/kayzen.yaml +++ b/static/bidder-info/kayzen.yaml @@ -1,3 +1,4 @@ +endpoint: "https://bids-{{.ZoneID}}.bidder.kayzen.io/?exchange={{.AccountID}}" maintainer: email: "platform-dev@kayzen.io" gvlVendorID: 528 diff --git a/static/bidder-info/kidoz.yaml b/static/bidder-info/kidoz.yaml index e2a9eee3fc7..dd9eb3c668b 100644 --- a/static/bidder-info/kidoz.yaml +++ b/static/bidder-info/kidoz.yaml @@ -1,3 +1,4 @@ +endpoint: "http://prebid-adapter.kidoz.net/openrtb2/auction?src=prebid-server" maintainer: email: prebid-support@kidoz.net capabilities: diff --git a/static/bidder-info/krushmedia.yaml b/static/bidder-info/krushmedia.yaml index 82055a29988..75d29b5ebe7 100644 --- a/static/bidder-info/krushmedia.yaml +++ b/static/bidder-info/krushmedia.yaml @@ -1,3 +1,4 @@ +endpoint: "http://ads4.krushmedia.com/?c=rtb&m=req&key={{.AccountID}}" maintainer: email: "adapter@krushmedia.com" capabilities: diff --git a/static/bidder-info/kubient.yaml b/static/bidder-info/kubient.yaml index bd294372d93..15c2708bcb3 100644 --- a/static/bidder-info/kubient.yaml +++ b/static/bidder-info/kubient.yaml @@ -1,3 +1,4 @@ +endpoint: "https://kssp.kbntx.ch/prebid" maintainer: email: "prebid@kubient.com" capabilities: diff --git a/static/bidder-info/lockerdome.yaml b/static/bidder-info/lockerdome.yaml index cdb0434935d..cfefb2f995b 100644 --- a/static/bidder-info/lockerdome.yaml +++ b/static/bidder-info/lockerdome.yaml @@ -1,3 +1,4 @@ +endpoint: "https://lockerdome.com/ladbid/prebidserver/openrtb2" maintainer: email: "bidding@lockerdome.com" capabilities: @@ -25,4 +26,4 @@ userSync: # url: "https://lockerdome.com/usync/prebidserver?pid=<<PlatformID>>&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}" # userMacro: "{{uid}}" supports: - - redirect \ No newline at end of file + - redirect diff --git a/static/bidder-info/logicad.yaml b/static/bidder-info/logicad.yaml index 4748cc75f09..5853ab020f3 100644 --- a/static/bidder-info/logicad.yaml +++ b/static/bidder-info/logicad.yaml @@ -1,3 +1,4 @@ +endpoint: "https://pbs.ladsp.com/adrequest/prebidserver" maintainer: email: "prebid@so-netmedia.jp" capabilities: @@ -9,6 +10,6 @@ capabilities: - banner userSync: redirect: - url: "https://cr-p31.ladsp.jp/cookiesender/31?r=true&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ru={{.RedirectURL}}" + url: "https://cr-p31.ladsp.com/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 216fae9becf..79e19a4393f 100644 --- a/static/bidder-info/lunamedia.yaml +++ b/static/bidder-info/lunamedia.yaml @@ -1,3 +1,4 @@ +endpoint: "http://api.lunamedia.io/xp/get?pubid={{.PublisherID}}" maintainer: email: "josh@lunamedia.io" capabilities: diff --git a/static/bidder-info/madvertise.yaml b/static/bidder-info/madvertise.yaml index 6156940e6d5..262987a6654 100644 --- a/static/bidder-info/madvertise.yaml +++ b/static/bidder-info/madvertise.yaml @@ -1,3 +1,4 @@ +endpoint: "https://mobile.mng-ads.com/bidrequest{{.ZoneID}}" maintainer: email: "support@madvertise.com" gvlVendorID: 153 diff --git a/static/bidder-info/marsmedia.yaml b/static/bidder-info/marsmedia.yaml index 3080962d37c..7ba4ea6ee7a 100644 --- a/static/bidder-info/marsmedia.yaml +++ b/static/bidder-info/marsmedia.yaml @@ -1,3 +1,4 @@ +endpoint: "https://bid306.rtbsrv.com/bidder/?bid=f3xtet" maintainer: email: "prebid@mars.media" gvlVendorID: 776 diff --git a/static/bidder-info/mediafuse.yaml b/static/bidder-info/mediafuse.yaml index 3c69bfeacaf..b78b21e16ea 100644 --- a/static/bidder-info/mediafuse.yaml +++ b/static/bidder-info/mediafuse.yaml @@ -1,17 +1,16 @@ +endpoint: "http://ib.adnxs.com/openrtb2" maintainer: email: "support@mediafuse.com" -gvlVendorID: 411 capabilities: app: mediaTypes: - banner - video + - native site: mediaTypes: - banner - video + - native 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 + key: "adnxs" diff --git a/static/bidder-info/medianet.yaml b/static/bidder-info/medianet.yaml index b4b5a21161a..ea47de2b11d 100644 --- a/static/bidder-info/medianet.yaml +++ b/static/bidder-info/medianet.yaml @@ -1,3 +1,5 @@ +endpoint: "https://prebid-adapter.media.net/rtb/pb/prebids2s" +extra_info: "https://medianet.golang.pbs.com" maintainer: email: "prebid-support@media.net" gvlVendorID: 142 diff --git a/static/bidder-info/mgid.yaml b/static/bidder-info/mgid.yaml index a9e20d631c0..8100fb4d348 100644 --- a/static/bidder-info/mgid.yaml +++ b/static/bidder-info/mgid.yaml @@ -1,3 +1,4 @@ +endpoint: "https://prebid.mgid.com/prebid/" maintainer: email: "prebid@mgid.com" gvlVendorID: 358 @@ -14,3 +15,4 @@ userSync: redirect: url: "https://cm.mgid.com/m?cdsp=363893&adu={{.RedirectURL}}" userMacro: "{muidn}" + diff --git a/static/bidder-info/mobfoxpb.yaml b/static/bidder-info/mobfoxpb.yaml index ba3bb60d554..0a1e78ec23c 100644 --- a/static/bidder-info/mobfoxpb.yaml +++ b/static/bidder-info/mobfoxpb.yaml @@ -1,3 +1,4 @@ +endpoint: "http://bes.mobfox.com/?c=__route__&m=__method__&key=__key__" maintainer: email: "platform@mobfox.com" capabilities: diff --git a/static/bidder-info/mobilefuse.yaml b/static/bidder-info/mobilefuse.yaml index 18a90a8866e..ac0f89295ed 100644 --- a/static/bidder-info/mobilefuse.yaml +++ b/static/bidder-info/mobilefuse.yaml @@ -1,3 +1,4 @@ +endpoint: "http://mfx.mobilefuse.com/openrtb?pub_id={{.PublisherID}}" maintainer: email: prebid@mobilefuse.com gvlVendorID: 909 @@ -6,3 +7,4 @@ capabilities: mediaTypes: - banner - video +endpointCompression: "GZIP" \ No newline at end of file diff --git a/static/bidder-info/nanointeractive.yaml b/static/bidder-info/nanointeractive.yaml index 3b4db38dff8..639c5450d2e 100644 --- a/static/bidder-info/nanointeractive.yaml +++ b/static/bidder-info/nanointeractive.yaml @@ -1,3 +1,4 @@ +endpoint: "https://ad.audiencemanager.de/hbs" maintainer: email: "development@nanointeractive.com" gvlVendorID: 72 diff --git a/static/bidder-info/nextmillennium.yaml b/static/bidder-info/nextmillennium.yaml index 2bb723bf4a6..e6f498ca159 100644 --- a/static/bidder-info/nextmillennium.yaml +++ b/static/bidder-info/nextmillennium.yaml @@ -1,5 +1,6 @@ +endpoint: "https://pbs.nextmillmedia.com/openrtb2/auction" maintainer: - email: "appdevs@nextmillennium.io" + email: "accountmanagers@nextmillennium.io" capabilities: app: mediaTypes: diff --git a/static/bidder-info/ninthdecimal.yaml b/static/bidder-info/ninthdecimal.yaml index db1392d78c8..8a4d7b8d299 100755 --- a/static/bidder-info/ninthdecimal.yaml +++ b/static/bidder-info/ninthdecimal.yaml @@ -1,3 +1,4 @@ +endpoint: "http://rtb.ninthdecimal.com/xp/get?pubid={{.PublisherID}}" maintainer: email: "abudig@ninthdecimal.com" capabilities: diff --git a/static/bidder-info/nobid.yaml b/static/bidder-info/nobid.yaml index 6fa65fe93bc..7c33998bf20 100644 --- a/static/bidder-info/nobid.yaml +++ b/static/bidder-info/nobid.yaml @@ -1,3 +1,4 @@ +endpoint: "https://ads.servenobid.com/ortb_adreq?tek=pbs&ver=1" maintainer: email: "developers@nobid.io" gvlVendorID: 816 diff --git a/static/bidder-info/oftmedia.yaml b/static/bidder-info/oftmedia.yaml new file mode 100644 index 00000000000..587a6643109 --- /dev/null +++ b/static/bidder-info/oftmedia.yaml @@ -0,0 +1,18 @@ +endpoint: "http://ghb.ortb.152media.info/pbs/ortb" +maintainer: + email: "admin@152media.com" +gvlVendorID: 1111 +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video +userSync: + # oftmedia 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-info/onetag.yaml b/static/bidder-info/onetag.yaml index b0aeeafe347..3677921ee5c 100644 --- a/static/bidder-info/onetag.yaml +++ b/static/bidder-info/onetag.yaml @@ -1,3 +1,4 @@ +endpoint: "https://prebid-server.onetag-sys.com/prebid-server/{{.PublisherID}}" maintainer: email: devops@onetag.com gvlVendorID: 241 @@ -14,5 +15,6 @@ capabilities: - native userSync: iframe: - url: "https://onetag-sys.com/usync/?redir={{.RedirectURL}}" + url: "https://onetag-sys.com/usync/?redir={{.RedirectURL}}&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}" userMacro: "${USER_TOKEN}" +endpointCompression: "GZIP" \ No newline at end of file diff --git a/static/bidder-info/openweb.yaml b/static/bidder-info/openweb.yaml index 5886c4a7845..9272719cf19 100644 --- a/static/bidder-info/openweb.yaml +++ b/static/bidder-info/openweb.yaml @@ -1,3 +1,4 @@ +endpoint: "http://ghb.spotim.market/pbs/ortb" maintainer: email: "monetization@openweb.com" gvlVendorID: 280 @@ -14,4 +15,4 @@ 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 + - redirect diff --git a/static/bidder-info/openx.yaml b/static/bidder-info/openx.yaml index 96add482a07..9837b5dc92c 100644 --- a/static/bidder-info/openx.yaml +++ b/static/bidder-info/openx.yaml @@ -1,3 +1,4 @@ +endpoint: "http://rtb.openx.net/prebid" maintainer: email: "prebid@openx.com" gvlVendorID: 69 @@ -18,3 +19,4 @@ 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 d28492b139a..fd16463f719 100644 --- a/static/bidder-info/operaads.yaml +++ b/static/bidder-info/operaads.yaml @@ -1,3 +1,4 @@ +endpoint: "https://s.adx.opera.com/ortb/v2/{{.PublisherID}}?ep={{.AccountID}}" maintainer: email: adtech-prebid-group@opera.com modifyingVastXmlAllowed: true diff --git a/static/bidder-info/orbidder.yaml b/static/bidder-info/orbidder.yaml index 467093c1256..b7891af3195 100644 --- a/static/bidder-info/orbidder.yaml +++ b/static/bidder-info/orbidder.yaml @@ -1,3 +1,4 @@ +endpoint: "https://orbidder.otto.de/openrtb2" maintainer: email: "realtime-siggi@otto.de" gvlVendorID: 559 diff --git a/static/bidder-info/outbrain.yaml b/static/bidder-info/outbrain.yaml index d24c603a6dc..69a12ca6dfa 100644 --- a/static/bidder-info/outbrain.yaml +++ b/static/bidder-info/outbrain.yaml @@ -1,3 +1,4 @@ +endpoint: "https://prebidtest.zemanta.com/api/bidder/prebidtest/bid/" maintainer: email: prog-ops-team@outbrain.com gvlVendorID: 164 diff --git a/static/bidder-info/pangle.yaml b/static/bidder-info/pangle.yaml index 3ac3a0ced0f..b70315679ef 100644 --- a/static/bidder-info/pangle.yaml +++ b/static/bidder-info/pangle.yaml @@ -1,3 +1,4 @@ +disabled: true maintainer: email: pangle_dsp@bytedance.com modifyingVastXmlAllowed: true diff --git a/static/bidder-info/pgam.yaml b/static/bidder-info/pgam.yaml index 16d25850fad..0c9b2f008b6 100644 --- a/static/bidder-info/pgam.yaml +++ b/static/bidder-info/pgam.yaml @@ -1,3 +1,4 @@ +endpoint: "http://ghb.pgamssp.com/pbs/ortb" maintainer: email: "ppatel@pgammedia.com" capabilities: diff --git a/static/bidder-info/playwire.yaml b/static/bidder-info/playwire.yaml index 602f7d38b78..8c25833a376 100644 --- a/static/bidder-info/playwire.yaml +++ b/static/bidder-info/playwire.yaml @@ -1,6 +1,6 @@ +endpoint: "https://grid.bidswitch.net/sp_bid?sp=prebid" maintainer: email: "na@playwire.com" -gvlVendorID: 686 capabilities: app: mediaTypes: diff --git a/static/bidder-info/playwire_ortb.yaml b/static/bidder-info/playwire_ortb.yaml index 602f7d38b78..8c25833a376 100644 --- a/static/bidder-info/playwire_ortb.yaml +++ b/static/bidder-info/playwire_ortb.yaml @@ -1,6 +1,6 @@ +endpoint: "https://grid.bidswitch.net/sp_bid?sp=prebid" maintainer: email: "na@playwire.com" -gvlVendorID: 686 capabilities: app: mediaTypes: diff --git a/static/bidder-info/pubmatic.yaml b/static/bidder-info/pubmatic.yaml index 1688e639569..9841e8992e1 100644 --- a/static/bidder-info/pubmatic.yaml +++ b/static/bidder-info/pubmatic.yaml @@ -1,3 +1,4 @@ +endpoint: "https://hbopenbid.pubmatic.com/translator?source=prebid-server" maintainer: email: "header-bidding@pubmatic.com" gvlVendorID: 76 @@ -6,10 +7,12 @@ capabilities: mediaTypes: - banner - video + - native site: mediaTypes: - banner - video + - native userSync: iframe: url: "https://ads.pubmatic.com/AdServer/js/user_sync.html?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&predirect={{.RedirectURL}}" diff --git a/static/bidder-info/pubnative.yaml b/static/bidder-info/pubnative.yaml index 44bba23d7f3..00e80f41643 100644 --- a/static/bidder-info/pubnative.yaml +++ b/static/bidder-info/pubnative.yaml @@ -1,3 +1,4 @@ +endpoint: "http://dsp.pubnative.net/bid/v1/request" maintainer: email: product@pubnative.net gvlVendorID: 512 diff --git a/static/bidder-info/pulsepoint.yaml b/static/bidder-info/pulsepoint.yaml index 635ceb46006..762dbbb0c73 100644 --- a/static/bidder-info/pulsepoint.yaml +++ b/static/bidder-info/pulsepoint.yaml @@ -1,3 +1,4 @@ +endpoint: "http://bid.contextweb.com/header/s/ortb/prebid-s2s" maintainer: email: "ExchangeTeam@pulsepoint.com" gvlVendorID: 81 diff --git a/static/bidder-info/pulsepoint_ortb.yaml b/static/bidder-info/pulsepoint_ortb.yaml index fd2074f9d63..3d28d0394b9 100644 --- a/static/bidder-info/pulsepoint_ortb.yaml +++ b/static/bidder-info/pulsepoint_ortb.yaml @@ -1,6 +1,6 @@ +endpoint: "http://rts.lga.contextweb.com/rt-seller/bid/openrtb/playwire" maintainer: email: "na@playwire.com" -gvlVendorID: 62 capabilities: app: mediaTypes: diff --git a/static/bidder-info/quantumdex.yaml b/static/bidder-info/quantumdex.yaml index 5ef240219fe..590737ac28b 100644 --- a/static/bidder-info/quantumdex.yaml +++ b/static/bidder-info/quantumdex.yaml @@ -1,3 +1,4 @@ +endpoint: "http://useast.quantumdex.io/auction/pbs" maintainer: email: "support@apacdex.com" modifyingVastXmlAllowed: false diff --git a/static/bidder-info/revcontent.yaml b/static/bidder-info/revcontent.yaml index 57e887565ce..a2e095a3e22 100644 --- a/static/bidder-info/revcontent.yaml +++ b/static/bidder-info/revcontent.yaml @@ -1,3 +1,5 @@ +endpoint: "https://trends.revcontent.com/rtb" +disabled: true maintainer: email: "developers@revcontent.com" gvlVendorID: 203 @@ -9,4 +11,4 @@ capabilities: site: mediaTypes: - banner - - native + - native \ No newline at end of file diff --git a/static/bidder-info/rhythmone.yaml b/static/bidder-info/rhythmone.yaml index 0ea79afc766..529eae12628 100644 --- a/static/bidder-info/rhythmone.yaml +++ b/static/bidder-info/rhythmone.yaml @@ -1,3 +1,4 @@ +endpoint: "http://tag.1rx.io/rmp" maintainer: email: "support@rhythmone.com" gvlVendorID: 36 @@ -13,4 +14,4 @@ capabilities: userSync: redirect: url: "https://sync.1rx.io/usersync2/rmphb?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}" - userMacro: "[RX_UUID]" + userMacro: "[RX_UUID]" \ No newline at end of file diff --git a/static/bidder-info/richaudience.yaml b/static/bidder-info/richaudience.yaml index 3e45dc0b362..d2f7abadeba 100644 --- a/static/bidder-info/richaudience.yaml +++ b/static/bidder-info/richaudience.yaml @@ -1,3 +1,4 @@ +endpoint: "http://ortb.richaudience.com/ortb/?bidder=pbs" maintainer: email: partnerintegrations@richaudience.com gvlVendorID: 108 @@ -15,3 +16,6 @@ userSync: iframe: url: "https://sync.richaudience.com/74889303289e27f327ad0c6de7be7264/?consentString={{.GDPRConsent}}&r={{.RedirectURL}}" userMacro: "[PDID]" + redirect: + url: "https://sync.richaudience.com/f7872c90c5d3791e2b51f7edce1a0a5d/?p=pbs&consentString={{.GDPRConsent}}&r={{.RedirectURL}}" + userMacro: "[PDID]" diff --git a/static/bidder-info/rtbhouse.yaml b/static/bidder-info/rtbhouse.yaml index 8d5ad3f03fb..6a116448f22 100644 --- a/static/bidder-info/rtbhouse.yaml +++ b/static/bidder-info/rtbhouse.yaml @@ -1,3 +1,4 @@ +endpoint: "http://prebidserver-s2s-ams.creativecdn.com/bidder/prebidserver/bids" maintainer: email: "prebid@rtbhouse.com" gvlVendorID: 16 @@ -9,4 +10,4 @@ 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 + - redirect diff --git a/static/bidder-info/rubicon.yaml b/static/bidder-info/rubicon.yaml index a9f65f6e441..b3de838c9fd 100644 --- a/static/bidder-info/rubicon.yaml +++ b/static/bidder-info/rubicon.yaml @@ -1,3 +1,5 @@ +endpoint: "http://exapi-us-east.rubiconproject.com/a/api/exchange.json" +disabled: true maintainer: email: "header-bidding@rubiconproject.com" gvlVendorID: 52 @@ -6,10 +8,12 @@ capabilities: mediaTypes: - banner - video + - native site: mediaTypes: - banner - video + - native 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. diff --git a/static/bidder-info/sa_lunamedia.yaml b/static/bidder-info/sa_lunamedia.yaml index f5c2cf78e20..568eb088397 100644 --- a/static/bidder-info/sa_lunamedia.yaml +++ b/static/bidder-info/sa_lunamedia.yaml @@ -1,3 +1,4 @@ +endpoint: "http://balancer.lmgssp.com/pserver" maintainer: email: "support@lunamedia.io" gvlVendorID: 998 diff --git a/static/bidder-info/seedingAlliance.yaml b/static/bidder-info/seedingAlliance.yaml new file mode 100644 index 00000000000..dee9fcd6026 --- /dev/null +++ b/static/bidder-info/seedingAlliance.yaml @@ -0,0 +1,13 @@ +endpoint: "https://b.nativendo.de/cds/rtb/bid?ssp=pbs" +maintainer: + email: tech@seeding-alliance.de +gvlVendorID: 371 +capabilities: + site: + mediaTypes: + - native + - banner +userSync: + redirect: + url: "https://dmp.nativendo.de/set-uuid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&redirect_url={{.RedirectURL}}" + userMacro: "$UID" diff --git a/static/bidder-info/sharethrough.yaml b/static/bidder-info/sharethrough.yaml index 51cca6d0576..0a8453d516f 100644 --- a/static/bidder-info/sharethrough.yaml +++ b/static/bidder-info/sharethrough.yaml @@ -1,3 +1,4 @@ +endpoint: "https://btlr.sharethrough.com/universal/v1?supply_id=FGMrCMMc" maintainer: email: pubgrowth.engineering@sharethrough.com gvlVendorID: 80 diff --git a/static/bidder-info/silvermob.yaml b/static/bidder-info/silvermob.yaml index 5f1e4809dd3..c6f6d8acde5 100644 --- a/static/bidder-info/silvermob.yaml +++ b/static/bidder-info/silvermob.yaml @@ -1,3 +1,4 @@ +endpoint: "http://{{.Host}}.silvermob.com/marketplace/api/dsp/bid/{{.ZoneID}}" maintainer: email: "support@silvermob.com" capabilities: @@ -5,4 +6,4 @@ capabilities: mediaTypes: - banner - video - - native \ No newline at end of file + - native diff --git a/static/bidder-info/smaato.yaml b/static/bidder-info/smaato.yaml index a070db1d7d3..1014e5a1274 100644 --- a/static/bidder-info/smaato.yaml +++ b/static/bidder-info/smaato.yaml @@ -1,3 +1,4 @@ +endpoint: "https://prebid.ad.smaato.net/oapi/prebid" maintainer: email: "prebid@smaato.com" gvlVendorID: 82 @@ -6,11 +7,14 @@ capabilities: mediaTypes: - banner - video + - native site: mediaTypes: - banner - video + - native userSync: redirect: url: "https://s.ad.smaato.net/c/?adExInit=p&redir={{.RedirectURL}}" userMacro: "$UID" + diff --git a/static/bidder-info/smartadserver.yaml b/static/bidder-info/smartadserver.yaml index a6b06d95aa7..48e88d94efa 100644 --- a/static/bidder-info/smartadserver.yaml +++ b/static/bidder-info/smartadserver.yaml @@ -1,3 +1,4 @@ +endpoint: "https://ssb-global.smartadserver.com" maintainer: email: "support@smartadserver.com" gvlVendorID: 45 @@ -6,11 +7,13 @@ capabilities: mediaTypes: - banner - video + - native site: mediaTypes: - banner - video + - native 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]" + userMacro: "[ssb_sync_pid]" \ No newline at end of file diff --git a/static/bidder-info/smarthub.yaml b/static/bidder-info/smarthub.yaml index 6932780f195..dcb65ede0ce 100644 --- a/static/bidder-info/smarthub.yaml +++ b/static/bidder-info/smarthub.yaml @@ -1,3 +1,4 @@ +endpoint: "http://{{.Host}}-prebid.smart-hub.io/?seat={{.AccountID}}&token={{.SourceId}}" maintainer: email: "support@smart-hub.io" capabilities: @@ -15,4 +16,4 @@ 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 + - redirect diff --git a/static/bidder-info/smartrtb.yaml b/static/bidder-info/smartrtb.yaml index d73afe0440c..8399f237177 100644 --- a/static/bidder-info/smartrtb.yaml +++ b/static/bidder-info/smartrtb.yaml @@ -1,3 +1,4 @@ +endpoint: "http://market-east.smrtb.com/json/publisher/rtb?pubid={{.PublisherID}}" maintainer: email: "engineering@smrtb.com" capabilities: @@ -13,3 +14,4 @@ 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 2704f007ca3..b36983bc7b1 100644 --- a/static/bidder-info/smartyads.yaml +++ b/static/bidder-info/smartyads.yaml @@ -1,3 +1,4 @@ +endpoint: "http://{{.Host}}.smartyads.com/bid?rtb_seat_id={{.SourceId}}&secret_key={{.AccountID}}" maintainer: email: "support@smartyads.com" capabilities: diff --git a/static/bidder-info/smilewanted.yaml b/static/bidder-info/smilewanted.yaml index e3d62bd5db6..44aff8a055a 100644 --- a/static/bidder-info/smilewanted.yaml +++ b/static/bidder-info/smilewanted.yaml @@ -1,3 +1,4 @@ +endpoint: "http://prebid-server.smilewanted.com" maintainer: email: "tech@smilewanted.com" gvlVendorID: 639 @@ -13,4 +14,4 @@ capabilities: userSync: redirect: url: "https://csync.smilewanted.com/getuid?source=prebid-server&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}" - userMacro: "$UID" + userMacro: "$UID" \ No newline at end of file diff --git a/static/bidder-info/sonobi.yaml b/static/bidder-info/sonobi.yaml index 9961d322083..6f9afc36b3f 100644 --- a/static/bidder-info/sonobi.yaml +++ b/static/bidder-info/sonobi.yaml @@ -1,3 +1,4 @@ +endpoint: "https://apex.go.sonobi.com/prebid?partnerid=71d9d3d8af" maintainer: email: "apex.prebid@sonobi.com" gvlVendorID: 104 diff --git a/static/bidder-info/sovrn.yaml b/static/bidder-info/sovrn.yaml index f26d2d93fc0..06dadcf9a05 100644 --- a/static/bidder-info/sovrn.yaml +++ b/static/bidder-info/sovrn.yaml @@ -1,3 +1,4 @@ +endpoint: "http://pbs.lijit.com/rtb/bid?src=prebid_server" maintainer: email: "sovrnoss@sovrn.com" gvlVendorID: 13 @@ -15,3 +16,7 @@ userSync: redirect: url: "https://ap.lijit.com/pixel?redir={{.RedirectURL}}" userMacro: "$UID" + iframe: + url: "https://ap.lijit.com/beacon/prebid-server/?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url={{.RedirectURL}}" + userMacro: "$UID" + \ No newline at end of file diff --git a/static/bidder-info/sspBC.yaml b/static/bidder-info/sspBC.yaml new file mode 100644 index 00000000000..572105d2407 --- /dev/null +++ b/static/bidder-info/sspBC.yaml @@ -0,0 +1,13 @@ +endpoint: "https://ssp.wp.pl/bidder/" +maintainer: + email: "prebid-dev@grupawp.pl" +gvlVendorID: 676 +capabilities: + site: + mediaTypes: + - banner +userSync: + default: iframe + iframe: + url: https://ssp.wp.pl/bidder/usersync?tcf=2&redirect={{.RedirectURL}} + userMacro: $UID diff --git a/static/bidder-info/streamkey.yaml b/static/bidder-info/streamkey.yaml index a13b71e588a..9e5d05abaec 100644 --- a/static/bidder-info/streamkey.yaml +++ b/static/bidder-info/streamkey.yaml @@ -1,3 +1,4 @@ +endpoint: "http://ghb.hb.streamkey.net/pbs/ortb" maintainer: email: "contact@streamkey.tv" capabilities: diff --git a/static/bidder-info/stroeerCore.yaml b/static/bidder-info/stroeerCore.yaml new file mode 100644 index 00000000000..32c78590bb8 --- /dev/null +++ b/static/bidder-info/stroeerCore.yaml @@ -0,0 +1,20 @@ +endpoint: "http://mhb.adscale.de/s2sdsh" +disabled: true +maintainer: + email: "help@cz.stroeer-labs.com" +gvlVendorID: 136 +capabilities: + app: + mediaTypes: + - banner + site: + mediaTypes: + - banner +userSync: + # for both user syncs, stroeerCore appends the user id to end of the redirect url and does not utilize a macro + iframe: + url: "https://js.adscale.de/pbsync.html?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&redirect={{.RedirectURL}}" + userMacro: "" + redirect: + url: "https://ih.adscale.de/uu?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&cburl={{.RedirectURL}}" + userMacro: "" \ No newline at end of file diff --git a/static/bidder-info/suntContent.yaml b/static/bidder-info/suntContent.yaml new file mode 100644 index 00000000000..e46cc4086e0 --- /dev/null +++ b/static/bidder-info/suntContent.yaml @@ -0,0 +1,13 @@ +endpoint: "https://b.suntcontent.se/cds/rtb/bid?ssp=pbs" +maintainer: + email: tech@seeding-alliance.de +gvlVendorID: 1097 +capabilities: + site: + mediaTypes: + - native + - banner +userSync: + redirect: + url: "https://dmp.suntcontent.se/set-uuid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&redirect_url={{.RedirectURL}}" + userMacro: "$UID" diff --git a/static/bidder-info/synacormedia.yaml b/static/bidder-info/synacormedia.yaml index 1769ab8282d..30f5308c517 100644 --- a/static/bidder-info/synacormedia.yaml +++ b/static/bidder-info/synacormedia.yaml @@ -1,3 +1,4 @@ +endpoint: "http://{{.Host}}.technoratimedia.com/openrtb/bids/{{.Host}}" maintainer: email: "eng-demand@imds.tv" capabilities: @@ -12,4 +13,4 @@ capabilities: userSync: iframe: url: "https://ad-cdn.technoratimedia.com/html/usersync.html?cb={{.RedirectURL}}" - userMacro: "[USER_ID]" + userMacro: "[USER_ID]" \ No newline at end of file diff --git a/static/bidder-info/taboola.yaml b/static/bidder-info/taboola.yaml new file mode 100644 index 00000000000..ef05d3114dd --- /dev/null +++ b/static/bidder-info/taboola.yaml @@ -0,0 +1,15 @@ +endpoint: "https://display.bidder.taboola.com/OpenRTB/TaboolaHB/auction" +maintainer: + email: ps-team@taboola.com +gvlVendorID: 42 +capabilities: + app: + mediaTypes: + - banner + site: + mediaTypes: + - banner +userSync: + redirect: + url: https://trc.taboola.com/sg/ps/1/cm?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}} + userMacro: "<TUID>" diff --git a/static/bidder-info/tappx.yaml b/static/bidder-info/tappx.yaml index 6a321af0727..02f0fe805d7 100644 --- a/static/bidder-info/tappx.yaml +++ b/static/bidder-info/tappx.yaml @@ -1,3 +1,4 @@ +endpoint: "http://{{.Host}}" maintainer: email: "tappx@tappx.com" gvlVendorID: 628 diff --git a/static/bidder-info/telaria.yaml b/static/bidder-info/telaria.yaml index 7d9501dc086..f3411509a22 100644 --- a/static/bidder-info/telaria.yaml +++ b/static/bidder-info/telaria.yaml @@ -1,3 +1,4 @@ +endpoint: "https://ads.tremorhub.com/ad/rtb/prebid" maintainer: email: "github@telaria.com" gvlVendorID: 202 @@ -11,4 +12,4 @@ capabilities: userSync: redirect: url: "https://pbs.publishers.tremorhub.com/pubsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&redir={{.RedirectURL}}" - userMacro: "[tvid]" + userMacro: "[tvid]" \ No newline at end of file diff --git a/static/bidder-info/trafficgate.yaml b/static/bidder-info/trafficgate.yaml index 1d9ff63cac9..61cfd84537f 100644 --- a/static/bidder-info/trafficgate.yaml +++ b/static/bidder-info/trafficgate.yaml @@ -1,3 +1,4 @@ +endpoint: "http://{{.Host}}.bc-plugin.com/?c=o&m=rtb" maintainer: email: "support@bidscube.com" modifyingVastXmlAllowed: true diff --git a/static/bidder-info/triplelift.yaml b/static/bidder-info/triplelift.yaml index 8a86428d56d..45811c0c868 100644 --- a/static/bidder-info/triplelift.yaml +++ b/static/bidder-info/triplelift.yaml @@ -1,3 +1,4 @@ +endpoint: "https://tlx.3lift.com/s2s/auction?sra=1&supplier_id=20" maintainer: email: "prebid@triplelift.com" gvlVendorID: 28 @@ -19,4 +20,5 @@ userSync: 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 + userMacro: "$UID" +endpointCompression: "GZIP" \ No newline at end of file diff --git a/static/bidder-info/triplelift_native.yaml b/static/bidder-info/triplelift_native.yaml index 96d49b32968..85ebd1a52cc 100644 --- a/static/bidder-info/triplelift_native.yaml +++ b/static/bidder-info/triplelift_native.yaml @@ -1,3 +1,6 @@ +endpoint: "https://tlx.3lift.com/s2sn/auction?supplier_id=20" +disabled: true +extra_info: "{\"publisher_whitelist\":[]}" maintainer: email: "prebid@triplelift.com" gvlVendorID: 28 diff --git a/static/bidder-info/trustx.yaml b/static/bidder-info/trustx.yaml index b6c13bc7000..5e407782e6a 100644 --- a/static/bidder-info/trustx.yaml +++ b/static/bidder-info/trustx.yaml @@ -1,3 +1,4 @@ +endpoint: "https://grid.bidswitch.net/sp_bid?sp=trustx" maintainer: email: "grid-tech@themediagrid.com" gvlVendorID: 686 @@ -13,4 +14,4 @@ capabilities: userSync: redirect: url: "https://x.bidswitch.net/check_uuid/{{.RedirectURL}}?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}" - userMacro: "${BSW_UUID}" + userMacro: "${BSW_UUID}" \ No newline at end of file diff --git a/static/bidder-info/ucfunnel.yaml b/static/bidder-info/ucfunnel.yaml index ca366c395c6..0034b9d4650 100644 --- a/static/bidder-info/ucfunnel.yaml +++ b/static/bidder-info/ucfunnel.yaml @@ -1,3 +1,4 @@ +endpoint: "https://pbs.aralego.com/prebid" maintainer: email: "support@ucfunnel.com" gvlVendorID: 607 diff --git a/static/bidder-info/unicorn.yaml b/static/bidder-info/unicorn.yaml index f1b5a4e7f3e..ba7fde10f15 100644 --- a/static/bidder-info/unicorn.yaml +++ b/static/bidder-info/unicorn.yaml @@ -1,6 +1,7 @@ +endpoint: "https://ds.uncn.jp/pb/0/bid.json" maintainer: email: prebid@unicorn.inc capabilities: app: mediaTypes: - - banner \ No newline at end of file + - banner diff --git a/static/bidder-info/unruly.yaml b/static/bidder-info/unruly.yaml index 9b1ba9c6f54..6389f831a64 100644 --- a/static/bidder-info/unruly.yaml +++ b/static/bidder-info/unruly.yaml @@ -1,3 +1,4 @@ +endpoint: "https://targeting.unrulymedia.com/unruly_prebid_server" maintainer: email: "prebidsupport@unrulygroup.com" gvlVendorID: 36 @@ -13,4 +14,4 @@ capabilities: userSync: redirect: url: "https://sync.1rx.io/usersync2/rmphb?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}" - userMacro: "[RX_UUID]" + userMacro: "[RX_UUID]" \ No newline at end of file diff --git a/static/bidder-info/valueimpression.yaml b/static/bidder-info/valueimpression.yaml index 5ef240219fe..9e6e583967c 100644 --- a/static/bidder-info/valueimpression.yaml +++ b/static/bidder-info/valueimpression.yaml @@ -1,3 +1,4 @@ +endpoint: "http://useast.quantumdex.io/auction/pbs" maintainer: email: "support@apacdex.com" modifyingVastXmlAllowed: false @@ -16,4 +17,4 @@ userSync: userMacro: "[UID]" redirect: url: "https://sync.quantumdex.io/getuid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}}" - userMacro: "[UID]" + userMacro: "[UID]" \ No newline at end of file diff --git a/static/bidder-info/verizonmedia.yaml b/static/bidder-info/verizonmedia.yaml index 80db16ed28b..9be15b84091 100644 --- a/static/bidder-info/verizonmedia.yaml +++ b/static/bidder-info/verizonmedia.yaml @@ -1,3 +1,4 @@ +disabled: true maintainer: email: "dsp-supply-prebid@verizonmedia.com" gvlVendorID: 25 diff --git a/static/bidder-info/videobyte.yaml b/static/bidder-info/videobyte.yaml index fc0e767e1af..742b4dc6641 100644 --- a/static/bidder-info/videobyte.yaml +++ b/static/bidder-info/videobyte.yaml @@ -1,3 +1,4 @@ +endpoint: "https://x.videobyte.com/ortbhb" maintainer: email: "prebid@videobyte.com" gvlVendorID: 1011 diff --git a/static/bidder-info/vidoomy.yaml b/static/bidder-info/vidoomy.yaml index 1cd683a71e0..ef9db4ff752 100644 --- a/static/bidder-info/vidoomy.yaml +++ b/static/bidder-info/vidoomy.yaml @@ -1,3 +1,4 @@ +endpoint: "https://p.vidoomy.com/api/rtbserver/pbs" maintainer: email: support@vidoomy.com gvlVendorID: 380 diff --git a/static/bidder-info/viewdeos.yaml b/static/bidder-info/viewdeos.yaml index bf0c1789fe3..c5e1e8ef02c 100644 --- a/static/bidder-info/viewdeos.yaml +++ b/static/bidder-info/viewdeos.yaml @@ -1,3 +1,4 @@ +endpoint: "http://ghb.sync.viewdeos.com/pbs/ortb" maintainer: email: "contact@viewdeos.com" gvlVendorID: 924 @@ -14,4 +15,4 @@ 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 + - redirect diff --git a/static/bidder-info/visx.yaml b/static/bidder-info/visx.yaml index 7faf1b94ed9..b2acafaaedd 100644 --- a/static/bidder-info/visx.yaml +++ b/static/bidder-info/visx.yaml @@ -1,3 +1,4 @@ +endpoint: "https://t.visx.net/s2s_bid?wrapperType=s2s_prebid_standard:0.1.1" maintainer: email: "supply.partners@yoc.com" gvlVendorID: 154 diff --git a/static/bidder-info/vrtcal.yaml b/static/bidder-info/vrtcal.yaml index 397d0c5e6f8..0983d546fe9 100644 --- a/static/bidder-info/vrtcal.yaml +++ b/static/bidder-info/vrtcal.yaml @@ -1,12 +1,19 @@ +endpoint: "http://rtb.vrtcal.com/bidder_prebid.vap?ssp=1804" maintainer: email: "support@vrtcal.com" +gvlVendorID: 706 capabilities: app: mediaTypes: - banner - video + site: + 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 + diff --git a/static/bidder-info/yahoossp.yaml b/static/bidder-info/yahoossp.yaml index cefa603dfd7..8c23c01e181 100644 --- a/static/bidder-info/yahoossp.yaml +++ b/static/bidder-info/yahoossp.yaml @@ -1,3 +1,4 @@ +endpoint: "https://s2shb.ssp.yahoo.com/admax/bid/partners/PBS" maintainer: email: "hb-fe-tech@oath.com" gvlVendorID: 25 diff --git a/static/bidder-info/yeahmobi.yaml b/static/bidder-info/yeahmobi.yaml index 063b09d0f75..ae41464f6fa 100644 --- a/static/bidder-info/yeahmobi.yaml +++ b/static/bidder-info/yeahmobi.yaml @@ -1,3 +1,4 @@ +endpoint: "https://{{.Host}}/prebid/bid" maintainer: email: "junping.zhao@yeahmobi.com" capabilities: @@ -5,4 +6,4 @@ capabilities: mediaTypes: - banner - video - - native \ No newline at end of file + - native diff --git a/static/bidder-info/yieldlab.yaml b/static/bidder-info/yieldlab.yaml index 14960089cd1..21932904a55 100644 --- a/static/bidder-info/yieldlab.yaml +++ b/static/bidder-info/yieldlab.yaml @@ -1,3 +1,4 @@ +endpoint: "https://ad.yieldlab.net/yp/" maintainer: email: "solutions@yieldlab.de" gvlVendorID: 70 diff --git a/static/bidder-info/yieldmo.yaml b/static/bidder-info/yieldmo.yaml index c5feacc83b9..9356a541f60 100644 --- a/static/bidder-info/yieldmo.yaml +++ b/static/bidder-info/yieldmo.yaml @@ -1,3 +1,4 @@ +endpoint: "https://ads.yieldmo.com/exchange/prebid-server" maintainer: email: "prebid@yieldmo.com" gvlVendorID: 173 @@ -13,4 +14,4 @@ capabilities: userSync: redirect: url: "https://ads.yieldmo.com/pbsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri={{.RedirectURL}}" - userMacro: "$UID" + userMacro: "$UID" \ No newline at end of file diff --git a/static/bidder-info/yieldone.yaml b/static/bidder-info/yieldone.yaml index 46215b5e8ec..1664ed9f674 100644 --- a/static/bidder-info/yieldone.yaml +++ b/static/bidder-info/yieldone.yaml @@ -1,3 +1,4 @@ +endpoint: "https://y.one.impact-ad.jp/hbs_imp" maintainer: email: "y1dev@platform-one.co.jp" capabilities: diff --git a/static/bidder-info/zeroclickfraud.yaml b/static/bidder-info/zeroclickfraud.yaml index 8f7149ea137..9a2b6979a5e 100644 --- a/static/bidder-info/zeroclickfraud.yaml +++ b/static/bidder-info/zeroclickfraud.yaml @@ -1,3 +1,4 @@ +endpoint: "http://{{.Host}}/openrtb2?sid={{.SourceId}}" maintainer: email: "support@datablocks.net" capabilities: @@ -14,4 +15,4 @@ capabilities: userSync: iframe: url: "https://s.0cf.io/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}}" - userMacro: "${uid}" + userMacro: "${uid}" \ No newline at end of file diff --git a/static/bidder-params/adnuntius.json b/static/bidder-params/adnuntius.json index a8e65bea343..ff975501edb 100644 --- a/static/bidder-params/adnuntius.json +++ b/static/bidder-params/adnuntius.json @@ -12,6 +12,10 @@ "network": { "type": "string", "description": "Network if required" + }, + "noCookies": { + "type": "boolean", + "description": "Disable cookies being set by the ad server." } }, diff --git a/static/bidder-params/adrino.json b/static/bidder-params/adrino.json new file mode 100644 index 00000000000..56f7404cbed --- /dev/null +++ b/static/bidder-params/adrino.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Adrino Adapter Params", + "description": "A schema which validates params accepted by the Adrino adapter", + "type": "object", + "properties": { + "hash": { + "type": "string", + "minLength": 1, + "description": "The placement id provided by Adrino." + } + }, + "required": [ + "hash" + ] +} diff --git a/static/bidder-params/adtrgtme.json b/static/bidder-params/adtrgtme.json new file mode 100644 index 00000000000..e0da860cedb --- /dev/null +++ b/static/bidder-params/adtrgtme.json @@ -0,0 +1,8 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Adtrgtme adapter params", + "description": "A schema which validates params accepted by the Adtrgtme adapter", + "type": "object", + + "properties": {} +} \ No newline at end of file diff --git a/static/bidder-params/appush.json b/static/bidder-params/appush.json new file mode 100644 index 00000000000..0e06cccf430 --- /dev/null +++ b/static/bidder-params/appush.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Appush Adapter Params", + "description": "A schema which validates params accepted by the Appush adapter", + "type": "object", + + "properties": { + "placementId": { + "type": "string", + "minLength": 1, + "description": "Placement ID" + }, + "endpointId": { + "type": "string", + "minLength": 1, + "description": "Endpoint ID" + } + }, + "oneOf": [ + { "required": ["placementId"] }, + { "required": ["endpointId"] } + ] + } \ No newline at end of file diff --git a/static/bidder-params/automatad.json b/static/bidder-params/automatad.json new file mode 100644 index 00000000000..bcf0f329f89 --- /dev/null +++ b/static/bidder-params/automatad.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Automatad Adapter Params", + "description": "A schema which validates params accepted by the Automatad adapter", + "type": "object", + + "properties": { + "position": { + "type": "string", + "description": "Position field" + }, + "placementId": { + "type": "string", + "description": "Placement field" + } + }, + "required": [] +} diff --git a/static/bidder-params/beyondmedia.json b/static/bidder-params/beyondmedia.json new file mode 100644 index 00000000000..999e1f37608 --- /dev/null +++ b/static/bidder-params/beyondmedia.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "AndBeyond.Media Adapter Params", + "description": "A schema which validates params accepted by the AndBeyond.Media adapter", + "type": "object", + + "properties": { + "placementId": { + "type": "string", + "minLength": 1, + "description": "Placement ID" + } + }, + "required": ["placementId"] + } \ No newline at end of file diff --git a/static/bidder-params/bidstack.json b/static/bidder-params/bidstack.json new file mode 100644 index 00000000000..d671673d1cf --- /dev/null +++ b/static/bidder-params/bidstack.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Bidstack Adapter Params", + "description": "A schema which validates params accepted by the Bidstack adapter", + "type": "object", + "properties": { + "publisherId": { + "type": "string", + "format": "uuid", + "minLength": 1, + "description": "An ID which identifies the publisher in Bidstack system" + }, + "placementId": { + "type": "string", + "description": "An ID which identifies the placement in Bidstack system" + }, + "consent": { + "type": "boolean", + "description": "User consent" + } + }, + "required" : [ "publisherId" ] +} diff --git a/static/bidder-params/blue.json b/static/bidder-params/blue.json new file mode 100644 index 00000000000..6624defd4dc --- /dev/null +++ b/static/bidder-params/blue.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Blue Adapter Params", + "description": "A schema which validates params accepted by the Blue adapter", + "type": "object", + "properties": { + "publisherId": { + "type": "string", + "description": "The publisher’s ID provided by Blue" + } + }, + "required": [ + "publisherId" + ] +} \ No newline at end of file diff --git a/static/bidder-params/boldwin.json b/static/bidder-params/boldwin.json new file mode 100644 index 00000000000..b5ac37d74ac --- /dev/null +++ b/static/bidder-params/boldwin.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Boldwin Adapter Params", + "description": "A schema which validates params accepted by the Boldwin adapter", + "type": "object", + + "properties": { + "placementId": { + "type": "string", + "minLength": 1, + "description": "Placement ID" + }, + "endpointId": { + "type": "string", + "minLength": 1, + "description": "Endpoint ID" + } + }, + "oneOf": [ + { "required": ["placementId"] }, + { "required": ["endpointId"] } + ] + } \ No newline at end of file diff --git a/static/bidder-params/ccx.json b/static/bidder-params/ccx.json new file mode 100644 index 00000000000..037af3db8a3 --- /dev/null +++ b/static/bidder-params/ccx.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Clickonometrics Adapter Params", + "description": "A schema which validates params accepted by the Clickonometrics adapter", + "type": "object", + "properties": { + "placementId": { + "type": "integer", + "description": "Placement ID" + } + }, + "required": ["placementId"] + } \ No newline at end of file diff --git a/static/bidder-params/dianomi.json b/static/bidder-params/dianomi.json new file mode 100644 index 00000000000..5eaef10d886 --- /dev/null +++ b/static/bidder-params/dianomi.json @@ -0,0 +1,20 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Dianomi Adapter Params", + "description": "A schema which validates params accepted by the Dianomi adapter", + "type": "object", + "properties": { + "smartadId": { + "type": ["integer", "string"], + "pattern": "^\\d+$", + "description": "An ID which identifies the smartad/placement selling the impression" + }, + "priceType": { + "type": ["string"], + "description": "gross or net. Default is net.", + "pattern": "gross|net" + } + }, + + "required": ["smartadId"] +} diff --git a/static/bidder-params/freewheel-ssp.json b/static/bidder-params/freewheel-ssp.json new file mode 100644 index 00000000000..103aed03198 --- /dev/null +++ b/static/bidder-params/freewheel-ssp.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "FreewheelSSP old Adapter Params", + "description": "A schema which validates params accepted by the FreewheelSSP adapter", + "type": "object", + + "properties": { + "zoneId": { + "type": "integer", + "description": "Zone ID" + } + }, + + "required": ["zoneId"] +} diff --git a/static/bidder-params/freewheelssp.json b/static/bidder-params/freewheelssp.json new file mode 100644 index 00000000000..6f93501a3e1 --- /dev/null +++ b/static/bidder-params/freewheelssp.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "FreewheelSSP Adapter Params", + "description": "A schema which validates params accepted by the FreewheelSSP adapter", + "type": "object", + + "properties": { + "zoneId": { + "type": "integer", + "description": "Zone ID" + } + }, + + "required": ["zoneId"] +} \ No newline at end of file diff --git a/static/bidder-params/infytv.json b/static/bidder-params/infytv.json new file mode 100644 index 00000000000..22f9b317dff --- /dev/null +++ b/static/bidder-params/infytv.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "InfyTV Adapter Params", + "description": "A schema which validates params accepted by the InfyTV adapter", + "type": "object", + "properties": { + "publisherId": { + "type": "string", + "description": "publisher ID" + }, + "placementId": { + "type": "string", + "description": "Placement ID" + } + }, + "required": [ + "publisherId" + ] +} \ No newline at end of file diff --git a/static/bidder-params/kargo.json b/static/bidder-params/kargo.json new file mode 100644 index 00000000000..15c11526b7c --- /dev/null +++ b/static/bidder-params/kargo.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Kargo Adapter Params", + "description": "A schema which validates params accepted by the Kargo adapter", + "type": "object", + + "properties": { + "adSlotID": { + "type": "string", + "description": "An ID which identifies the adslot placement. Equivalent to the id of target inventory, ad unit code, or placement id" + } + }, + + "required": ["adSlotID"] + } diff --git a/static/bidder-params/mediafuse.json b/static/bidder-params/mediafuse.json index 4f38f89d299..8d577240d96 100644 --- a/static/bidder-params/mediafuse.json +++ b/static/bidder-params/mediafuse.json @@ -5,22 +5,105 @@ "type": "object", "properties": { - "placementId": { + "placement_id": { "type": "integer", "description": "An ID which identifies this placement of the impression" }, - "siteId": { + "placementId": { "type": "integer", - "description": "An ID which identifies the site selling the impression" + "description": "Deprecated, use placement_id instead." }, - "aid": { - "type": "integer", - "description": "An ID which identifies the channel" + "inv_code": { + "type": "string", + "description": "A code identifying the inventory of this placement." + }, + "invCode": { + "type": "string", + "description": "Deprecated, use inv_code instead." + }, + "member": { + "type": "string", + "description": "An ID which identifies the member selling the impression." + }, + "keywords": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "description": "A key with one or more values associated with it. These are used in buy-side segment targeting.", + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + }, + "required": ["key"] + } + }, + "traffic_source_code": { + "type": "string", + "description": "Specifies the third-party source of this impression." }, - "bidFloor": { + "trafficSourceCode": { + "type": "string", + "description": "Deprecated, use traffic_source_code instead." + }, + "reserve": { "type": "number", - "description": "BidFloor, US Dollars" + "description": "The minimium acceptable bid, in CPM, using US Dollars" + }, + "position": { + "type": "string", + "enum": ["above", "below"], + "description": "Specifies the ad unit as above or below the fold" + }, + "use_pmt_rule": { + "type": "boolean", + "description": "Boolean to signal Mediafuse to apply the relevant payment rule" + }, + "generate_ad_pod_id": { + "type": "boolean", + "description": "Boolean to signal Mediafuse to add ad pod id to each request" + }, + "private_sizes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "w": { + "type": "integer" + }, + "h": { + "type": "integer" + } + }, + "required": ["w", "h"] + }, + "description": "Private sizes (ex: [{\"w\": 300, \"h\": 250},{...}]), experimental, may not be supported." } }, - "required": ["aid"] -} + + "oneOf": [{ + "oneOf": [{ + "required": ["placementId"] + }, { + "required": ["placement_id"] + }] + }, { + "oneOf": [{ + "required": ["invCode", "member"] + }, { + "required": ["inv_code", "member"] + }] + }], + + "not": { + "required": ["placementId", "invCode", "member"] + } +} \ No newline at end of file diff --git a/static/bidder-params/oftmedia.json b/static/bidder-params/oftmedia.json new file mode 100644 index 00000000000..dba2aa7f8e4 --- /dev/null +++ b/static/bidder-params/oftmedia.json @@ -0,0 +1,26 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "OFTMedia Adapter Params", + "description": "A schema which validates params accepted by the OFTMedia adapter", + + "type": "object", + "properties": { + "placementId": { + "type": "integer", + "description": "An ID which identifies this placement of the impression" + }, + "siteId": { + "type": "integer", + "description": "An ID which identifies the site selling the impression" + }, + "aid": { + "type": "integer", + "description": "An ID which identifies the channel" + }, + "bidFloor": { + "type": "number", + "description": "BidFloor, US Dollars" + } + }, + "required": ["aid"] +} diff --git a/static/bidder-params/seedingAlliance.json b/static/bidder-params/seedingAlliance.json new file mode 100644 index 00000000000..cf9aa375eb4 --- /dev/null +++ b/static/bidder-params/seedingAlliance.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Seeding Alliance Adapter Params", + "description": "A schema which validates params accepted by the Seeding Alliance adapter", + "type": "object", + "properties": { + "adUnitId": { + "type": "string", + "description": "Ad Unit ID", + "minLength": 1 + } + }, + "required": [ + "adUnitId" + ] +} \ No newline at end of file diff --git a/static/bidder-params/sspBC.json b/static/bidder-params/sspBC.json new file mode 100644 index 00000000000..27cb1a62276 --- /dev/null +++ b/static/bidder-params/sspBC.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "sspBC Bid Adapter Params", + "description": "A schema which validates params accepted by the sspBC bid adapter", + "type": "object", + "properties": { + "siteid": { + "type": [ + "string" + ], + "description": "Identifier of site that is selling the impression" + }, + "id": { + "type": [ + "string" + ], + "description": "Identifier of adslot that the auction in running on" + }, + "test": { + "type": [ + "integer" + ], + "description": "Switch that enables test responses" + } + }, + "required": [] +} \ No newline at end of file diff --git a/static/bidder-params/stroeerCore.json b/static/bidder-params/stroeerCore.json new file mode 100644 index 00000000000..d3476be4197 --- /dev/null +++ b/static/bidder-params/stroeerCore.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "StroeerCore Adapter Params", + "description": "A schema which validates params accepted by the StroeerCore adapter", + + "type": "object", + "properties": { + "sid": { + "type": "string", + "description": "Slot Id" + } + }, + "required": ["sid"] +} diff --git a/static/bidder-params/suntContent.json b/static/bidder-params/suntContent.json new file mode 100644 index 00000000000..e85ad3f5de5 --- /dev/null +++ b/static/bidder-params/suntContent.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "SUNT Content Adapter Params", + "description": "A schema which validates params accepted by the SUNT Content adapter", + "type": "object", + "properties": { + "adUnitId": { + "type": "string", + "description": "Ad Unit ID", + "minLength": 1 + } + }, + "required": [ + "adUnitId" + ] +} \ No newline at end of file diff --git a/static/bidder-params/taboola.json b/static/bidder-params/taboola.json new file mode 100644 index 00000000000..2d924493a1e --- /dev/null +++ b/static/bidder-params/taboola.json @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Taboola Adapter Params", + "description": "A schema which validates params accepted by the Taboola adapter", + + "type": "object", + "properties": { + "publisherId": { + "type": "string" + }, + "publisherDomain": { + "type": "string" + }, + "tagid": { + "type": "string" + }, + "bidfloor": { + "type": "number" + }, + "bcat": { + "type": "array", + "items": { + "type": "string" + } + }, + "badv": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": ["tagid","publisherId"] +} diff --git a/static/bidder-params/unicorn.json b/static/bidder-params/unicorn.json index f9c4e1677b6..9348246c449 100644 --- a/static/bidder-params/unicorn.json +++ b/static/bidder-params/unicorn.json @@ -9,7 +9,7 @@ "description": "In Application, if placementId is empty, prebid server configuration id will be used as placementId." }, "publisherId": { - "type": "integer", + "type": "string", "description": "Account specific publisher id" }, "mediaId": { @@ -21,5 +21,5 @@ "description": "Account ID for charge request" } }, - "required" : ["mediaId", "accountId"] + "required" : ["accountId"] } \ No newline at end of file diff --git a/static/bidder-params/yieldlab.json b/static/bidder-params/yieldlab.json index 900d65da6e5..9d0fd0e88c0 100644 --- a/static/bidder-params/yieldlab.json +++ b/static/bidder-params/yieldlab.json @@ -12,10 +12,6 @@ "type": "string", "description": "Yieldlab ID of the supply" }, - "adSize": { - "type": "string", - "description": "Size of the adslot in pixel, e.g. 200x50" - }, "extId": { "type": "string", "description": "External ID used for reporting" @@ -27,7 +23,6 @@ }, "required": [ "adslotId", - "supplyId", - "adSize" + "supplyId" ] } diff --git a/static/bidder-params/yssp.json b/static/bidder-params/yssp.json deleted file mode 100644 index fdadc747a4c..00000000000 --- a/static/bidder-params/yssp.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "$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"] -} diff --git a/stored_requests/backends/db_fetcher/fetcher.go b/stored_requests/backends/db_fetcher/fetcher.go index 1ad64a3ca3f..7963751bcf3 100644 --- a/stored_requests/backends/db_fetcher/fetcher.go +++ b/stored_requests/backends/db_fetcher/fetcher.go @@ -2,36 +2,42 @@ package db_fetcher import ( "context" - "database/sql" "encoding/json" + "github.com/lib/pq" "github.com/golang/glog" "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/stored_requests/backends/db_provider" ) -func NewFetcher(db *sql.DB, queryMaker func(int, int) string, responseQueryMaker func(int) string) stored_requests.AllFetcher { - if db == nil { - glog.Fatalf("The Postgres Stored Request Fetcher requires a database connection. Please report this as a bug.") +func NewFetcher( + provider db_provider.DbProvider, + queryTemplate string, + responseQueryTemplate string, +) stored_requests.AllFetcher { + + if provider == nil { + glog.Fatalf("The Database Stored Request Fetcher requires a database connection. Please report this as a bug.") } - if queryMaker == nil { - glog.Fatalf("The Postgres Stored Request Fetcher requires a queryMaker function. Please report this as a bug.") + if queryTemplate == "" { + glog.Fatalf("The Database Stored Request Fetcher requires a queryTemplate. Please report this as a bug.") } - if responseQueryMaker == nil { - glog.Fatalf("The Postgres Stored Response Fetcher requires a responseQueryMaker function. Please report this as a bug.") + if responseQueryTemplate == "" { + glog.Fatalf("The Database Stored Response Fetcher requires a responseQueryTemplate. Please report this as a bug.") } return &dbFetcher{ - db: db, - queryMaker: queryMaker, - responseQueryMaker: responseQueryMaker, + provider: provider, + queryTemplate: queryTemplate, + responseQueryTemplate: responseQueryTemplate, } } // dbFetcher fetches Stored Requests from a database. This should be instantiated through the NewFetcher() function. type dbFetcher struct { - db *sql.DB - queryMaker func(numReqs int, numImps int) (query string) - responseQueryMaker func(numIds int) (query string) + provider db_provider.DbProvider + queryTemplate string + responseQueryTemplate string } func (fetcher *dbFetcher) FetchRequests(ctx context.Context, requestIDs []string, impIDs []string) (map[string]json.RawMessage, map[string]json.RawMessage, []error) { @@ -39,16 +45,21 @@ func (fetcher *dbFetcher) FetchRequests(ctx context.Context, requestIDs []string return nil, nil, nil } - query := fetcher.queryMaker(len(requestIDs), len(impIDs)) - idInterfaces := make([]interface{}, len(requestIDs)+len(impIDs)) + requestIDsParam := make([]interface{}, len(requestIDs)) for i := 0; i < len(requestIDs); i++ { - idInterfaces[i] = requestIDs[i] + requestIDsParam[i] = requestIDs[i] } + impIDsParam := make([]interface{}, len(impIDs)) for i := 0; i < len(impIDs); i++ { - idInterfaces[i+len(requestIDs)] = impIDs[i] + impIDsParam[i] = impIDs[i] } - rows, err := fetcher.db.QueryContext(ctx, query, idInterfaces...) + params := []db_provider.QueryParam{ + {Name: "REQUEST_ID_LIST", Value: requestIDsParam}, + {Name: "IMP_ID_LIST", Value: impIDsParam}, + } + + rows, err := fetcher.provider.QueryContext(ctx, fetcher.queryTemplate, params...) if err != nil { if err != context.DeadlineExceeded && !isBadInput(err) { glog.Errorf("Error reading from Stored Request DB: %s", err.Error()) @@ -82,7 +93,7 @@ func (fetcher *dbFetcher) FetchRequests(ctx context.Context, requestIDs []string case "imp": storedImpData[id] = data default: - glog.Errorf("Postgres result set with id=%s has invalid type: %s. This will be ignored.", id, dataType) + glog.Errorf("Database result set with id=%s has invalid type: %s. This will be ignored.", id, dataType) } } @@ -102,13 +113,15 @@ func (fetcher *dbFetcher) FetchResponses(ctx context.Context, ids []string) (dat return nil, nil } - query := fetcher.responseQueryMaker(len(ids)) idInterfaces := make([]interface{}, len(ids)) for i := 0; i < len(ids); i++ { idInterfaces[i] = ids[i] } + params := []db_provider.QueryParam{ + {Name: "ID_LIST", Value: idInterfaces}, + } - rows, err := fetcher.db.QueryContext(ctx, query, idInterfaces...) + rows, err := fetcher.provider.QueryContext(ctx, fetcher.responseQueryTemplate, params...) if err != nil { return nil, []error{err} } diff --git a/stored_requests/backends/db_fetcher/fetcher_test.go b/stored_requests/backends/db_fetcher/fetcher_test.go index 8959736dbbb..04753fb8af5 100644 --- a/stored_requests/backends/db_fetcher/fetcher_test.go +++ b/stored_requests/backends/db_fetcher/fetcher_test.go @@ -11,20 +11,21 @@ import ( "time" "github.com/DATA-DOG/go-sqlmock" + "github.com/prebid/prebid-server/stored_requests/backends/db_provider" "github.com/stretchr/testify/assert" ) func TestEmptyQuery(t *testing.T) { - db, _, err := sqlmock.New() + provider, _, err := db_provider.NewDbProviderMock() if err != nil { t.Fatalf("Unexpected error stubbing DB: %v", err) } - defer db.Close() + defer provider.Close() fetcher := dbFetcher{ - db: db, - queryMaker: successfulQueryMaker(""), - responseQueryMaker: successfulResponseQueryMaker(""), + provider: provider, + queryTemplate: "", + responseQueryTemplate: "", } storedReqs, storedImps, errs := fetcher.FetchRequests(context.Background(), nil, nil) assertErrorCount(t, 0, errs) @@ -45,7 +46,7 @@ func TestGoodResponse(t *testing.T) { AddRow("imp-id-2", `{"imp":true,"value":2}`, "imp") mock, fetcher := newFetcher(t, mockReturn, mockQuery, "request-id") - defer fetcher.db.Close() + defer fetcher.provider.Close() storedReqs, storedImps, errs := fetcher.FetchRequests(context.Background(), []string{"request-id"}, nil) @@ -98,7 +99,7 @@ func TestFetchResponses(t *testing.T) { for _, test := range testCases { mock, fetcher := newFetcher(t, test.mockReturn, test.mockQuery, test.arguments...) - defer fetcher.db.Close() + defer fetcher.provider.Close() storedResponses, errs := fetcher.FetchResponses(context.Background(), test.respIds) @@ -120,7 +121,7 @@ func TestPartialResponse(t *testing.T) { AddRow("stored-req-id", "{}", "request") mock, fetcher := newFetcher(t, mockReturn, mockQuery, "stored-req-id", "stored-req-id-2") - defer fetcher.db.Close() + defer fetcher.provider.Close() storedReqs, storedImps, errs := fetcher.FetchRequests(context.Background(), []string{"stored-req-id", "stored-req-id-2"}, nil) @@ -137,7 +138,7 @@ func TestEmptyResponse(t *testing.T) { mockReturn := sqlmock.NewRows([]string{"id", "data", "dataType"}) mock, fetcher := newFetcher(t, mockReturn, mockQuery, "stored-req-id", "stored-req-id-2", "stored-imp-id") - defer fetcher.db.Close() + defer fetcher.provider.Close() storedReqs, storedImps, errs := fetcher.FetchRequests(context.Background(), []string{"stored-req-id", "stored-req-id-2"}, []string{"stored-imp-id"}) @@ -149,7 +150,7 @@ func TestEmptyResponse(t *testing.T) { // TestDatabaseError makes sure we exit with an error if the DB query fails. func TestDatabaseError(t *testing.T) { - db, mock, err := sqlmock.New() + provider, mock, err := db_provider.NewDbProviderMock() if err != nil { t.Fatalf("Failed to create mock: %v", err) } @@ -157,8 +158,8 @@ func TestDatabaseError(t *testing.T) { mock.ExpectQuery(".*").WillReturnError(errors.New("Invalid query.")) fetcher := &dbFetcher{ - db: db, - queryMaker: successfulQueryMaker("SELECT id, data, dataType FROM my_table WHERE id IN (?, ?)"), + provider: provider, + queryTemplate: "SELECT id, data, dataType FROM my_table WHERE id IN (?, ?)", } storedReqs, storedImps, errs := fetcher.FetchRequests(context.Background(), []string{"stored-req-id"}, nil) @@ -169,7 +170,7 @@ func TestDatabaseError(t *testing.T) { // TestContextDeadlines makes sure a hung query returns when the timeout expires. func TestContextDeadlines(t *testing.T) { - db, mock, err := sqlmock.New() + provider, mock, err := db_provider.NewDbProviderMock() if err != nil { t.Fatalf("Failed to create mock: %v", err) } @@ -177,9 +178,9 @@ func TestContextDeadlines(t *testing.T) { mock.ExpectQuery(".*").WillDelayFor(2 * time.Minute) fetcher := &dbFetcher{ - db: db, - queryMaker: successfulQueryMaker("SELECT id, requestData FROM my_table WHERE id IN (?, ?)"), - responseQueryMaker: successfulResponseQueryMaker("SELECT id, responseData FROM my_table WHERE id IN (?, ?)"), + provider: provider, + queryTemplate: "SELECT id, requestData FROM my_table WHERE id IN (?, ?)", + responseQueryTemplate: "SELECT id, responseData FROM my_table WHERE id IN (?, ?)", } ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond) @@ -197,7 +198,7 @@ func TestContextDeadlines(t *testing.T) { // TestContextCancelled makes sure a hung query returns when the context is cancelled. func TestContextCancelled(t *testing.T) { - db, mock, err := sqlmock.New() + provider, mock, err := db_provider.NewDbProviderMock() if err != nil { t.Fatalf("Failed to create mock: %v", err) } @@ -205,9 +206,9 @@ func TestContextCancelled(t *testing.T) { mock.ExpectQuery(".*").WillDelayFor(2 * time.Minute) fetcher := &dbFetcher{ - db: db, - queryMaker: successfulQueryMaker("SELECT id, requestData FROM my_table WHERE id IN (?, ?)"), - responseQueryMaker: successfulResponseQueryMaker("SELECT id, responseData FROM my_table WHERE id IN (?, ?)"), + provider: provider, + queryTemplate: "SELECT id, requestData FROM my_table WHERE id IN (?, ?)", + responseQueryTemplate: "SELECT id, responseData FROM my_table WHERE id IN (?, ?)", } ctx, cancel := context.WithCancel(context.Background()) @@ -224,7 +225,7 @@ func TestContextCancelled(t *testing.T) { // Prevents #338 func TestRowErrors(t *testing.T) { - db, mock, err := sqlmock.New() + provider, mock, err := db_provider.NewDbProviderMock() if err != nil { t.Fatalf("Failed to create mock: %v", err) } @@ -234,8 +235,8 @@ func TestRowErrors(t *testing.T) { rows.RowError(1, errors.New("Error reading from row 1")) mock.ExpectQuery(".*").WillReturnRows(rows) fetcher := &dbFetcher{ - db: db, - queryMaker: successfulQueryMaker("SELECT id, data, dataType FROM my_table WHERE id IN (?)"), + provider: provider, + queryTemplate: "SELECT id, data, dataType FROM my_table WHERE id IN (?)", } data, _, errs := fetcher.FetchRequests(context.Background(), []string{"foo", "bar"}, nil) assertErrorCount(t, 1, errs) @@ -246,7 +247,7 @@ func TestRowErrors(t *testing.T) { } func TestRowErrorsFetchResponses(t *testing.T) { - db, mock, err := sqlmock.New() + provider, mock, err := db_provider.NewDbProviderMock() if err != nil { t.Fatalf("Failed to create mock: %v", err) } @@ -256,9 +257,9 @@ func TestRowErrorsFetchResponses(t *testing.T) { rows.RowError(1, errors.New("Error reading from row 1")) mock.ExpectQuery(".*").WillReturnRows(rows) fetcher := &dbFetcher{ - db: db, - queryMaker: successfulQueryMaker("SELECT id, data, dataType FROM my_table WHERE id IN (?)"), - responseQueryMaker: successfulResponseQueryMaker("SELECT id, data, dataType FROM my_table WHERE id IN (?)"), + provider: provider, + queryTemplate: "SELECT id, data, dataType FROM my_table WHERE id IN (?)", + responseQueryTemplate: "SELECT id, data, dataType FROM my_table WHERE id IN (?)", } data, errs := fetcher.FetchResponses(context.Background(), []string{"foo", "bar"}) assertErrorCount(t, 1, errs) @@ -269,7 +270,7 @@ func TestRowErrorsFetchResponses(t *testing.T) { } func newFetcher(t *testing.T, rows *sqlmock.Rows, query string, args ...driver.Value) (sqlmock.Sqlmock, *dbFetcher) { - db, mock, err := sqlmock.New() + provider, mock, err := db_provider.NewDbProviderMock() if err != nil { t.Fatalf("Failed to create mock: %v", err) return nil, nil @@ -278,9 +279,9 @@ func newFetcher(t *testing.T, rows *sqlmock.Rows, query string, args ...driver.V queryRegex := fmt.Sprintf("^%s$", regexp.QuoteMeta(query)) mock.ExpectQuery(queryRegex).WithArgs(args...).WillReturnRows(rows) fetcher := &dbFetcher{ - db: db, - queryMaker: successfulQueryMaker(query), - responseQueryMaker: successfulResponseQueryMaker(query), + provider: provider, + queryTemplate: query, + responseQueryTemplate: query, } return mock, fetcher @@ -317,15 +318,3 @@ func assertErrorCount(t *testing.T, num int, errs []error) { t.Errorf("Wrong number of errors. Expected %d. Got %d. Errors are %v", num, len(errs), errs) } } - -func successfulQueryMaker(response string) func(int, int) string { - return func(numReqs int, numImps int) string { - return response - } -} - -func successfulResponseQueryMaker(response string) func(int) string { - return func(numIds int) string { - return response - } -} diff --git a/stored_requests/backends/db_provider/db_provider.go b/stored_requests/backends/db_provider/db_provider.go new file mode 100644 index 00000000000..df6ae81e8e6 --- /dev/null +++ b/stored_requests/backends/db_provider/db_provider.go @@ -0,0 +1,51 @@ +package db_provider + +import ( + "context" + "database/sql" + + "github.com/golang/glog" + "github.com/prebid/prebid-server/config" +) + +type DbProvider interface { + Config() config.DatabaseConnection + ConnString() string + Open() error + Close() error + Ping() error + PrepareQuery(template string, params ...QueryParam) (query string, args []interface{}) + QueryContext(ctx context.Context, template string, params ...QueryParam) (*sql.Rows, error) +} + +func NewDbProvider(dataType config.DataType, cfg config.DatabaseConnection) DbProvider { + var provider DbProvider + + switch cfg.Driver { + case "mysql": + provider = &MySqlDbProvider{ + cfg: cfg, + } + case "postgres": + provider = &PostgresDbProvider{ + cfg: cfg, + } + default: + glog.Fatalf("Unsupported database driver %s", cfg.Driver) + return nil + } + + if err := provider.Open(); err != nil { + glog.Fatalf("Failed to open %s database connection: %v", dataType, err) + } + if err := provider.Ping(); err != nil { + glog.Fatalf("Failed to ping %s database: %v", dataType, err) + } + + return provider +} + +type QueryParam struct { + Name string + Value interface{} +} diff --git a/stored_requests/backends/db_provider/db_provider_mock.go b/stored_requests/backends/db_provider/db_provider_mock.go new file mode 100644 index 00000000000..3432f62e713 --- /dev/null +++ b/stored_requests/backends/db_provider/db_provider_mock.go @@ -0,0 +1,67 @@ +package db_provider + +import ( + "context" + "database/sql" + "reflect" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/prebid/prebid-server/config" +) + +func NewDbProviderMock() (*DbProviderMock, sqlmock.Sqlmock, error) { + db, mock, err := sqlmock.New() + if err != nil { + return nil, nil, err + } + + provider := &DbProviderMock{ + db: db, + mock: mock, + } + + return provider, mock, nil +} + +type DbProviderMock struct { + db *sql.DB + mock sqlmock.Sqlmock +} + +func (provider DbProviderMock) Config() config.DatabaseConnection { + return config.DatabaseConnection{} +} + +func (provider DbProviderMock) ConnString() string { + return "" +} + +func (provider DbProviderMock) Open() error { + return nil +} + +func (provider DbProviderMock) Close() error { + return nil +} + +func (provider DbProviderMock) Ping() error { + return nil +} + +func (provider DbProviderMock) PrepareQuery(template string, params ...QueryParam) (query string, args []interface{}) { + for _, param := range params { + if reflect.TypeOf(param.Value).Kind() == reflect.Slice { + idList := param.Value.([]interface{}) + args = append(args, idList...) + } else { + args = append(args, param.Value) + } + } + return template, args +} + +func (provider DbProviderMock) QueryContext(ctx context.Context, template string, params ...QueryParam) (*sql.Rows, error) { + query, args := provider.PrepareQuery(template, params...) + + return provider.db.QueryContext(ctx, query, args...) +} diff --git a/stored_requests/backends/db_provider/db_provider_test.go b/stored_requests/backends/db_provider/db_provider_test.go new file mode 100644 index 00000000000..1bb70419f3f --- /dev/null +++ b/stored_requests/backends/db_provider/db_provider_test.go @@ -0,0 +1,154 @@ +package db_provider + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestPrepareQuery(t *testing.T) { + tests := []struct { + description string + + template string + params []QueryParam + mySqlQuery string + mySqlArgs []interface{} + postgresQuery string + postgresArgs []interface{} + }{ + { + description: "Np parameters", + template: "SELECT * FROM table", + params: []QueryParam{}, + mySqlQuery: "SELECT * FROM table", + mySqlArgs: []interface{}{}, + postgresQuery: "SELECT * FROM table", + postgresArgs: []interface{}{}, + }, + { + description: "One simple parameter", + template: "SELECT * FROM table WHERE id = $ID", + params: []QueryParam{{Name: "ID", Value: "1001"}}, + mySqlQuery: "SELECT * FROM table WHERE id = ?", + mySqlArgs: []interface{}{"1001"}, + postgresQuery: "SELECT * FROM table WHERE id = $1", + postgresArgs: []interface{}{"1001"}, + }, + { + description: "Two simple parameters", + template: "SELECT * FROM table WHERE id = $ID AND name = $NAME", + params: []QueryParam{ + {Name: "ID", Value: "1001"}, + {Name: "NAME", Value: "Alice"}, + }, + mySqlQuery: "SELECT * FROM table WHERE id = ? AND name = ?", + mySqlArgs: []interface{}{"1001", "Alice"}, + postgresQuery: "SELECT * FROM table WHERE id = $1 AND name = $2", + postgresArgs: []interface{}{"1001", "Alice"}, + }, + { + description: "Two simple parameters, used several times", + template: "SELECT $ID, $NAME, * FROM table WHERE id = $ID AND name = $NAME", + params: []QueryParam{ + {Name: "ID", Value: "1001"}, + {Name: "NAME", Value: "Alice"}, + }, + mySqlQuery: "SELECT ?, ?, * FROM table WHERE id = ? AND name = ?", + mySqlArgs: []interface{}{"1001", "Alice", "1001", "Alice"}, + postgresQuery: "SELECT $1, $2, * FROM table WHERE id = $1 AND name = $2", + postgresArgs: []interface{}{"1001", "Alice"}, + }, + { + description: "Empty list parameter", + template: "SELECT * FROM table WHERE id IN $IDS", + params: []QueryParam{{Name: "IDS", Value: []interface{}{}}}, + mySqlQuery: "SELECT * FROM table WHERE id IN (NULL)", + mySqlArgs: []interface{}{}, + postgresQuery: "SELECT * FROM table WHERE id IN (NULL)", + postgresArgs: []interface{}{}, + }, + { + description: "One list parameter", + template: "SELECT * FROM table WHERE id IN $IDS", + params: []QueryParam{{Name: "IDS", Value: []interface{}{"1001", "1002"}}}, + mySqlQuery: "SELECT * FROM table WHERE id IN (?, ?)", + mySqlArgs: []interface{}{"1001", "1002"}, + postgresQuery: "SELECT * FROM table WHERE id IN ($1, $2)", + postgresArgs: []interface{}{"1001", "1002"}, + }, + { + description: "Two list parameters", + template: "SELECT * FROM table WHERE id IN $IDS OR name in $NAMES", + params: []QueryParam{ + {Name: "IDS", Value: []interface{}{"1001"}}, + {Name: "NAMES", Value: []interface{}{"Bob", "Nancy"}}, + }, + mySqlQuery: "SELECT * FROM table WHERE id IN (?) OR name in (?, ?)", + mySqlArgs: []interface{}{"1001", "Bob", "Nancy"}, + postgresQuery: "SELECT * FROM table WHERE id IN ($1) OR name in ($2, $3)", + postgresArgs: []interface{}{"1001", "Bob", "Nancy"}, + }, + { + description: "Mix of simple and list parameters", + template: ` + SELECT * FROM table1 + WHERE last_updated > $LAST_UPDATED + AND (id IN $IDS OR name in $NAMES) + UNION ALL + SELECT * FROM table1 + WHERE last_updated > $LAST_UPDATED + AND (id IN $IDS OR name in $NAMES) + `, + params: []QueryParam{ + {Name: "LAST_UPDATED", Value: "1970-01-01"}, + {Name: "IDS", Value: []interface{}{"1001"}}, + {Name: "NAMES", Value: []interface{}{"Bob", "Nancy"}}, + }, + mySqlQuery: ` + SELECT * FROM table1 + WHERE last_updated > ? + AND (id IN (?) OR name in (?, ?)) + UNION ALL + SELECT * FROM table1 + WHERE last_updated > ? + AND (id IN (?) OR name in (?, ?)) + `, + mySqlArgs: []interface{}{ + "1970-01-01", + "1001", + "Bob", "Nancy", + "1970-01-01", + "1001", + "Bob", "Nancy", + }, + postgresQuery: ` + SELECT * FROM table1 + WHERE last_updated > $1 + AND (id IN ($2) OR name in ($3, $4)) + UNION ALL + SELECT * FROM table1 + WHERE last_updated > $1 + AND (id IN ($2) OR name in ($3, $4)) + `, + postgresArgs: []interface{}{ + "1970-01-01", + "1001", + "Bob", "Nancy", + }, + }, + } + + for _, tt := range tests { + mySqlDbProvider := MySqlDbProvider{} + mySqlQuery, mySqlArgs := mySqlDbProvider.PrepareQuery(tt.template, tt.params...) + assert.Equal(t, tt.mySqlQuery, mySqlQuery, fmt.Sprintf("MySql: %s", tt.description)) + assert.Equal(t, tt.mySqlArgs, mySqlArgs, fmt.Sprintf("MySql: %s", tt.description)) + + postgresDbProvider := PostgresDbProvider{} + postgresQuery, postgresArgs := postgresDbProvider.PrepareQuery(tt.template, tt.params...) + assert.Equal(t, tt.postgresQuery, postgresQuery, fmt.Sprintf("Postgres: %s", tt.description)) + assert.Equal(t, tt.postgresArgs, postgresArgs, fmt.Sprintf("Postgres: %s", tt.description)) + } +} diff --git a/stored_requests/backends/db_provider/mysql_dbprovider.go b/stored_requests/backends/db_provider/mysql_dbprovider.go new file mode 100644 index 00000000000..91c37f04910 --- /dev/null +++ b/stored_requests/backends/db_provider/mysql_dbprovider.go @@ -0,0 +1,151 @@ +package db_provider + +import ( + "bytes" + "context" + "database/sql" + "regexp" + "sort" + "strconv" + "strings" + + "github.com/prebid/prebid-server/config" +) + +type MySqlDbProvider struct { + cfg config.DatabaseConnection + db *sql.DB +} + +func (provider *MySqlDbProvider) Config() config.DatabaseConnection { + return provider.cfg +} + +func (provider *MySqlDbProvider) Open() error { + db, err := sql.Open(provider.cfg.Driver, provider.ConnString()) + + if err != nil { + return err + } + + provider.db = db + return nil +} + +func (provider *MySqlDbProvider) Close() error { + if provider.db != nil { + db := provider.db + provider.db = nil + return db.Close() + } + + return nil +} + +func (provider *MySqlDbProvider) Ping() error { + return provider.db.Ping() +} + +func (provider *MySqlDbProvider) ConnString() string { + buffer := bytes.NewBuffer(nil) + + if provider.cfg.Username != "" { + buffer.WriteString(provider.cfg.Username) + if provider.cfg.Password != "" { + buffer.WriteString(":") + buffer.WriteString(provider.cfg.Password) + } + buffer.WriteString("@") + } + + buffer.WriteString("tcp(") + if provider.cfg.Host != "" { + buffer.WriteString(provider.cfg.Host) + } + + if provider.cfg.Port > 0 { + buffer.WriteString(":") + buffer.WriteString(strconv.Itoa(provider.cfg.Port)) + } + buffer.WriteString(")") + + buffer.WriteString("/") + + if provider.cfg.Database != "" { + buffer.WriteString(provider.cfg.Database) + } + + return buffer.String() +} + +func (provider *MySqlDbProvider) PrepareQuery(template string, params ...QueryParam) (query string, args []interface{}) { + query = template + args = []interface{}{} + + type occurrence struct { + startIndex int + param QueryParam + } + occurrences := []occurrence{} + + for _, param := range params { + re := regexp.MustCompile("\\$" + param.Name) + matches := re.FindAllIndex([]byte(query), -1) + for _, match := range matches { + occurrences = append(occurrences, + occurrence{ + startIndex: match[0], + param: param, + }) + } + } + sort.Slice(occurrences, func(i, j int) bool { + return occurrences[i].startIndex < occurrences[j].startIndex + }) + + for _, occurrence := range occurrences { + switch occurrence.param.Value.(type) { + case []interface{}: + idList := occurrence.param.Value.([]interface{}) + args = append(args, idList...) + default: + args = append(args, occurrence.param.Value) + } + } + + for _, param := range params { + switch param.Value.(type) { + case []interface{}: + len := len(param.Value.([]interface{})) + idList := provider.createIdList(len) + query = strings.Replace(query, "$"+param.Name, idList, -1) + default: + query = strings.Replace(query, "$"+param.Name, "?", -1) + } + } + return +} + +func (provider *MySqlDbProvider) QueryContext(ctx context.Context, template string, params ...QueryParam) (*sql.Rows, error) { + query, args := provider.PrepareQuery(template, params...) + return provider.db.QueryContext(ctx, query, args...) +} + +func (provider *MySqlDbProvider) createIdList(numArgs int) string { + // Any empty list like "()" is illegal in MySql. A (NULL) is the next best thing, + // though, since `id IN (NULL)` is valid for all "id" column types, and evaluates to an empty set. + if numArgs == 0 { + return "(NULL)" + } + + result := bytes.NewBuffer(make([]byte, 0, 2+3*numArgs)) + result.WriteString("(") + for i := 1; i < numArgs; i++ { + result.WriteString("?") + result.WriteString(", ") + } + result.WriteString("?") + result.WriteString(")") + + return result.String() +} diff --git a/stored_requests/backends/db_provider/mysql_dbprovider_test.go b/stored_requests/backends/db_provider/mysql_dbprovider_test.go new file mode 100644 index 00000000000..e0796d00e95 --- /dev/null +++ b/stored_requests/backends/db_provider/mysql_dbprovider_test.go @@ -0,0 +1,89 @@ +package db_provider + +import ( + "testing" + + "github.com/prebid/prebid-server/config" + "github.com/stretchr/testify/assert" +) + +func TestConnStringMySql(t *testing.T) { + type Params struct { + db string + host string + port int + username string + password string + } + + tests := []struct { + name string + params Params + connString string + }{ + { + params: Params{ + db: "", + }, + connString: "tcp()/", + }, + { + params: Params{ + db: "TestDB", + }, + connString: "tcp()/TestDB", + }, + { + params: Params{ + host: "example.com", + }, + connString: "tcp(example.com)/", + }, + { + params: Params{ + port: 20, + }, + connString: "tcp(:20)/", + }, + { + params: Params{ + username: "someuser", + }, + connString: "someuser@tcp()/", + }, + { + params: Params{ + username: "someuser", + password: "somepassword", + }, + connString: "someuser:somepassword@tcp()/", + }, + { + params: Params{ + db: "TestDB", + host: "example.com", + port: 20, + username: "someuser", + password: "somepassword", + }, + connString: "someuser:somepassword@tcp(example.com:20)/TestDB", + }, + } + + for _, test := range tests { + cfg := config.DatabaseConnection{ + Database: test.params.db, + Host: test.params.host, + Port: test.params.port, + Username: test.params.username, + Password: test.params.password, + } + + provider := MySqlDbProvider{ + cfg: cfg, + } + + connString := provider.ConnString() + assert.Equal(t, test.connString, connString, "Strings did not match") + } +} diff --git a/stored_requests/backends/db_provider/postgres_dbprovider.go b/stored_requests/backends/db_provider/postgres_dbprovider.go new file mode 100644 index 00000000000..cbd8d9d0913 --- /dev/null +++ b/stored_requests/backends/db_provider/postgres_dbprovider.go @@ -0,0 +1,138 @@ +package db_provider + +import ( + "bytes" + "context" + "database/sql" + "fmt" + "strconv" + "strings" + + "github.com/prebid/prebid-server/config" +) + +type PostgresDbProvider struct { + cfg config.DatabaseConnection + db *sql.DB +} + +func (provider *PostgresDbProvider) Config() config.DatabaseConnection { + return provider.cfg +} + +func (provider *PostgresDbProvider) Open() error { + db, err := sql.Open(provider.cfg.Driver, provider.ConnString()) + + if err != nil { + return err + } + + provider.db = db + return nil +} + +func (provider *PostgresDbProvider) Close() error { + if provider.db != nil { + db := provider.db + provider.db = nil + return db.Close() + } + + return nil +} + +func (provider *PostgresDbProvider) Ping() error { + return provider.db.Ping() +} + +func (provider *PostgresDbProvider) ConnString() string { + buffer := bytes.NewBuffer(nil) + + if provider.cfg.Host != "" { + buffer.WriteString("host=") + buffer.WriteString(provider.cfg.Host) + buffer.WriteString(" ") + } + + if provider.cfg.Port > 0 { + buffer.WriteString("port=") + buffer.WriteString(strconv.Itoa(provider.cfg.Port)) + buffer.WriteString(" ") + } + + if provider.cfg.Username != "" { + buffer.WriteString("user=") + buffer.WriteString(provider.cfg.Username) + buffer.WriteString(" ") + } + + if provider.cfg.Password != "" { + buffer.WriteString("password=") + buffer.WriteString(provider.cfg.Password) + buffer.WriteString(" ") + } + + if provider.cfg.Database != "" { + buffer.WriteString("dbname=") + buffer.WriteString(provider.cfg.Database) + buffer.WriteString(" ") + } + + buffer.WriteString("sslmode=disable") + return buffer.String() +} + +func (provider *PostgresDbProvider) PrepareQuery(template string, params ...QueryParam) (query string, args []interface{}) { + query = template + args = []interface{}{} + + for _, param := range params { + switch v := param.Value.(type) { + case []interface{}: + idList := v + idListStr := provider.createIdList(len(args), len(idList)) + args = append(args, idList...) + query = strings.Replace(query, "$"+param.Name, idListStr, -1) + default: + args = append(args, param.Value) + query = strings.Replace(query, "$"+param.Name, fmt.Sprintf("$%d", len(args)), -1) + } + } + return +} + +func (provider *PostgresDbProvider) QueryContext(ctx context.Context, template string, params ...QueryParam) (*sql.Rows, error) { + query, args := provider.PrepareQuery(template, params...) + return provider.db.QueryContext(ctx, query, args...) +} + +func (provider *PostgresDbProvider) createIdList(numSoFar int, numArgs int) string { + // Any empty list like "()" is illegal in Postgres. A (NULL) is the next best thing, + // though, since `id IN (NULL)` is valid for all "id" column types, and evaluates to an empty set. + // + // The query plan also suggests that it's basically free: + // + // explain SELECT id, requestData FROM stored_requests WHERE id in $ID_LIST; + // + // QUERY PLAN + // ------------------------------------------- + // Result (cost=0.00..0.00 rows=0 width=16) + // One-Time Filter: false + // (2 rows) + if numArgs == 0 { + return "(NULL)" + } + + final := bytes.NewBuffer(make([]byte, 0, 2+4*numArgs)) + final.WriteString("(") + for i := numSoFar + 1; i < numSoFar+numArgs; i++ { + final.WriteString("$") + final.WriteString(strconv.Itoa(i)) + final.WriteString(", ") + } + final.WriteString("$") + final.WriteString(strconv.Itoa(numSoFar + numArgs)) + final.WriteString(")") + + return final.String() +} diff --git a/stored_requests/backends/db_provider/postgres_dbprovider_test.go b/stored_requests/backends/db_provider/postgres_dbprovider_test.go new file mode 100644 index 00000000000..50d825dfdfb --- /dev/null +++ b/stored_requests/backends/db_provider/postgres_dbprovider_test.go @@ -0,0 +1,89 @@ +package db_provider + +import ( + "testing" + + "github.com/prebid/prebid-server/config" + "github.com/stretchr/testify/assert" +) + +func TestConnStringPostgres(t *testing.T) { + type Params struct { + db string + host string + port int + username string + password string + } + + tests := []struct { + name string + params Params + connString string + }{ + { + params: Params{ + db: "", + }, + connString: "sslmode=disable", + }, + { + params: Params{ + db: "TestDB", + }, + connString: "dbname=TestDB sslmode=disable", + }, + { + params: Params{ + host: "example.com", + }, + connString: "host=example.com sslmode=disable", + }, + { + params: Params{ + port: 20, + }, + connString: "port=20 sslmode=disable", + }, + { + params: Params{ + username: "someuser", + }, + connString: "user=someuser sslmode=disable", + }, + { + params: Params{ + username: "someuser", + password: "somepassword", + }, + connString: "user=someuser password=somepassword sslmode=disable", + }, + { + params: Params{ + db: "TestDB", + host: "example.com", + port: 20, + username: "someuser", + password: "somepassword", + }, + connString: "host=example.com port=20 user=someuser password=somepassword dbname=TestDB sslmode=disable", + }, + } + + for _, test := range tests { + cfg := config.DatabaseConnection{ + Database: test.params.db, + Host: test.params.host, + Port: test.params.port, + Username: test.params.username, + Password: test.params.password, + } + + provider := PostgresDbProvider{ + cfg: cfg, + } + + connString := provider.ConnString() + assert.Equal(t, test.connString, connString, "Strings did not match") + } +} diff --git a/stored_requests/backends/file_fetcher/fetcher.go b/stored_requests/backends/file_fetcher/fetcher.go index 56677e264d9..3d7c28a2a6e 100644 --- a/stored_requests/backends/file_fetcher/fetcher.go +++ b/stored_requests/backends/file_fetcher/fetcher.go @@ -4,7 +4,7 @@ import ( "context" "encoding/json" "fmt" - "io/ioutil" + "os" "strings" "github.com/prebid/prebid-server/stored_requests" @@ -104,7 +104,7 @@ func collectStoredData(directory string, fileSystem FileSystem, err error) (File if err != nil { return FileSystem{nil, nil}, err } - fileInfos, err := ioutil.ReadDir(directory) + fileInfos, err := os.ReadDir(directory) if err != nil { return FileSystem{nil, nil}, err } @@ -122,7 +122,7 @@ func collectStoredData(directory string, fileSystem FileSystem, err error) (File } else { if strings.HasSuffix(fileInfo.Name(), ".json") { // Skip the .gitignore - fileData, err := ioutil.ReadFile(fmt.Sprintf("%s/%s", directory, fileInfo.Name())) + fileData, err := os.ReadFile(fmt.Sprintf("%s/%s", directory, fileInfo.Name())) if err != nil { return FileSystem{nil, nil}, err } diff --git a/stored_requests/backends/http_fetcher/fetcher.go b/stored_requests/backends/http_fetcher/fetcher.go index 70fcf51f83b..60d3bcfabd3 100644 --- a/stored_requests/backends/http_fetcher/fetcher.go +++ b/stored_requests/backends/http_fetcher/fetcher.go @@ -5,7 +5,7 @@ import ( "context" "encoding/json" "fmt" - "io/ioutil" + "io" "net/http" "net/url" "strings" @@ -28,26 +28,26 @@ import ( // // The above endpoints should return a payload like: // -// { -// "requests": { -// "req1": { ... stored data for req1 ... }, -// "req2": { ... stored data for req2 ... }, -// }, -// "imps": { -// "imp1": { ... stored data for imp1 ... }, -// "imp2": { ... stored data for imp2 ... }, -// "imp3": null // If imp3 is not found -// } -// } -// or -// { -// "accounts": { -// "acc1": { ... config data for acc1 ... }, -// "acc2": { ... config data for acc2 ... }, -// }, -// } +// { +// "requests": { +// "req1": { ... stored data for req1 ... }, +// "req2": { ... stored data for req2 ... }, +// }, +// "imps": { +// "imp1": { ... stored data for imp1 ... }, +// "imp2": { ... stored data for imp2 ... }, +// "imp3": null // If imp3 is not found +// } +// } // +// or // +// { +// "accounts": { +// "acc1": { ... config data for acc1 ... }, +// "acc2": { ... config data for acc2 ... }, +// }, +// } func NewFetcher(client *http.Client, endpoint string) *HttpFetcher { // Do some work up-front to figure out if the (configurable) endpoint has a query string or not. // When we build requests, we'll either want to add `?request-ids=...&imp-ids=...` _or_ @@ -106,9 +106,11 @@ func (fetcher *HttpFetcher) FetchResponses(ctx context.Context, ids []string) (d // GET {endpoint}?account-ids=["account1","account2",...] // // The endpoint is expected to respond with a JSON map with accountID -> json.RawMessage -// { -// "account1": { ... account json ... } -// } +// +// { +// "account1": { ... account json ... } +// } +// // The JSON contents of account config is returned as-is (NOT validated) func (fetcher *HttpFetcher) FetchAccounts(ctx context.Context, accountIDs []string) (map[string]json.RawMessage, []error) { if len(accountIDs) == 0 { @@ -127,7 +129,7 @@ func (fetcher *HttpFetcher) FetchAccounts(ctx context.Context, accountIDs []stri } } defer httpResp.Body.Close() - respBytes, err := ioutil.ReadAll(httpResp.Body) + respBytes, err := io.ReadAll(httpResp.Body) if err != nil { return nil, []error{ fmt.Errorf(`Error fetching accounts %v via http: error reading response: %v`, accountIDs, err), @@ -199,7 +201,7 @@ func (fetcher *HttpFetcher) FetchCategories(ctx context.Context, primaryAdServer } defer httpResp.Body.Close() - respBytes, err := ioutil.ReadAll(httpResp.Body) + respBytes, err := io.ReadAll(httpResp.Body) tmp := make(map[string]stored_requests.Category) if err := json.Unmarshal(respBytes, &tmp); err != nil { @@ -225,7 +227,7 @@ func buildRequest(endpoint string, requestIDs []string, impIDs []string) (*http. } func unpackResponse(resp *http.Response) (requestData map[string]json.RawMessage, impData map[string]json.RawMessage, errs []error) { - respBytes, err := ioutil.ReadAll(resp.Body) + respBytes, err := io.ReadAll(resp.Body) if err != nil { errs = append(errs, err) return diff --git a/stored_requests/config/config.go b/stored_requests/config/config.go index f091126800a..9cb349d1f72 100644 --- a/stored_requests/config/config.go +++ b/stored_requests/config/config.go @@ -2,7 +2,6 @@ package config import ( "context" - "database/sql" "net/http" "time" @@ -13,6 +12,7 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/stored_requests" "github.com/prebid/prebid-server/stored_requests/backends/db_fetcher" + "github.com/prebid/prebid-server/stored_requests/backends/db_provider" "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" "github.com/prebid/prebid-server/stored_requests/backends/file_fetcher" "github.com/prebid/prebid-server/stored_requests/backends/http_fetcher" @@ -20,18 +20,11 @@ import ( "github.com/prebid/prebid-server/stored_requests/caches/nil_cache" "github.com/prebid/prebid-server/stored_requests/events" apiEvents "github.com/prebid/prebid-server/stored_requests/events/api" + databaseEvents "github.com/prebid/prebid-server/stored_requests/events/database" httpEvents "github.com/prebid/prebid-server/stored_requests/events/http" - postgresEvents "github.com/prebid/prebid-server/stored_requests/events/postgres" "github.com/prebid/prebid-server/util/task" ) -// This gets set to the connection string used when a database connection is made. We only support a single -// database currently, so all fetchers need to share the same db connection for now. -type dbConnection struct { - conn string - db *sql.DB -} - // CreateStoredRequests returns three things: // // 1. A Fetcher which can be used to get Stored Requests @@ -42,31 +35,28 @@ type dbConnection struct { // // 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 CreateStoredRequests(cfg *config.StoredRequests, metricsEngine metrics.MetricsEngine, client *http.Client, router *httprouter.Router, dbc *dbConnection) (fetcher stored_requests.AllFetcher, shutdown func()) { +func CreateStoredRequests(cfg *config.StoredRequests, metricsEngine metrics.MetricsEngine, client *http.Client, router *httprouter.Router, provider db_provider.DbProvider) (fetcher stored_requests.AllFetcher, shutdown func()) { // Create database connection if given options for one - if cfg.Postgres.ConnectionInfo.Database != "" { - conn := cfg.Postgres.ConnectionInfo.ConnString() - - if dbc.conn == "" { - glog.Infof("Connecting to Postgres for Stored %s. DB=%s, host=%s, port=%d, user=%s", + if cfg.Database.ConnectionInfo.Database != "" { + if provider == nil { + glog.Infof("Connecting to Database for Stored %s. Driver=%s, DB=%s, host=%s, port=%d, user=%s", cfg.DataType(), - cfg.Postgres.ConnectionInfo.Database, - cfg.Postgres.ConnectionInfo.Host, - cfg.Postgres.ConnectionInfo.Port, - cfg.Postgres.ConnectionInfo.Username) - db := newPostgresDB(cfg.DataType(), cfg.Postgres.ConnectionInfo) - dbc.conn = conn - dbc.db = db + cfg.Database.ConnectionInfo.Driver, + cfg.Database.ConnectionInfo.Database, + cfg.Database.ConnectionInfo.Host, + cfg.Database.ConnectionInfo.Port, + cfg.Database.ConnectionInfo.Username) + provider = db_provider.NewDbProvider(cfg.DataType(), cfg.Database.ConnectionInfo) } // Error out if config is trying to use multiple database connections for different stored requests (not supported yet) - if conn != dbc.conn { + if provider.Config() != cfg.Database.ConnectionInfo { glog.Fatal("Multiple database connection settings found in config, only a single database connection is currently supported.") } } - eventProducers := newEventProducers(cfg, client, dbc.db, metricsEngine, router) - fetcher = newFetcher(cfg, client, dbc.db) + eventProducers := newEventProducers(cfg, client, provider, metricsEngine, router) + fetcher = newFetcher(cfg, client, provider) var shutdown1 func() @@ -80,13 +70,13 @@ func CreateStoredRequests(cfg *config.StoredRequests, metricsEngine metrics.Metr if shutdown1 != nil { shutdown1() } - if dbc.db != nil { - db := dbc.db - dbc.db = nil - dbc.conn = "" - if err := db.Close(); err != nil { - glog.Errorf("Error closing DB connection: %v", err) - } + + if provider == nil { + return + } + + if err := provider.Close(); err != nil { + glog.Errorf("Error closing DB connection: %v", err) } } @@ -115,14 +105,14 @@ func NewStoredRequests(cfg *config.Configuration, metricsEngine metrics.MetricsE videoFetcher stored_requests.Fetcher, storedRespFetcher stored_requests.Fetcher) { - var dbc dbConnection + var provider db_provider.DbProvider - fetcher1, shutdown1 := CreateStoredRequests(&cfg.StoredRequests, metricsEngine, client, router, &dbc) - fetcher2, shutdown2 := CreateStoredRequests(&cfg.StoredRequestsAMP, metricsEngine, client, router, &dbc) - fetcher3, shutdown3 := CreateStoredRequests(&cfg.CategoryMapping, metricsEngine, client, router, &dbc) - fetcher4, shutdown4 := CreateStoredRequests(&cfg.StoredVideo, metricsEngine, client, router, &dbc) - fetcher5, shutdown5 := CreateStoredRequests(&cfg.Accounts, metricsEngine, client, router, &dbc) - fetcher6, shutdown6 := CreateStoredRequests(&cfg.StoredResponses, metricsEngine, client, router, &dbc) + fetcher1, shutdown1 := CreateStoredRequests(&cfg.StoredRequests, metricsEngine, client, router, provider) + fetcher2, shutdown2 := CreateStoredRequests(&cfg.StoredRequestsAMP, metricsEngine, client, router, provider) + fetcher3, shutdown3 := CreateStoredRequests(&cfg.CategoryMapping, metricsEngine, client, router, provider) + fetcher4, shutdown4 := CreateStoredRequests(&cfg.StoredVideo, metricsEngine, client, router, provider) + fetcher5, shutdown5 := CreateStoredRequests(&cfg.Accounts, metricsEngine, client, router, provider) + fetcher6, shutdown6 := CreateStoredRequests(&cfg.StoredResponses, metricsEngine, client, router, provider) fetcher = fetcher1.(stored_requests.Fetcher) ampFetcher = fetcher2.(stored_requests.Fetcher) @@ -159,17 +149,18 @@ func addListeners(cache stored_requests.Cache, eventProducers []events.EventProd } } -func newFetcher(cfg *config.StoredRequests, client *http.Client, db *sql.DB) (fetcher stored_requests.AllFetcher) { +func newFetcher(cfg *config.StoredRequests, client *http.Client, provider db_provider.DbProvider) (fetcher stored_requests.AllFetcher) { idList := make(stored_requests.MultiFetcher, 0, 3) if cfg.Files.Enabled { fFetcher := newFilesystem(cfg.DataType(), cfg.Files.Path) idList = append(idList, fFetcher) } - 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, cfg.Postgres.FetcherQueries.MakeQueryResponses)) - } else if cfg.Postgres.CacheInitialization.Query != "" && cfg.Postgres.PollUpdates.Query != "" { + if cfg.Database.FetcherQueries.QueryTemplate != "" { + glog.Infof("Loading Stored %s data via Database.\nQuery: %s", cfg.DataType(), cfg.Database.FetcherQueries.QueryTemplate) + idList = append(idList, db_fetcher.NewFetcher(provider, + cfg.Database.FetcherQueries.QueryTemplate, cfg.Database.FetcherQueries.QueryTemplate)) + } else if cfg.Database.CacheInitialization.Query != "" && cfg.Database.PollUpdates.Query != "" { //in this case data will be loaded to cache via poll for updates event idList = append(idList, empty_fetcher.EmptyFetcher{}) } @@ -202,28 +193,28 @@ func newCache(cfg *config.StoredRequests) stored_requests.Cache { return cache } -func newEventProducers(cfg *config.StoredRequests, client *http.Client, db *sql.DB, metricsEngine metrics.MetricsEngine, router *httprouter.Router) (eventProducers []events.EventProducer) { +func newEventProducers(cfg *config.StoredRequests, client *http.Client, provider db_provider.DbProvider, metricsEngine metrics.MetricsEngine, router *httprouter.Router) (eventProducers []events.EventProducer) { if cfg.CacheEvents.Enabled { eventProducers = append(eventProducers, newEventsAPI(router, cfg.CacheEvents.Endpoint)) } if cfg.HTTPEvents.RefreshRate != 0 && cfg.HTTPEvents.Endpoint != "" { eventProducers = append(eventProducers, newHttpEvents(client, cfg.HTTPEvents.TimeoutDuration(), cfg.HTTPEvents.RefreshRateDuration(), cfg.HTTPEvents.Endpoint)) } - if cfg.Postgres.CacheInitialization.Query != "" { - pgEventCfg := postgresEvents.PostgresEventProducerConfig{ - DB: db, + if cfg.Database.CacheInitialization.Query != "" { + dbEventCfg := databaseEvents.DatabaseEventProducerConfig{ + Provider: provider, RequestType: cfg.DataType(), - CacheInitQuery: cfg.Postgres.CacheInitialization.Query, - CacheInitTimeout: time.Duration(cfg.Postgres.CacheInitialization.Timeout) * time.Millisecond, - CacheUpdateQuery: cfg.Postgres.PollUpdates.Query, - CacheUpdateTimeout: time.Duration(cfg.Postgres.PollUpdates.Timeout) * time.Millisecond, + CacheInitQuery: cfg.Database.CacheInitialization.Query, + CacheInitTimeout: time.Duration(cfg.Database.CacheInitialization.Timeout) * time.Millisecond, + CacheUpdateQuery: cfg.Database.PollUpdates.Query, + CacheUpdateTimeout: time.Duration(cfg.Database.PollUpdates.Timeout) * time.Millisecond, MetricsEngine: metricsEngine, } - pgEventProducer := postgresEvents.NewPostgresEventProducer(pgEventCfg) - fetchInterval := time.Duration(cfg.Postgres.PollUpdates.RefreshRate) * time.Second - pgEventTickerTask := task.NewTickerTask(fetchInterval, pgEventProducer) - pgEventTickerTask.Start() - eventProducers = append(eventProducers, pgEventProducer) + dbEventProducer := databaseEvents.NewDatabaseEventProducer(dbEventCfg) + fetchInterval := time.Duration(cfg.Database.PollUpdates.RefreshRate) * time.Second + dbEventTickerTask := task.NewTickerTask(fetchInterval, dbEventProducer) + dbEventTickerTask.Start() + eventProducers = append(eventProducers, dbEventProducer) } return } @@ -251,19 +242,6 @@ func newFilesystem(dataType config.DataType, configPath string) stored_requests. return fetcher } -func newPostgresDB(dataType config.DataType, cfg config.PostgresConnection) *sql.DB { - db, err := sql.Open("postgres", cfg.ConnString()) - if err != nil { - glog.Fatalf("Failed to open %s postgres connection: %v", dataType, err) - } - - if err := db.Ping(); err != nil { - glog.Fatalf("Failed to ping %s postgres: %v", dataType, err) - } - - return db -} - // consolidate returns a single Fetcher from an array of fetchers of any size. func consolidate(dataType config.DataType, fetchers []stored_requests.AllFetcher) stored_requests.AllFetcher { if len(fetchers) == 0 { diff --git a/stored_requests/config/config_test.go b/stored_requests/config/config_test.go index 7cf6c38af0c..b06feea7d31 100644 --- a/stored_requests/config/config_test.go +++ b/stored_requests/config/config_test.go @@ -2,7 +2,6 @@ package config import ( "context" - "database/sql" "encoding/json" "errors" "net/http" @@ -17,6 +16,7 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/metrics" "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/stored_requests/backends/db_provider" "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" "github.com/prebid/prebid-server/stored_requests/backends/http_fetcher" "github.com/prebid/prebid-server/stored_requests/events" @@ -56,59 +56,68 @@ func TestNewEmptyFetcher(t *testing.T) { }, { config: &config.StoredRequests{ - Postgres: config.PostgresConfig{ - CacheInitialization: config.PostgresCacheInitializer{ + Database: config.DatabaseConfig{ + ConnectionInfo: config.DatabaseConnection{ + Driver: "postgres", + }, + CacheInitialization: config.DatabaseCacheInitializer{ Query: "test query", }, - PollUpdates: config.PostgresUpdatePolling{ + PollUpdates: config.DatabaseUpdatePolling{ Query: "test poll query", }, - FetcherQueries: config.PostgresFetcherQueries{ + FetcherQueries: config.DatabaseFetcherQueries{ 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", + description: "If Database fetcher query is not defined, but Database Cache init query and Database update polling query are defined EmptyFetcher should be returned", }, { config: &config.StoredRequests{ - Postgres: config.PostgresConfig{ - CacheInitialization: config.PostgresCacheInitializer{ + Database: config.DatabaseConfig{ + ConnectionInfo: config.DatabaseConnection{ + Driver: "postgres", + }, + CacheInitialization: config.DatabaseCacheInitializer{ Query: "", }, - PollUpdates: config.PostgresUpdatePolling{ + PollUpdates: config.DatabaseUpdatePolling{ Query: "", }, - FetcherQueries: config.PostgresFetcherQueries{ + FetcherQueries: config.DatabaseFetcherQueries{ 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", + description: "If Database fetcher query is defined, but Database Cache init query and Database update polling query are not defined not EmptyFetcher (DBFetcher) should be returned", }, { config: &config.StoredRequests{ - Postgres: config.PostgresConfig{ - CacheInitialization: config.PostgresCacheInitializer{ + Database: config.DatabaseConfig{ + ConnectionInfo: config.DatabaseConnection{ + Driver: "postgres", + }, + CacheInitialization: config.DatabaseCacheInitializer{ Query: "test cache query", }, - PollUpdates: config.PostgresUpdatePolling{ + PollUpdates: config.DatabaseUpdatePolling{ Query: "test poll query", }, - FetcherQueries: config.PostgresFetcherQueries{ + FetcherQueries: config.DatabaseFetcherQueries{ 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", + description: "If Database fetcher query is defined and Database Cache init query and Database update polling query are defined not EmptyFetcher (DBFetcher) should be returned", }, } for _, test := range testCases { - fetcher := newFetcher(test.config, nil, &sql.DB{}) + fetcher := newFetcher(test.config, nil, db_provider.DbProviderMock{}) 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") @@ -190,18 +199,18 @@ func TestNewInMemoryAccountCache(t *testing.T) { assert.True(t, isEmptyCacheType(cache.Responses), "The newCache method should return an empty Responses cache for Accounts config") } -func TestNewPostgresEventProducers(t *testing.T) { +func TestNewDatabaseEventProducers(t *testing.T) { metricsMock := &metrics.MetricsEngineMock{} metricsMock.Mock.On("RecordStoredDataFetchTime", mock.Anything, mock.Anything).Return() metricsMock.Mock.On("RecordStoredDataError", mock.Anything).Return() cfg := &config.StoredRequests{ - Postgres: config.PostgresConfig{ - CacheInitialization: config.PostgresCacheInitializer{ + Database: config.DatabaseConfig{ + CacheInitialization: config.DatabaseCacheInitializer{ Timeout: 50, Query: "SELECT id, requestData, type FROM stored_data", }, - PollUpdates: config.PostgresUpdatePolling{ + PollUpdates: config.DatabaseUpdatePolling{ RefreshRate: 20, Timeout: 50, Query: "SELECT id, requestData, type FROM stored_data WHERE last_updated > $1", @@ -209,13 +218,13 @@ func TestNewPostgresEventProducers(t *testing.T) { }, } client := &http.Client{} - db, mock, err := sqlmock.New() + provider, mock, err := db_provider.NewDbProviderMock() if err != nil { t.Fatalf("Failed to create mock: %v", err) } - mock.ExpectQuery("^" + regexp.QuoteMeta(cfg.Postgres.CacheInitialization.Query) + "$").WillReturnError(errors.New("Query failed")) + mock.ExpectQuery("^" + regexp.QuoteMeta(cfg.Database.CacheInitialization.Query) + "$").WillReturnError(errors.New("Query failed")) - evProducers := newEventProducers(cfg, client, db, metricsMock, nil) + evProducers := newEventProducers(cfg, client, provider, metricsMock, nil) assertProducerLength(t, evProducers, 1) assertExpectationsMet(t, mock) diff --git a/stored_requests/data/by_id/accounts/test.json b/stored_requests/data/by_id/accounts/test.json index 07492f756ff..699f6bd1e57 100644 --- a/stored_requests/data/by_id/accounts/test.json +++ b/stored_requests/data/by_id/accounts/test.json @@ -1,19 +1,52 @@ { - "id": "test", - "name": "test account", - "disabled": true, - "cache_ttl": { - "banner": 600, - "video": 3600, - "native": 3600, - "audio": 3600 + "id": "test", + "name": "test account", + "disabled": true, + "cache_ttl": { + "banner": 600, + "video": 3600, + "native": 3600, + "audio": 3600 + }, + "events": { + "enabled": true + }, + "cookie_sync": { + "default_limt": 20, + "max_limit": 50, + "default_coop_sync": true + }, + "hooks": { + "execution_plan": { + "endpoints": { + "/openrtb2/auction": { + "stages": { + "entrypoint": { + "groups": [ + { + "timeout": 5, + "hook_sequence": [ + { + "module_code": "acme.foobar", + "hook_impl_code": "validate-query-params" + } + ] + } + ] + } + } + } + } }, - "events": { - "enabled": true - }, - "cookie_sync": { - "default_limt": 20, - "max_limit": 50, - "default_coop_sync": true + "modules": { + "acme": { + "foobar": { + "allow_reject": false, + "attributes": { + "name": "foo" + } + } + } } + } } diff --git a/stored_requests/events/api/api.go b/stored_requests/events/api/api.go index 6dce4ebaad6..bf8edd2d849 100644 --- a/stored_requests/events/api/api.go +++ b/stored_requests/events/api/api.go @@ -2,7 +2,7 @@ package api import ( "encoding/json" - "io/ioutil" + "io" "net/http" "github.com/julienschmidt/httprouter" @@ -35,7 +35,7 @@ func NewEventsAPI() (events.EventProducer, httprouter.Handle) { func (api *eventsAPI) HandleEvent(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { if r.Method == "POST" { - body, err := ioutil.ReadAll(r.Body) + body, err := io.ReadAll(r.Body) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte("Missing update data.\n")) @@ -51,7 +51,7 @@ func (api *eventsAPI) HandleEvent(w http.ResponseWriter, r *http.Request, _ http api.saves <- save } else if r.Method == "DELETE" { - body, err := ioutil.ReadAll(r.Body) + body, err := io.ReadAll(r.Body) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte("Missing invalidation data.\n")) diff --git a/stored_requests/events/postgres/database.go b/stored_requests/events/database/database.go similarity index 82% rename from stored_requests/events/postgres/database.go rename to stored_requests/events/database/database.go index a666fae5106..24eddf214eb 100644 --- a/stored_requests/events/postgres/database.go +++ b/stored_requests/events/database/database.go @@ -1,4 +1,4 @@ -package postgres +package database import ( "bytes" @@ -11,6 +11,7 @@ import ( "github.com/golang/glog" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/stored_requests/backends/db_provider" "github.com/prebid/prebid-server/stored_requests/events" "github.com/prebid/prebid-server/util/timeutil" ) @@ -25,10 +26,11 @@ var storedDataTypeMetricMap = map[config.DataType]metrics.StoredDataType{ config.VideoDataType: metrics.VideoDataType, config.AMPRequestDataType: metrics.AMPDataType, config.AccountDataType: metrics.AccountDataType, + config.ResponseDataType: metrics.ResponseDataType, } -type PostgresEventProducerConfig struct { - DB *sql.DB +type DatabaseEventProducerConfig struct { + Provider db_provider.DbProvider RequestType config.DataType CacheInitQuery string CacheInitTimeout time.Duration @@ -37,20 +39,20 @@ type PostgresEventProducerConfig struct { MetricsEngine metrics.MetricsEngine } -type PostgresEventProducer struct { - cfg PostgresEventProducerConfig +type DatabaseEventProducer struct { + cfg DatabaseEventProducerConfig lastUpdate time.Time invalidations chan events.Invalidation saves chan events.Save time timeutil.Time } -func NewPostgresEventProducer(cfg PostgresEventProducerConfig) (eventProducer *PostgresEventProducer) { - if cfg.DB == nil { - glog.Fatalf("The Postgres Stored %s Loader needs a database connection to work.", cfg.RequestType) +func NewDatabaseEventProducer(cfg DatabaseEventProducerConfig) (eventProducer *DatabaseEventProducer) { + if cfg.Provider == nil { + glog.Fatalf("The Database Stored %s Loader needs a database connection to work.", cfg.RequestType) } - return &PostgresEventProducer{ + return &DatabaseEventProducer{ cfg: cfg, lastUpdate: time.Time{}, saves: make(chan events.Save, 1), @@ -59,7 +61,7 @@ func NewPostgresEventProducer(cfg PostgresEventProducerConfig) (eventProducer *P } } -func (e *PostgresEventProducer) Run() error { +func (e *DatabaseEventProducer) Run() error { if e.lastUpdate.IsZero() { return e.fetchAll() } @@ -67,21 +69,21 @@ func (e *PostgresEventProducer) Run() error { return e.fetchDelta() } -func (e *PostgresEventProducer) Saves() <-chan events.Save { +func (e *DatabaseEventProducer) Saves() <-chan events.Save { return e.saves } -func (e *PostgresEventProducer) Invalidations() <-chan events.Invalidation { +func (e *DatabaseEventProducer) Invalidations() <-chan events.Invalidation { return e.invalidations } -func (e *PostgresEventProducer) fetchAll() (fetchErr error) { +func (e *DatabaseEventProducer) fetchAll() (fetchErr error) { timeout := e.cfg.CacheInitTimeout * time.Millisecond ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() startTime := e.time.Now().UTC() - rows, err := e.cfg.DB.QueryContext(ctx, e.cfg.CacheInitQuery) + rows, err := e.cfg.Provider.QueryContext(ctx, e.cfg.CacheInitQuery) elapsedTime := time.Since(startTime) e.recordFetchTime(elapsedTime, metrics.FetchAll) @@ -112,13 +114,18 @@ func (e *PostgresEventProducer) fetchAll() (fetchErr error) { return nil } -func (e *PostgresEventProducer) fetchDelta() (fetchErr error) { +func (e *DatabaseEventProducer) fetchDelta() (fetchErr error) { timeout := e.cfg.CacheUpdateTimeout * time.Millisecond ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() startTime := e.time.Now().UTC() - rows, err := e.cfg.DB.QueryContext(ctx, e.cfg.CacheUpdateQuery, e.lastUpdate) + + params := []db_provider.QueryParam{ + {Name: "LAST_UPDATED", Value: e.lastUpdate}, + } + + rows, err := e.cfg.Provider.QueryContext(ctx, e.cfg.CacheUpdateQuery, params...) elapsedTime := time.Since(startTime) e.recordFetchTime(elapsedTime, metrics.FetchDelta) @@ -149,7 +156,7 @@ func (e *PostgresEventProducer) fetchDelta() (fetchErr error) { return nil } -func (e *PostgresEventProducer) recordFetchTime(elapsedTime time.Duration, fetchType metrics.StoredDataFetchType) { +func (e *DatabaseEventProducer) recordFetchTime(elapsedTime time.Duration, fetchType metrics.StoredDataFetchType) { e.cfg.MetricsEngine.RecordStoredDataFetchTime( metrics.StoredDataLabels{ DataType: storedDataTypeMetricMap[e.cfg.RequestType], @@ -157,7 +164,7 @@ func (e *PostgresEventProducer) recordFetchTime(elapsedTime time.Duration, fetch }, elapsedTime) } -func (e *PostgresEventProducer) recordError(errorType metrics.StoredDataError) { +func (e *DatabaseEventProducer) recordError(errorType metrics.StoredDataError) { e.cfg.MetricsEngine.RecordStoredDataError( metrics.StoredDataLabels{ DataType: storedDataTypeMetricMap[e.cfg.RequestType], @@ -167,7 +174,7 @@ func (e *PostgresEventProducer) recordError(errorType metrics.StoredDataError) { // sendEvents reads the rows and sends notifications into the channel for any updates. // If it returns an error, then callers can be certain that no events were sent to the channels. -func (e *PostgresEventProducer) sendEvents(rows *sql.Rows) (err error) { +func (e *DatabaseEventProducer) sendEvents(rows *sql.Rows) (err error) { storedRequestData := make(map[string]json.RawMessage) storedImpData := make(map[string]json.RawMessage) storedRespData := make(map[string]json.RawMessage) diff --git a/stored_requests/events/postgres/database_test.go b/stored_requests/events/database/database_test.go similarity index 96% rename from stored_requests/events/postgres/database_test.go rename to stored_requests/events/database/database_test.go index c3dfa0ae70e..8ce21bfde95 100644 --- a/stored_requests/events/postgres/database_test.go +++ b/stored_requests/events/database/database_test.go @@ -1,4 +1,4 @@ -package postgres +package database import ( "encoding/json" @@ -9,6 +9,7 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/stored_requests/backends/db_provider" "github.com/prebid/prebid-server/stored_requests/events" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -113,7 +114,7 @@ func TestFetchAllSuccess(t *testing.T) { } for _, tt := range tests { - db, dbMock, _ := sqlmock.New() + provider, dbMock, _ := db_provider.NewDbProviderMock() dbMock.ExpectQuery(fakeQueryRegex()).WillReturnRows(tt.giveMockRows) metricsMock := &metrics.MetricsEngineMock{} @@ -122,8 +123,8 @@ func TestFetchAllSuccess(t *testing.T) { DataFetchType: metrics.FetchAll, }, mock.Anything).Return() - eventProducer := NewPostgresEventProducer(PostgresEventProducerConfig{ - DB: db, + eventProducer := NewDatabaseEventProducer(DatabaseEventProducerConfig{ + Provider: provider, RequestType: config.RequestDataType, CacheInitTimeout: 100 * time.Millisecond, CacheInitQuery: fakeQuery, @@ -196,7 +197,7 @@ func TestFetchAllErrors(t *testing.T) { } for _, tt := range tests { - db, dbMock, _ := sqlmock.New() + provider, dbMock, _ := db_provider.NewDbProviderMock() if tt.giveMockRows == nil { dbMock.ExpectQuery(fakeQueryRegex()).WillReturnError(errors.New("Query failed.")) } else { @@ -213,8 +214,8 @@ func TestFetchAllErrors(t *testing.T) { Error: tt.wantRecordedError, }).Return() - eventProducer := NewPostgresEventProducer(PostgresEventProducerConfig{ - DB: db, + eventProducer := NewDatabaseEventProducer(DatabaseEventProducerConfig{ + Provider: provider, RequestType: config.RequestDataType, CacheInitTimeout: time.Duration(tt.giveTimeoutMS) * time.Millisecond, CacheInitQuery: fakeQuery, @@ -349,7 +350,7 @@ func TestFetchDeltaSuccess(t *testing.T) { } for _, tt := range tests { - db, dbMock, _ := sqlmock.New() + provider, dbMock, _ := db_provider.NewDbProviderMock() dbMock.ExpectQuery(fakeQueryRegex()).WillReturnRows(tt.giveMockRows) metricsMock := &metrics.MetricsEngineMock{} @@ -358,8 +359,8 @@ func TestFetchDeltaSuccess(t *testing.T) { DataFetchType: metrics.FetchDelta, }, mock.Anything).Return() - eventProducer := NewPostgresEventProducer(PostgresEventProducerConfig{ - DB: db, + eventProducer := NewDatabaseEventProducer(DatabaseEventProducerConfig{ + Provider: provider, RequestType: config.RequestDataType, CacheUpdateTimeout: 100 * time.Millisecond, CacheUpdateQuery: fakeQuery, @@ -437,7 +438,7 @@ func TestFetchDeltaErrors(t *testing.T) { } for _, tt := range tests { - db, dbMock, _ := sqlmock.New() + provider, dbMock, _ := db_provider.NewDbProviderMock() if tt.giveMockRows == nil { dbMock.ExpectQuery(fakeQueryRegex()).WillReturnError(errors.New("Query failed.")) } else { @@ -454,8 +455,8 @@ func TestFetchDeltaErrors(t *testing.T) { Error: tt.wantRecordedError, }).Return() - eventProducer := NewPostgresEventProducer(PostgresEventProducerConfig{ - DB: db, + eventProducer := NewDatabaseEventProducer(DatabaseEventProducerConfig{ + Provider: provider, RequestType: config.RequestDataType, CacheUpdateTimeout: time.Duration(tt.giveTimeoutMS) * time.Millisecond, CacheUpdateQuery: fakeQuery, diff --git a/stored_requests/events/http/http.go b/stored_requests/events/http/http.go index 6450e0de2a0..1c4f8fdff73 100644 --- a/stored_requests/events/http/http.go +++ b/stored_requests/events/http/http.go @@ -4,7 +4,7 @@ import ( "bytes" "context" "encoding/json" - "io/ioutil" + "io" httpCore "net/http" "net/url" "time" @@ -23,40 +23,44 @@ import ( // It expects the following endpoint to exist remotely: // // GET {endpoint} -// -- Returns all the known Stored Requests and Stored Imps. +// +// -- Returns all the known Stored Requests and Stored Imps. +// // GET {endpoint}?last-modified={timestamp} -// -- Returns the Stored Requests and Stored Imps which have been updated since the last timestamp. -// This timestamp will be sent in the rfc3339 format, using UTC and no timezone shift. -// For more info, see: https://tools.ietf.org/html/rfc3339 +// +// -- Returns the Stored Requests and Stored Imps which have been updated since the last timestamp. +// This timestamp will be sent in the rfc3339 format, using UTC and no timezone shift. +// For more info, see: https://tools.ietf.org/html/rfc3339 // // The responses should be JSON like this: // -// { -// "requests": { -// "request1": { ... stored request data ... }, -// "request2": { ... stored request data ... }, -// "request3": { ... stored request data ... }, -// }, -// "imps": { -// "imp1": { ... stored data for imp1 ... }, -// "imp2": { ... stored data for imp2 ... }, -// }, -// "responses": { -// "resp1": { ... stored data for resp1 ... }, -// "resp2": { ... stored data for resp2 ... }, -// } -// } +// { +// "requests": { +// "request1": { ... stored request data ... }, +// "request2": { ... stored request data ... }, +// "request3": { ... stored request data ... }, +// }, +// "imps": { +// "imp1": { ... stored data for imp1 ... }, +// "imp2": { ... stored data for imp2 ... }, +// }, +// "responses": { +// "resp1": { ... stored data for resp1 ... }, +// "resp2": { ... stored data for resp2 ... }, +// } +// } +// // or -// { -// "accounts": { -// "acc1": { ... config data for acc1 ... }, -// "acc2": { ... config data for acc2 ... }, -// }, -// } +// +// { +// "accounts": { +// "acc1": { ... config data for acc1 ... }, +// "acc2": { ... config data for acc2 ... }, +// }, +// } // // To signal deletions, the endpoint may return { "deleted": true } // in place of the Stored Data if the "last-modified" param existed. -// func NewHTTPEvents(client *httpCore.Client, endpoint string, ctxProducer func() (ctx context.Context, canceller func()), refreshRate time.Duration) *HTTPEvents { // If we're not given a function to produce Contexts, use the Background one. if ctxProducer == nil { @@ -168,7 +172,7 @@ func (e *HTTPEvents) parse(endpoint string, resp *httpCore.Response, err error) } defer resp.Body.Close() - respBytes, err := ioutil.ReadAll(resp.Body) + respBytes, err := io.ReadAll(resp.Body) if err != nil { glog.Errorf("Failed to read body of GET %s for Stored Requests: %v", endpoint, err) return nil, false diff --git a/stored_responses/stored_responses.go b/stored_responses/stored_responses.go index f354060011e..f98e3d42cb4 100644 --- a/stored_responses/stored_responses.go +++ b/stored_responses/stored_responses.go @@ -4,8 +4,9 @@ import ( "context" "encoding/json" "fmt" + "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/stored_requests" ) @@ -17,6 +18,8 @@ type StoredResponseIdToStoredResponse map[string]json.RawMessage type BidderImpsWithBidResponses map[openrtb_ext.BidderName]map[string]json.RawMessage type ImpsWithBidResponses map[string]json.RawMessage type ImpBidderStoredResp map[string]map[string]json.RawMessage +type ImpBidderReplaceImpID map[string]map[string]bool +type BidderImpReplaceImpID map[string]map[string]bool func InitStoredBidResponses(req *openrtb2.BidRequest, storedBidResponses ImpBidderStoredResp) BidderImpsWithBidResponses { removeImpsWithStoredResponses(req, storedBidResponses) @@ -57,7 +60,9 @@ func extractStoredResponsesIds(impInfo []ImpExtPrebidData, bidderMap map[string]openrtb_ext.BidderName) ( StoredResponseIDs, ImpBiddersWithBidResponseIDs, - ImpsWithAuctionResponseIDs, error, + ImpsWithAuctionResponseIDs, + ImpBidderReplaceImpID, + error, ) { // extractStoredResponsesIds returns: // 1) all stored responses ids from all imps @@ -66,16 +71,18 @@ func extractStoredResponsesIds(impInfo []ImpExtPrebidData, impBiddersWithBidResponseIDs := ImpBiddersWithBidResponseIDs{} // 3) imp id to stored resp id impAuctionResponseIDs := ImpsWithAuctionResponseIDs{} + // 4) imp id to bidder to bool replace imp in response + impBidderReplaceImp := ImpBidderReplaceImpID{} for index, impData := range impInfo { impId, err := jsonparser.GetString(impData.Imp, "id") if err != nil { - return nil, nil, nil, fmt.Errorf("request.imp[%d] missing required field: \"id\"", index) + return nil, nil, nil, nil, fmt.Errorf("request.imp[%d] missing required field: \"id\"", index) } if impData.ImpExtPrebid.StoredAuctionResponse != nil { if len(impData.ImpExtPrebid.StoredAuctionResponse.ID) == 0 { - return nil, nil, nil, fmt.Errorf("request.imp[%d] has ext.prebid.storedauctionresponse specified, but \"id\" field is missing ", index) + return nil, nil, nil, nil, fmt.Errorf("request.imp[%d] has ext.prebid.storedauctionresponse specified, but \"id\" field is missing ", index) } allStoredResponseIDs = append(allStoredResponseIDs, impData.ImpExtPrebid.StoredAuctionResponse.ID) @@ -85,24 +92,35 @@ func extractStoredResponsesIds(impInfo []ImpExtPrebidData, if len(impData.ImpExtPrebid.StoredBidResponse) > 0 { bidderStoredRespId := make(map[string]string) + bidderReplaceImpId := make(map[string]bool) for _, bidderResp := range impData.ImpExtPrebid.StoredBidResponse { if len(bidderResp.ID) == 0 || len(bidderResp.Bidder) == 0 { - return nil, nil, nil, fmt.Errorf("request.imp[%d] has ext.prebid.storedbidresponse specified, but \"id\" or/and \"bidder\" fields are missing ", index) + return nil, nil, nil, nil, fmt.Errorf("request.imp[%d] has ext.prebid.storedbidresponse specified, but \"id\" or/and \"bidder\" fields are missing ", index) } //check if bidder is valid/exists if _, isValid := bidderMap[bidderResp.Bidder]; !isValid { - return nil, nil, nil, fmt.Errorf("request.imp[impId: %s].ext contains unknown bidder: %s. Did you forget an alias in request.ext.prebid.aliases?", impId, bidderResp.Bidder) + return nil, nil, nil, nil, fmt.Errorf("request.imp[impId: %s].ext.prebid.bidder contains unknown bidder: %s. Did you forget an alias in request.ext.prebid.aliases?", impId, bidderResp.Bidder) } // bidder is unique per one bid stored response // if more than one bidder specified the last defined bidder id will take precedence bidderStoredRespId[bidderResp.Bidder] = bidderResp.ID impBiddersWithBidResponseIDs[impId] = bidderStoredRespId + + // stored response config can specify if imp id should be replaced with imp id from request + replaceImpId := true + if bidderResp.ReplaceImpId != nil { + // replaceimpid is true if not specified + replaceImpId = *bidderResp.ReplaceImpId + } + bidderReplaceImpId[bidderResp.Bidder] = replaceImpId + impBidderReplaceImp[impId] = bidderReplaceImpId + //storedAuctionResponseIds are not unique, but fetch will return single data for repeated ids allStoredResponseIDs = append(allStoredResponseIDs, bidderResp.ID) } } } - return allStoredResponseIDs, impBiddersWithBidResponseIDs, impAuctionResponseIDs, nil + return allStoredResponseIDs, impBiddersWithBidResponseIDs, impAuctionResponseIDs, impBidderReplaceImp, nil } // ProcessStoredResponses takes the incoming request as JSON with any @@ -111,26 +129,41 @@ func extractStoredResponsesIds(impInfo []ImpExtPrebidData, // Note that processStoredResponses must be called after processStoredRequests // because stored imps and stored requests can contain stored auction responses and stored bid responses // so the stored requests/imps have to be merged into the incoming request prior to processing stored auction responses. -func ProcessStoredResponses(ctx context.Context, requestJson []byte, storedRespFetcher stored_requests.Fetcher, bidderMap map[string]openrtb_ext.BidderName) (ImpsWithBidResponses, ImpBidderStoredResp, []error) { +func ProcessStoredResponses(ctx context.Context, requestJson []byte, storedRespFetcher stored_requests.Fetcher, bidderMap map[string]openrtb_ext.BidderName) (ImpsWithBidResponses, ImpBidderStoredResp, BidderImpReplaceImpID, []error) { impInfo, errs := parseImpInfo(requestJson) if len(errs) > 0 { - return nil, nil, errs + return nil, nil, nil, errs } - storedResponsesIds, impBidderToStoredBidResponseId, impIdToRespId, err := extractStoredResponsesIds(impInfo, bidderMap) + storedResponsesIds, impBidderToStoredBidResponseId, impIdToRespId, impBidderReplaceImp, err := extractStoredResponsesIds(impInfo, bidderMap) if err != nil { - return nil, nil, append(errs, err) + return nil, nil, nil, append(errs, err) } if len(storedResponsesIds) > 0 { storedResponses, errs := storedRespFetcher.FetchResponses(ctx, storedResponsesIds) if len(errs) > 0 { - return nil, nil, errs + return nil, nil, nil, errs } + bidderImpIdReplaceImp := flipMap(impBidderReplaceImp) impIdToStoredResp, impBidderToStoredBidResponse := buildStoredResponsesMaps(storedResponses, impBidderToStoredBidResponseId, impIdToRespId) - return impIdToStoredResp, impBidderToStoredBidResponse, nil + return impIdToStoredResp, impBidderToStoredBidResponse, bidderImpIdReplaceImp, nil + } + return nil, nil, nil, nil +} + +// flipMap takes map[impID][bidderName]replaceImpId and modifies it to map[bidderName][impId]replaceImpId +func flipMap(impBidderReplaceImpId ImpBidderReplaceImpID) BidderImpReplaceImpID { + flippedMap := BidderImpReplaceImpID{} + for impId, impData := range impBidderReplaceImpId { + for bidder, replaceImpId := range impData { + if _, ok := flippedMap[bidder]; !ok { + flippedMap[bidder] = make(map[string]bool) + } + flippedMap[bidder][impId] = replaceImpId + } } - return nil, nil, nil + return flippedMap } func buildStoredResponsesMaps(storedResponses StoredResponseIdToStoredResponse, impBidderToStoredBidResponseId ImpBiddersWithBidResponseIDs, impIdToRespId ImpsWithAuctionResponseIDs) (ImpsWithBidResponses, ImpBidderStoredResp) { diff --git a/stored_responses/stored_responses_test.go b/stored_responses/stored_responses_test.go index e63bbc995a1..c44adc0ec66 100644 --- a/stored_responses/stored_responses_test.go +++ b/stored_responses/stored_responses_test.go @@ -4,10 +4,11 @@ import ( "context" "encoding/json" "errors" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "testing" + + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" - "testing" ) func TestRemoveImpsWithStoredResponses(t *testing.T) { @@ -227,7 +228,7 @@ func TestProcessStoredAuctionAndBidResponsesErrors(t *testing.T) { } } ]}`), - expectedErrorList: []error{errors.New("request.imp[impId: imp-id1].ext contains unknown bidder: testBidder123. Did you forget an alias in request.ext.prebid.aliases?")}, + expectedErrorList: []error{errors.New("request.imp[impId: imp-id1].ext.prebid.bidder contains unknown bidder: testBidder123. Did you forget an alias in request.ext.prebid.aliases?")}, }, { description: "Invalid stored auction response format: empty stored Auction Response Id in second imp", @@ -284,7 +285,7 @@ func TestProcessStoredAuctionAndBidResponsesErrors(t *testing.T) { } for _, test := range testCases { - _, _, errorList := ProcessStoredResponses(nil, test.requestJson, nil, bidderMap) + _, _, _, errorList := ProcessStoredResponses(nil, test.requestJson, nil, bidderMap) assert.Equalf(t, test.expectedErrorList, errorList, "Error doesn't match: %s\n", test.description) } @@ -307,6 +308,7 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { requestJson []byte expectedStoredAuctionResponses ImpsWithBidResponses expectedStoredBidResponses ImpBidderStoredResp + expectedBidderImpReplaceImpID BidderImpReplaceImpID }{ { description: "No stored responses", @@ -322,6 +324,7 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { ]}`), expectedStoredAuctionResponses: nil, expectedStoredBidResponses: nil, + expectedBidderImpReplaceImpID: nil, }, { description: "Stored auction response one imp", @@ -343,7 +346,8 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { expectedStoredAuctionResponses: ImpsWithBidResponses{ "imp-id1": bidStoredResp1, }, - expectedStoredBidResponses: ImpBidderStoredResp{}, + expectedStoredBidResponses: ImpBidderStoredResp{}, + expectedBidderImpReplaceImpID: BidderImpReplaceImpID{}, }, { description: "Stored bid response one imp", @@ -366,6 +370,9 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { expectedStoredBidResponses: ImpBidderStoredResp{ "imp-id1": {"bidderA": bidStoredResp1}, }, + expectedBidderImpReplaceImpID: BidderImpReplaceImpID{ + "bidderA": map[string]bool{"imp-id1": true}, + }, }, { description: "Stored bid responses two bidders one imp", @@ -378,8 +385,8 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { }, "prebid": { "storedbidresponse": [ - {"bidder":"bidderA", "id": "1"}, - {"bidder":"bidderB", "id": "2"} + {"bidder":"bidderA", "id": "1", "replaceimpid": true}, + {"bidder":"bidderB", "id": "2", "replaceimpid": false} ] } } @@ -389,6 +396,10 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { expectedStoredBidResponses: ImpBidderStoredResp{ "imp-id1": {"bidderA": bidStoredResp1, "bidderB": bidStoredResp2}, }, + expectedBidderImpReplaceImpID: BidderImpReplaceImpID{ + "bidderA": map[string]bool{"imp-id1": true}, + "bidderB": map[string]bool{"imp-id1": false}, + }, }, { //This is not a valid scenario for real auction request, added for testing purposes @@ -418,6 +429,10 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { expectedStoredBidResponses: ImpBidderStoredResp{ "imp-id1": {"bidderA": bidStoredResp1, "bidderB": bidStoredResp2}, }, + expectedBidderImpReplaceImpID: BidderImpReplaceImpID{ + "bidderA": map[string]bool{"imp-id1": true}, + "bidderB": map[string]bool{"imp-id1": true}, + }, }, { description: "Stored auction response three imps", @@ -467,7 +482,8 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { "imp-id2": bidStoredResp2, "imp-id3": bidStoredResp3, }, - expectedStoredBidResponses: ImpBidderStoredResp{}, + expectedStoredBidResponses: ImpBidderStoredResp{}, + expectedBidderImpReplaceImpID: BidderImpReplaceImpID{}, }, { description: "Stored auction response three imps duplicated stored auction response", @@ -517,7 +533,8 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { "imp-id2": bidStoredResp2, "imp-id3": bidStoredResp2, }, - expectedStoredBidResponses: ImpBidderStoredResp{}, + expectedStoredBidResponses: ImpBidderStoredResp{}, + expectedBidderImpReplaceImpID: BidderImpReplaceImpID{}, }, { description: "Stored bid responses two bidders two imp", @@ -530,7 +547,7 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { }, "prebid": { "storedbidresponse": [ - {"bidder":"bidderA", "id": "1"}, + {"bidder":"bidderA", "id": "1", "replaceimpid": false}, {"bidder":"bidderB", "id": "2"} ] } @@ -545,7 +562,7 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { "prebid": { "storedbidresponse": [ {"bidder":"bidderA", "id": "3"}, - {"bidder":"bidderB", "id": "2"} + {"bidder":"bidderB", "id": "2", "replaceimpid": false} ] } } @@ -556,18 +573,78 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { "imp-id1": {"bidderA": bidStoredResp1, "bidderB": bidStoredResp2}, "imp-id2": {"bidderA": bidStoredResp3, "bidderB": bidStoredResp2}, }, + expectedBidderImpReplaceImpID: BidderImpReplaceImpID{ + "bidderA": map[string]bool{"imp-id1": false, "imp-id2": true}, + "bidderB": map[string]bool{"imp-id1": true, "imp-id2": false}, + }, }, } for _, test := range testCases { - storedAuctionResponses, storedBidResponses, errorList := ProcessStoredResponses(nil, test.requestJson, fetcher, bidderMap) + storedAuctionResponses, storedBidResponses, bidderImpReplaceImpId, errorList := ProcessStoredResponses(nil, test.requestJson, fetcher, bidderMap) assert.Equal(t, test.expectedStoredAuctionResponses, storedAuctionResponses, "storedAuctionResponses doesn't match: %s\n", test.description) assert.Equalf(t, test.expectedStoredBidResponses, storedBidResponses, "storedBidResponses doesn't match: %s\n", test.description) + assert.Equal(t, test.expectedBidderImpReplaceImpID, bidderImpReplaceImpId, "bidderImpReplaceImpId doesn't match: %s\n", test.description) assert.Nil(t, errorList, "Error should be nil") } } +func TestFlipMap(t *testing.T) { + testCases := []struct { + description string + inImpBidderReplaceImpID ImpBidderReplaceImpID + outBidderImpReplaceImpID BidderImpReplaceImpID + }{ + { + description: "Empty ImpBidderReplaceImpID", + inImpBidderReplaceImpID: ImpBidderReplaceImpID{}, + outBidderImpReplaceImpID: BidderImpReplaceImpID{}, + }, + { + description: "Nil ImpBidderReplaceImpID", + inImpBidderReplaceImpID: nil, + outBidderImpReplaceImpID: BidderImpReplaceImpID{}, + }, + { + description: "ImpBidderReplaceImpID has a one element map with single element", + inImpBidderReplaceImpID: ImpBidderReplaceImpID{"imp-id": {"bidderA": true}}, + outBidderImpReplaceImpID: BidderImpReplaceImpID{"bidderA": {"imp-id": true}}, + }, + { + description: "ImpBidderReplaceImpID has a one element map with multiple elements", + inImpBidderReplaceImpID: ImpBidderReplaceImpID{"imp-id": {"bidderA": true, "bidderB": false}}, + outBidderImpReplaceImpID: BidderImpReplaceImpID{"bidderA": {"imp-id": true}, "bidderB": {"imp-id": false}}, + }, + { + description: "ImpBidderReplaceImpID has multiple elements map with single element", + inImpBidderReplaceImpID: ImpBidderReplaceImpID{ + "imp-id1": {"bidderA": true}, + "imp-id2": {"bidderB": false}}, + outBidderImpReplaceImpID: BidderImpReplaceImpID{ + "bidderA": {"imp-id1": true}, + "bidderB": {"imp-id2": false}}, + }, + { + description: "ImpBidderReplaceImpID has multiple elements map with multiple elements", + inImpBidderReplaceImpID: ImpBidderReplaceImpID{ + "imp-id1": {"bidderA": true, "bidderB": false, "bidderC": false, "bidderD": true}, + "imp-id2": {"bidderA": false, "bidderB": false, "bidderC": true, "bidderD": true}, + "imp-id3": {"bidderA": false, "bidderB": true, "bidderC": true, "bidderD": false}}, + outBidderImpReplaceImpID: BidderImpReplaceImpID{ + "bidderA": {"imp-id1": true, "imp-id2": false, "imp-id3": false}, + "bidderB": {"imp-id1": false, "imp-id2": false, "imp-id3": true}, + "bidderC": {"imp-id1": false, "imp-id2": true, "imp-id3": true}, + "bidderD": {"imp-id1": true, "imp-id2": true, "imp-id3": false}}, + }, + } + + for _, test := range testCases { + actualResult := flipMap(test.inImpBidderReplaceImpID) + assert.Equal(t, test.outBidderImpReplaceImpID, actualResult, "Incorrect flipped map for test case %s\n", test.description) + } +} + type mockStoredBidResponseFetcher struct { data map[string]json.RawMessage } diff --git a/usersync/syncersbuilder.go b/usersync/syncersbuilder.go index 4c4c4207af6..c521d36dde2 100644 --- a/usersync/syncersbuilder.go +++ b/usersync/syncersbuilder.go @@ -81,7 +81,7 @@ func BuildSyncers(hostConfig *config.Configuration, bidderInfos config.BidderInf } func shouldCreateSyncer(cfg config.BidderInfo) bool { - if !cfg.Enabled { + if cfg.Disabled { return false } diff --git a/usersync/syncersbuilder_test.go b/usersync/syncersbuilder_test.go index 79727be6742..b3672a501bb 100644 --- a/usersync/syncersbuilder_test.go +++ b/usersync/syncersbuilder_test.go @@ -23,14 +23,14 @@ func TestBuildSyncers(t *testing.T) { hostConfig = config.Configuration{ExternalURL: "http://host.com", UserSync: config.UserSync{RedirectURL: "{{.ExternalURL}}/{{.SyncerKey}}/host"}} iframeConfig = &config.SyncerEndpoint{URL: "https://bidder.com/iframe?redirect={{.RedirectURL}}"} iframeConfigError = &config.SyncerEndpoint{URL: "https://bidder.com/iframe?redirect={{xRedirectURL}}"} // Error caused by invalid macro - 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", IFrame: iframeConfigError}} - 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}} + infoKeyAPopulated = config.BidderInfo{Disabled: false, Syncer: &config.Syncer{Key: "a", IFrame: iframeConfig}} + infoKeyADisabled = config.BidderInfo{Disabled: true, Syncer: &config.Syncer{Key: "a", IFrame: iframeConfig}} + infoKeyAEmpty = config.BidderInfo{Disabled: false, Syncer: &config.Syncer{Key: "a"}} + infoKeyAError = config.BidderInfo{Disabled: false, Syncer: &config.Syncer{Key: "a", IFrame: iframeConfigError}} + infoKeyASupportsOnly = config.BidderInfo{Disabled: false, Syncer: &config.Syncer{Supports: []string{"iframe"}}} + infoKeyBPopulated = config.BidderInfo{Disabled: false, Syncer: &config.Syncer{Key: "b", IFrame: iframeConfig}} + infoKeyBEmpty = config.BidderInfo{Disabled: false, Syncer: &config.Syncer{Key: "b"}} + infoKeyMissingPopulated = config.BidderInfo{Disabled: false, Syncer: &config.Syncer{IFrame: iframeConfig}} ) // NOTE: The hostConfig includes the syncer key in the RedirectURL to distinguish between the syncer keys @@ -190,82 +190,82 @@ func TestShouldCreateSyncer(t *testing.T) { }{ { description: "Enabled, No Syncer", - given: config.BidderInfo{Enabled: true, Syncer: nil}, + given: config.BidderInfo{Disabled: false, Syncer: nil}, expected: false, }, { description: "Enabled, Syncer", - given: config.BidderInfo{Enabled: true, Syncer: &config.Syncer{Key: "anyKey"}}, + given: config.BidderInfo{Disabled: false, Syncer: &config.Syncer{Key: "anyKey"}}, expected: true, }, { description: "Enabled, Syncer - Fully Loaded", - given: config.BidderInfo{Enabled: true, Syncer: &config.Syncer{Key: "anyKey", Supports: anySupports, IFrame: anyEndpoint, Redirect: anyEndpoint, SupportCORS: &anyCORS}}, + given: config.BidderInfo{Disabled: false, Syncer: &config.Syncer{Key: "anyKey", 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"}}, + given: config.BidderInfo{Disabled: false, Syncer: &config.Syncer{Key: "anyKey"}}, expected: true, }, { description: "Enabled, Syncer - Only Supports", - given: config.BidderInfo{Enabled: true, Syncer: &config.Syncer{Supports: anySupports}}, + given: config.BidderInfo{Disabled: false, Syncer: &config.Syncer{Supports: anySupports}}, expected: false, }, { description: "Enabled, Syncer - Only IFrame", - given: config.BidderInfo{Enabled: true, Syncer: &config.Syncer{IFrame: anyEndpoint}}, + given: config.BidderInfo{Disabled: false, Syncer: &config.Syncer{IFrame: anyEndpoint}}, expected: true, }, { description: "Enabled, Syncer - Only Redirect", - given: config.BidderInfo{Enabled: true, Syncer: &config.Syncer{Redirect: anyEndpoint}}, + given: config.BidderInfo{Disabled: false, Syncer: &config.Syncer{Redirect: anyEndpoint}}, expected: true, }, { description: "Enabled, Syncer - Only SupportCORS", - given: config.BidderInfo{Enabled: true, Syncer: &config.Syncer{SupportCORS: &anyCORS}}, + given: config.BidderInfo{Disabled: false, Syncer: &config.Syncer{SupportCORS: &anyCORS}}, expected: true, }, { description: "Disabled, No Syncer", - given: config.BidderInfo{Enabled: false, Syncer: nil}, + given: config.BidderInfo{Disabled: true, Syncer: nil}, expected: false, }, { description: "Disabled, Syncer", - given: config.BidderInfo{Enabled: false, Syncer: &config.Syncer{Key: "anyKey"}}, + given: config.BidderInfo{Disabled: true, Syncer: &config.Syncer{Key: "anyKey"}}, expected: false, }, { description: "Disabled, Syncer - Fully Loaded", - given: config.BidderInfo{Enabled: false, Syncer: &config.Syncer{Key: "anyKey", Supports: anySupports, IFrame: anyEndpoint, Redirect: anyEndpoint, SupportCORS: &anyCORS}}, + given: config.BidderInfo{Disabled: true, Syncer: &config.Syncer{Key: "anyKey", 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"}}, + given: config.BidderInfo{Disabled: true, Syncer: &config.Syncer{Key: "anyKey"}}, expected: false, }, { description: "Disabled, Syncer - Only Supports", - given: config.BidderInfo{Enabled: false, Syncer: &config.Syncer{Supports: anySupports}}, + given: config.BidderInfo{Disabled: true, Syncer: &config.Syncer{Supports: anySupports}}, expected: false, }, { description: "Disabled, Syncer - Only IFrame", - given: config.BidderInfo{Enabled: false, Syncer: &config.Syncer{IFrame: anyEndpoint}}, + given: config.BidderInfo{Disabled: true, Syncer: &config.Syncer{IFrame: anyEndpoint}}, expected: false, }, { description: "Disabled, Syncer - Only Redirect", - given: config.BidderInfo{Enabled: false, Syncer: &config.Syncer{Redirect: anyEndpoint}}, + given: config.BidderInfo{Disabled: true, Syncer: &config.Syncer{Redirect: anyEndpoint}}, expected: false, }, { description: "Disabled, Syncer - Only SupportCORS", - given: config.BidderInfo{Enabled: false, Syncer: &config.Syncer{SupportCORS: &anyCORS}}, + given: config.BidderInfo{Disabled: true, Syncer: &config.Syncer{SupportCORS: &anyCORS}}, expected: false, }, } diff --git a/util/jsonutil/jsonutil.go b/util/jsonutil/jsonutil.go index d8716297faf..3b468731cad 100644 --- a/util/jsonutil/jsonutil.go +++ b/util/jsonutil/jsonutil.go @@ -13,7 +13,7 @@ var openCurlyBracket = []byte("{")[0] var closingCurlyBracket = []byte("}")[0] var quote = []byte(`"`)[0] -//Finds element in json byte array with any level of nesting +// 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) @@ -96,7 +96,7 @@ func FindElement(extension []byte, elementNames ...string) (bool, int64, int64, return found, startIndex, endIndex, nil } -//Drops element from json byte array +// 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 diff --git a/util/task/func_runner.go b/util/task/func_runner.go new file mode 100644 index 00000000000..fe6de614d2d --- /dev/null +++ b/util/task/func_runner.go @@ -0,0 +1,15 @@ +package task + +import "time" + +type funcRunner struct { + run func() error +} + +func (r funcRunner) Run() error { + return r.run() +} + +func NewTickerTaskFromFunc(interval time.Duration, runner func() error) *TickerTask { + return NewTickerTask(interval, funcRunner{run: runner}) +} diff --git a/util/task/func_runner_test.go b/util/task/func_runner_test.go new file mode 100644 index 00000000000..86481e4ee5f --- /dev/null +++ b/util/task/func_runner_test.go @@ -0,0 +1,28 @@ +package task + +import ( + "sync" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestNewTickerTaskFromFunc(t *testing.T) { + var runCountMutex sync.Mutex + runCount := 0 + + funcTest := func() error { + runCountMutex.Lock() + defer runCountMutex.Unlock() + runCount++ + return nil + } + + anyDuration := 1 * time.Hour // not used for this test + task := NewTickerTaskFromFunc(anyDuration, funcTest) + + err := task.runner.Run() + assert.NoError(t, err) + assert.Equal(t, 1, runCount) +} diff --git a/version/version.go b/version/version.go index 4292abbe8d9..55705b2ad20 100644 --- a/version/version.go +++ b/version/version.go @@ -2,7 +2,9 @@ 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//`" +// +// 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 @@ -11,7 +13,9 @@ const VerUnknown = "unknown" // Rev holds binary revision string // Populated using: -// go build -ldflags "-X github.com/prebid/prebid-server/version.Rev=`git rev-parse --short HEAD`" +// +// 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 diff --git a/version/xprebidheader.go b/version/xprebidheader.go index 5d91ca49808..784c4af8c2d 100644 --- a/version/xprebidheader.go +++ b/version/xprebidheader.go @@ -3,7 +3,7 @@ package version import ( "strings" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" ) diff --git a/version/xprebidheader_test.go b/version/xprebidheader_test.go index 1529a8f7423..0c57f063dc4 100644 --- a/version/xprebidheader_test.go +++ b/version/xprebidheader_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/stretchr/testify/assert" "github.com/prebid/prebid-server/openrtb_ext" From dc955fb8c67f63e991be192c93a91174c3ad2624 Mon Sep 17 00:00:00 2001 From: tcreamer <tcreamer@playwire.com> Date: Thu, 5 Jan 2023 12:44:36 -0500 Subject: [PATCH 058/125] update heroku go version --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index fe4519d88e1..b3b8d747c2b 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/prebid/prebid-server go 1.16 // Magic comment that determines which Go version Heroku uses. -// +heroku goVersion go1.16 +// +heroku goVersion go1.19 replace github.com/prebid-server/adapters/playwire_ortb => ./adapters/playwire_ortb/ From 0f1dcd4df785384c9bf45989539e52d1f34034c7 Mon Sep 17 00:00:00 2001 From: tcreamer <tcreamer@playwire.com> Date: Thu, 5 Jan 2023 13:08:04 -0500 Subject: [PATCH 059/125] tests --- adapters/playwire/playwire_test.go | 6 +++--- adapters/playwire_ortb/playwire_ortb_test.go | 9 ++++++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/adapters/playwire/playwire_test.go b/adapters/playwire/playwire_test.go index e2f6f1990d6..c2fd63ec2c1 100644 --- a/adapters/playwire/playwire_test.go +++ b/adapters/playwire/playwire_test.go @@ -9,12 +9,12 @@ import ( ) func TestJsonSamples(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderPlaywire, config.Adapter{ - Endpoint: "http://localhost/prebid"}) + bidder, buildErr := Builder(openrtb_ext.BidderGumGum, config.Adapter{ + Endpoint: "https://g2.gumgum.com/providers/prbds2s/bid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) } - adapterstest.RunJSONBidderTest(t, "playwiretest", bidder) + adapterstest.RunJSONBidderTest(t, "gumgumtest", bidder) } diff --git a/adapters/playwire_ortb/playwire_ortb_test.go b/adapters/playwire_ortb/playwire_ortb_test.go index 6a45a92c22b..e68c31cb4a5 100644 --- a/adapters/playwire_ortb/playwire_ortb_test.go +++ b/adapters/playwire_ortb/playwire_ortb_test.go @@ -6,5 +6,12 @@ import ( ) func TestJsonSamples(t *testing.T) { - adapterstest.RunJSONBidderTest(t, "gumgumtest", NewOrtbBidder("https://g2.gumgum.com/providers/prbds2s/bid")) + bidder, buildErr := Builder(openrtb_ext.BidderGumGum, config.Adapter{ + Endpoint: "https://g2.gumgum.com/providers/prbds2s/bid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "gumgumtest", bidder) } From 71912e2b87707262a6eeff46f6b7ecc5ded4f043 Mon Sep 17 00:00:00 2001 From: tcreamer <tcreamer@playwire.com> Date: Thu, 5 Jan 2023 13:18:42 -0500 Subject: [PATCH 060/125] params_test for playwire adapter --- adapters/playwire/params_test.go | 53 ++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 adapters/playwire/params_test.go diff --git a/adapters/playwire/params_test.go b/adapters/playwire/params_test.go new file mode 100644 index 00000000000..bcc79589288 --- /dev/null +++ b/adapters/playwire/params_test.go @@ -0,0 +1,53 @@ +package playwire + +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.BidderGumGum, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected gumgum 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{ + `{"zone":"dc9d6be1"}`, +} + +var invalidParams = []string{ + `null`, + `nil`, + ``, + `{}`, + `[]`, + `true`, + `2`, + `{"zone":12345678}`, + `{"zone":""}`, + `{"placementId": 1}`, + `{"zone": true}`, + `{"placementId": 1, "zone":"1234567"}`, +} From 8432dc48de7a17365ba2ce5e4b3ca105af559a11 Mon Sep 17 00:00:00 2001 From: tcreamer <tcreamer@playwire.com> Date: Thu, 5 Jan 2023 13:50:35 -0500 Subject: [PATCH 061/125] updating playwire adapter to match grid.. --- adapters/playwire/playwire.go | 335 +++++++++++++++++++++++++++++++--- 1 file changed, 311 insertions(+), 24 deletions(-) diff --git a/adapters/playwire/playwire.go b/adapters/playwire/playwire.go index c14d6cb1e2d..d2335ec1d89 100644 --- a/adapters/playwire/playwire.go +++ b/adapters/playwire/playwire.go @@ -4,18 +4,49 @@ import ( "encoding/json" "fmt" "net/http" + "sort" + "strings" "github.com/prebid/openrtb/v17/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 PlaywireAdapter struct { endpoint string } +type PlaywireBid struct { + *openrtb2.Bid + AdmNative json.RawMessage `json:"adm_native,omitempty"` + ContentType openrtb_ext.BidType `json:"content_type"` +} + +type PlaywireSeatBid struct { + *openrtb2.SeatBid + Bid []PlaywireBid `json:"bid"` +} + +type PlaywireResponse struct { + *openrtb2.BidResponse + SeatBid []PlaywireSeatBid `json:"seatbid,omitempty"` +} + +type PlaywireBidExt struct { + Bidder ExtBidder `json:"bidder"` +} + +type ExtBidder struct { + Playwire ExtBidderPlaywire `json:"playwire"` +} + +type ExtBidderPlaywire struct { + DemandSource string `json:"demandSource"` +} + type ExtImpDataAdServer struct { Name string `json:"name"` AdSlot string `json:"adslot"` @@ -27,10 +58,193 @@ type ExtImpData struct { } type ExtImp struct { - Prebid *openrtb_ext.ExtImpPrebid `json:"prebid,omitempty"` - Bidder json.RawMessage `json:"bidder"` - Data *ExtImpData `json:"data,omitempty"` - Gpid string `json:"gpid,omitempty"` + Prebid *openrtb_ext.ExtImpPrebid `json:"prebid,omitempty"` + Bidder json.RawMessage `json:"bidder"` + Data *ExtImpData `json:"data,omitempty"` + Gpid string `json:"gpid,omitempty"` + Skadn json.RawMessage `json:"skadn,omitempty"` + Context json.RawMessage `json:"context,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 { @@ -69,16 +283,43 @@ func setImpExtData(imp openrtb2.Imp) openrtb2.Imp { return imp } +func fixNative(req json.RawMessage) (json.RawMessage, error) { + var playwireReq map[string]interface{} + var parsedRequest map[string]interface{} + + if err := json.Unmarshal(req, &playwireReq); err != nil { + return req, nil + } + if imps, exists := maputil.ReadEmbeddedSlice(playwireReq, "imp"); exists { + for _, imp := range imps { + if playwireImp, ok := imp.(map[string]interface{}); ok { + native, hasNative := maputil.ReadEmbeddedMap(playwireImp, "native") + if hasNative { + request, hasRequest := maputil.ReadEmbeddedString(native, "request") + if hasRequest { + delete(native, "request") + if err := json.Unmarshal([]byte(request), &parsedRequest); err == nil { + native["request_native"] = parsedRequest + } else { + native["request_native"] = request + } + } + } + } + } + } + + return json.Marshal(playwireReq) +} + // MakeRequests makes the HTTP requests which should be made to fetch bids. func (a *PlaywireAdapter) 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 +333,23 @@ func (a *PlaywireAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ad 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 + } + + fixedReqJSON, err := fixNative(reqJSON) + if err != nil { errors = append(errors, err) return nil, errors @@ -106,7 +361,7 @@ func (a *PlaywireAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ad return []*adapters.RequestData{{ Method: "POST", Uri: a.endpoint, - Body: reqJSON, + Body: fixedReqJSON, Headers: headers, }}, errors } @@ -129,7 +384,7 @@ func (a *PlaywireAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa }} } - var bidResp openrtb2.BidResponse + var bidResp PlaywireResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -138,14 +393,23 @@ func (a *PlaywireAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa for _, sb := range bidResp.SeatBid { for i := range sb.Bid { - bidType, err := getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp) + bidMeta, err := getBidMeta(sb.Bid[i].Ext) + bidType, err := getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp, sb.Bid[i]) + if sb.Bid[i].AdmNative != nil && sb.Bid[i].AdM == "" { + if bytes, err := json.Marshal(sb.Bid[i].AdmNative); err == nil { + sb.Bid[i].AdM = string(bytes) + } + } if err != nil { return nil, []error{err} } + openrtb2Bid := sb.Bid[i].Bid + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ - Bid: &sb.Bid[i], + Bid: openrtb2Bid, BidType: bidType, + BidMeta: bidMeta, }) } } @@ -161,19 +425,42 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server co return bidder, 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 - } +func getBidMeta(ext json.RawMessage) (*openrtb_ext.ExtBidPrebidMeta, error) { + var bidExt PlaywireBidExt - if imp.Video != nil { - return openrtb_ext.BidTypeVideo, nil - } + if err := json.Unmarshal(ext, &bidExt); err != nil { + return nil, err + } + var bidMeta *openrtb_ext.ExtBidPrebidMeta + if bidExt.Bidder.Playwire.DemandSource != "" { + bidMeta = &openrtb_ext.ExtBidPrebidMeta{ + NetworkName: bidExt.Bidder.Playwire.DemandSource, + } + } + return bidMeta, nil +} + +func getMediaTypeForImp(impID string, imps []openrtb2.Imp, bidWithType PlaywireBid) (openrtb_ext.BidType, error) { + if bidWithType.ContentType != "" { + return bidWithType.ContentType, nil + } else { + 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 + } + + if imp.Native != nil { + return openrtb_ext.BidTypeNative, nil + } - return "", &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unknown impression type for ID: \"%s\"", impID), + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unknown impression type for ID: \"%s\"", impID), + } } } } From 6bcb4b22fc87b2789675e5b25f48483ca48813f7 Mon Sep 17 00:00:00 2001 From: tcreamer <tcreamer@playwire.com> Date: Thu, 5 Jan 2023 14:05:32 -0500 Subject: [PATCH 062/125] removew playwire adapter and use grid --- adapters/playwire/params_test.go | 53 -- adapters/playwire/playwire.go | 472 ------------------ adapters/playwire/playwire_test.go | 20 - .../playwiretest/exemplary/simple-banner.json | 87 ---- .../playwiretest/exemplary/simple-video.json | 87 ---- .../playwiretest/exemplary/with_gpid.json | 100 ---- .../playwiretest/params/race/banner.json | 4 - .../supplemental/bad_bidder_request.json | 33 -- .../supplemental/bad_ext_request.json | 30 -- .../supplemental/bad_response.json | 63 --- .../supplemental/empty_uid_request.json | 33 -- .../supplemental/no_imp_request.json | 13 - .../playwiretest/supplemental/status_204.json | 58 --- .../playwiretest/supplemental/status_400.json | 63 --- .../playwiretest/supplemental/status_418.json | 63 --- exchange/adapter_builders.go | 3 +- 16 files changed, 1 insertion(+), 1181 deletions(-) delete mode 100644 adapters/playwire/params_test.go delete mode 100644 adapters/playwire/playwire.go delete mode 100644 adapters/playwire/playwire_test.go delete mode 100644 adapters/playwire/playwiretest/exemplary/simple-banner.json delete mode 100644 adapters/playwire/playwiretest/exemplary/simple-video.json delete mode 100644 adapters/playwire/playwiretest/exemplary/with_gpid.json delete mode 100644 adapters/playwire/playwiretest/params/race/banner.json delete mode 100644 adapters/playwire/playwiretest/supplemental/bad_bidder_request.json delete mode 100644 adapters/playwire/playwiretest/supplemental/bad_ext_request.json delete mode 100644 adapters/playwire/playwiretest/supplemental/bad_response.json delete mode 100644 adapters/playwire/playwiretest/supplemental/empty_uid_request.json delete mode 100644 adapters/playwire/playwiretest/supplemental/no_imp_request.json delete mode 100644 adapters/playwire/playwiretest/supplemental/status_204.json delete mode 100644 adapters/playwire/playwiretest/supplemental/status_400.json delete mode 100644 adapters/playwire/playwiretest/supplemental/status_418.json diff --git a/adapters/playwire/params_test.go b/adapters/playwire/params_test.go deleted file mode 100644 index bcc79589288..00000000000 --- a/adapters/playwire/params_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package playwire - -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.BidderGumGum, json.RawMessage(validParam)); err != nil { - t.Errorf("Schema rejected gumgum 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{ - `{"zone":"dc9d6be1"}`, -} - -var invalidParams = []string{ - `null`, - `nil`, - ``, - `{}`, - `[]`, - `true`, - `2`, - `{"zone":12345678}`, - `{"zone":""}`, - `{"placementId": 1}`, - `{"zone": true}`, - `{"placementId": 1, "zone":"1234567"}`, -} diff --git a/adapters/playwire/playwire.go b/adapters/playwire/playwire.go deleted file mode 100644 index d2335ec1d89..00000000000 --- a/adapters/playwire/playwire.go +++ /dev/null @@ -1,472 +0,0 @@ -package playwire - -import ( - "encoding/json" - "fmt" - "net/http" - "sort" - "strings" - - "github.com/prebid/openrtb/v17/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 PlaywireAdapter struct { - endpoint string -} - -type PlaywireBid struct { - *openrtb2.Bid - AdmNative json.RawMessage `json:"adm_native,omitempty"` - ContentType openrtb_ext.BidType `json:"content_type"` -} - -type PlaywireSeatBid struct { - *openrtb2.SeatBid - Bid []PlaywireBid `json:"bid"` -} - -type PlaywireResponse struct { - *openrtb2.BidResponse - SeatBid []PlaywireSeatBid `json:"seatbid,omitempty"` -} - -type PlaywireBidExt struct { - Bidder ExtBidder `json:"bidder"` -} - -type ExtBidder struct { - Playwire ExtBidderPlaywire `json:"playwire"` -} - -type ExtBidderPlaywire struct { - DemandSource string `json:"demandSource"` -} - -type ExtImpDataAdServer struct { - Name string `json:"name"` - AdSlot string `json:"adslot"` -} - -type ExtImpData struct { - PbAdslot string `json:"pbadslot,omitempty"` - AdServer *ExtImpDataAdServer `json:"adserver,omitempty"` -} - -type ExtImp struct { - Prebid *openrtb_ext.ExtImpPrebid `json:"prebid,omitempty"` - Bidder json.RawMessage `json:"bidder"` - Data *ExtImpData `json:"data,omitempty"` - Gpid string `json:"gpid,omitempty"` - Skadn json.RawMessage `json:"skadn,omitempty"` - Context json.RawMessage `json:"context,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 playwire extension - var ext adapters.ExtImpBidder - var playwireExt openrtb_ext.ExtImpPlaywire - if err := json.Unmarshal(imp.Ext, &ext); err != nil { - return err - } - if err := json.Unmarshal(ext.Bidder, &playwireExt); err != nil { - return err - } - - if playwireExt.Uid == 0 { - err := &errortypes.BadInput{ - Message: "uid is empty", - } - return err - } - // no error - return nil -} - -func setImpExtData(imp openrtb2.Imp) openrtb2.Imp { - var ext ExtImp - if err := json.Unmarshal(imp.Ext, &ext); err != nil { - return imp - } - if ext.Data != nil && ext.Data.AdServer != nil && ext.Data.AdServer.AdSlot != "" { - ext.Gpid = ext.Data.AdServer.AdSlot - extJSON, err := json.Marshal(ext) - if err == nil { - imp.Ext = extJSON - } - } - return imp -} - -func fixNative(req json.RawMessage) (json.RawMessage, error) { - var playwireReq map[string]interface{} - var parsedRequest map[string]interface{} - - if err := json.Unmarshal(req, &playwireReq); err != nil { - return req, nil - } - if imps, exists := maputil.ReadEmbeddedSlice(playwireReq, "imp"); exists { - for _, imp := range imps { - if playwireImp, ok := imp.(map[string]interface{}); ok { - native, hasNative := maputil.ReadEmbeddedMap(playwireImp, "native") - if hasNative { - request, hasRequest := maputil.ReadEmbeddedString(native, "request") - if hasRequest { - delete(native, "request") - if err := json.Unmarshal([]byte(request), &parsedRequest); err == nil { - native["request_native"] = parsedRequest - } else { - native["request_native"] = request - } - } - } - } - } - } - - return json.Marshal(playwireReq) -} - -// MakeRequests makes the HTTP requests which should be made to fetch bids. -func (a *PlaywireAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - var errors = make([]error, 0) - - // this will contain all the valid impressions - var validImps []openrtb2.Imp - // pre-process the imps - for _, imp := range request.Imp { - if err := processImp(&imp); err == nil { - validImps = append(validImps, setImpExtData(imp)) - } else { - errors = append(errors, err) - } - } - if len(validImps) == 0 { - err := &errortypes.BadInput{ - Message: "No valid impressions for playwire", - } - errors = append(errors, err) - return nil, errors - } - - 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 - } - - fixedReqJSON, err := fixNative(reqJSON) - - if err != nil { - errors = append(errors, err) - return nil, errors - } - - headers := http.Header{} - headers.Add("Content-Type", "application/json;charset=utf-8") - - return []*adapters.RequestData{{ - Method: "POST", - Uri: a.endpoint, - Body: fixedReqJSON, - Headers: headers, - }}, errors -} - -// MakeBids unpacks the server's response into Bids. -func (a *PlaywireAdapter) 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 bidResp PlaywireResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { - return nil, []error{err} - } - - bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) - - for _, sb := range bidResp.SeatBid { - for i := range sb.Bid { - bidMeta, err := getBidMeta(sb.Bid[i].Ext) - bidType, err := getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp, sb.Bid[i]) - if sb.Bid[i].AdmNative != nil && sb.Bid[i].AdM == "" { - if bytes, err := json.Marshal(sb.Bid[i].AdmNative); err == nil { - sb.Bid[i].AdM = string(bytes) - } - } - if err != nil { - return nil, []error{err} - } - - openrtb2Bid := sb.Bid[i].Bid - - bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ - Bid: openrtb2Bid, - BidType: bidType, - BidMeta: bidMeta, - }) - } - } - return bidResponse, nil - -} - -// Builder builds a new instance of the Playwire adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { - bidder := &PlaywireAdapter{ - endpoint: config.Endpoint, - } - return bidder, nil -} - -func getBidMeta(ext json.RawMessage) (*openrtb_ext.ExtBidPrebidMeta, error) { - var bidExt PlaywireBidExt - - if err := json.Unmarshal(ext, &bidExt); err != nil { - return nil, err - } - var bidMeta *openrtb_ext.ExtBidPrebidMeta - if bidExt.Bidder.Playwire.DemandSource != "" { - bidMeta = &openrtb_ext.ExtBidPrebidMeta{ - NetworkName: bidExt.Bidder.Playwire.DemandSource, - } - } - return bidMeta, nil -} - -func getMediaTypeForImp(impID string, imps []openrtb2.Imp, bidWithType PlaywireBid) (openrtb_ext.BidType, error) { - if bidWithType.ContentType != "" { - return bidWithType.ContentType, nil - } else { - 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 - } - - if imp.Native != nil { - return openrtb_ext.BidTypeNative, nil - } - - return "", &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unknown impression type for ID: \"%s\"", impID), - } - } - } - } - - // This shouldnt happen. Lets handle it just incase by returning an error. - return "", &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Failed to find impression for ID: \"%s\"", impID), - } -} diff --git a/adapters/playwire/playwire_test.go b/adapters/playwire/playwire_test.go deleted file mode 100644 index c2fd63ec2c1..00000000000 --- a/adapters/playwire/playwire_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package playwire - -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.BidderGumGum, config.Adapter{ - Endpoint: "https://g2.gumgum.com/providers/prbds2s/bid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) - - if buildErr != nil { - t.Fatalf("Builder returned unexpected error %v", buildErr) - } - - adapterstest.RunJSONBidderTest(t, "gumgumtest", bidder) -} diff --git a/adapters/playwire/playwiretest/exemplary/simple-banner.json b/adapters/playwire/playwiretest/exemplary/simple-banner.json deleted file mode 100644 index a1603fd4b6c..00000000000 --- a/adapters/playwire/playwiretest/exemplary/simple-banner.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "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 - } - } - }] - }, - - "httpCalls": [{ - "expectedRequest": { - "uri": "http://localhost/prebid", - "body": { - "id": "test-request-id", - "site": { - "page": "https://good.site/url" - }, - "imp": [{ - "id": "test-imp-id", - "banner": { - "format": [{ - "w": 300, - "h": 250 - }] - }, - "ext": { - "bidder": { - "uid": 1 - } - } - }] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [{ - "seat": "playwire", - "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/playwire/playwiretest/exemplary/simple-video.json b/adapters/playwire/playwiretest/exemplary/simple-video.json deleted file mode 100644 index fc15013a8c0..00000000000 --- a/adapters/playwire/playwiretest/exemplary/simple-video.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "site": { - "page": "https://good.site/url" - }, - "imp": [{ - "id": "test-imp-id", - "video": { - "mimes": ["video/mp4"], - "protocols": [2, 5], - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - "uid": 1 - } - } - }] - }, - - "httpCalls": [{ - "expectedRequest": { - "uri": "http://localhost/prebid", - "body": { - "id": "test-request-id", - "site": { - "page": "https://good.site/url" - }, - "imp": [{ - "id": "test-imp-id", - "video": { - "mimes": ["video/mp4"], - "protocols": [2, 5], - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - "uid": 1 - } - } - }] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [{ - "seat": "playwire", - "bid": [{ - "id": "randomid", - "impid": "test-imp-id", - "price": 0.500000, - "adid": "12345678", - "adm": "some-test-ad-vast", - "cid": "987", - "crid": "12345678", - "h": 250, - "w": 300 - }] - }], - "cur": "USD" - } - } - }], - - "expectedBidResponses": [{ - "currency": "USD", - "bids": [{ - "bid": { - "id": "randomid", - "impid": "test-imp-id", - "price": 0.5, - "adm": "some-test-ad-vast", - "adid": "12345678", - "cid": "987", - "crid": "12345678", - "w": 300, - "h": 250 - }, - "type": "video" - }] - }] -} diff --git a/adapters/playwire/playwiretest/exemplary/with_gpid.json b/adapters/playwire/playwiretest/exemplary/with_gpid.json deleted file mode 100644 index 386f827cf99..00000000000 --- a/adapters/playwire/playwiretest/exemplary/with_gpid.json +++ /dev/null @@ -1,100 +0,0 @@ -{ - "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 - }, - "data": { - "adserver": { - "name": "some_name", - "adslot": "some_slot" - } - } - } - }] - }, - - "httpCalls": [{ - "expectedRequest": { - "uri": "http://localhost/prebid", - "body": { - "id": "test-request-id", - "site": { - "page": "https://good.site/url" - }, - "imp": [{ - "id": "test-imp-id", - "banner": { - "format": [{ - "w": 300, - "h": 250 - }] - }, - "ext": { - "bidder": { - "uid": 1 - }, - "data": { - "adserver": { - "name": "some_name", - "adslot": "some_slot" - } - }, - "gpid": "some_slot" - } - }] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [{ - "seat": "playwire", - "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/playwire/playwiretest/params/race/banner.json b/adapters/playwire/playwiretest/params/race/banner.json deleted file mode 100644 index 7e347f11b45..00000000000 --- a/adapters/playwire/playwiretest/params/race/banner.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "uid": 1 -} - diff --git a/adapters/playwire/playwiretest/supplemental/bad_bidder_request.json b/adapters/playwire/playwiretest/supplemental/bad_bidder_request.json deleted file mode 100644 index cb9c9333070..00000000000 --- a/adapters/playwire/playwiretest/supplemental/bad_bidder_request.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": "some not exist" - } - } - ] - }, - "expectedMakeRequestsErrors": [ - { - "value": "json: cannot unmarshal string into Go value of type openrtb_ext.ExtImpPlaywire", - "comparison": "literal" - }, - { - "value": "No valid impressions for playwire", - "comparison": "literal" - } - ], - "httpCalls":[], - "expectedBidResponses": [] -} diff --git a/adapters/playwire/playwiretest/supplemental/bad_ext_request.json b/adapters/playwire/playwiretest/supplemental/bad_ext_request.json deleted file mode 100644 index 76cce111948..00000000000 --- a/adapters/playwire/playwiretest/supplemental/bad_ext_request.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": "any" - } - ] - }, - "expectedMakeRequestsErrors": [ - { - "value": "json: cannot unmarshal string into Go value of type adapters.ExtImpBidder", - "comparison": "literal" - }, - { - "value": "No valid impressions for playwire", - "comparison": "literal" - } - ], - "httpCalls": [] -} diff --git a/adapters/playwire/playwiretest/supplemental/bad_response.json b/adapters/playwire/playwiretest/supplemental/bad_response.json deleted file mode 100644 index a9d38368ab2..00000000000 --- a/adapters/playwire/playwiretest/supplemental/bad_response.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "uid": 1 - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://localhost/prebid", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "uid": 1 - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": "{\"id\"data.lost" - } - } - ], - - "expectedMakeBidsErrors": [ - { - "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", - "comparison": "literal" - } - ] -} diff --git a/adapters/playwire/playwiretest/supplemental/empty_uid_request.json b/adapters/playwire/playwiretest/supplemental/empty_uid_request.json deleted file mode 100644 index e6b242942e9..00000000000 --- a/adapters/playwire/playwiretest/supplemental/empty_uid_request.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": {} - } - } - ] - }, - "expectedMakeRequestsErrors": [ - { - "value": "uid is empty", - "comparison": "literal" - }, - { - "value": "No valid impressions for playwire", - "comparison": "literal" - } - ], - "httpCalls":[], - "expectedBidResponses": [] -} diff --git a/adapters/playwire/playwiretest/supplemental/no_imp_request.json b/adapters/playwire/playwiretest/supplemental/no_imp_request.json deleted file mode 100644 index c12686f7328..00000000000 --- a/adapters/playwire/playwiretest/supplemental/no_imp_request.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [] - }, - "expectedMakeRequestsErrors": [ - { - "value": "No valid impressions for playwire", - "comparison": "literal" - } - ], - "httpCalls":[] -} diff --git a/adapters/playwire/playwiretest/supplemental/status_204.json b/adapters/playwire/playwiretest/supplemental/status_204.json deleted file mode 100644 index f935cbe85ae..00000000000 --- a/adapters/playwire/playwiretest/supplemental/status_204.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "uid": 1 - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://localhost/prebid", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "uid": 1 - } - } - } - ] - } - }, - "mockResponse": { - "status": 204, - "body": {} - } - } - ], - - "expectedBidResponses": [] -} diff --git a/adapters/playwire/playwiretest/supplemental/status_400.json b/adapters/playwire/playwiretest/supplemental/status_400.json deleted file mode 100644 index 629b1c07bd7..00000000000 --- a/adapters/playwire/playwiretest/supplemental/status_400.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "uid": 1 - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://localhost/prebid", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "uid": 1 - } - } - } - ] - } - }, - "mockResponse": { - "status": 400, - "body": {} - } - } - ], - - "expectedMakeBidsErrors": [ - { - "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", - "comparison": "literal" - } - ] -} diff --git a/adapters/playwire/playwiretest/supplemental/status_418.json b/adapters/playwire/playwiretest/supplemental/status_418.json deleted file mode 100644 index 0ca365c76ce..00000000000 --- a/adapters/playwire/playwiretest/supplemental/status_418.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "uid": 1 - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://localhost/prebid", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "uid": 1 - } - } - } - ] - } - }, - "mockResponse": { - "status": 418, - "body": {} - } - } - ], - - "expectedMakeBidsErrors": [ - { - "value": "Unexpected status code: 418. Run with request.debug = 1 for more info", - "comparison": "literal" - } - ] -} diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 3669e0cac7c..1a53dd9e438 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -112,7 +112,6 @@ import ( "github.com/prebid/prebid-server/adapters/orbidder" "github.com/prebid/prebid-server/adapters/outbrain" "github.com/prebid/prebid-server/adapters/pangle" - "github.com/prebid/prebid-server/adapters/playwire" "github.com/prebid/prebid-server/adapters/pubmatic" "github.com/prebid/prebid-server/adapters/pubnative" "github.com/prebid/prebid-server/adapters/pulsepoint" @@ -282,7 +281,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderOutbrain: outbrain.Builder, openrtb_ext.BidderPangle: pangle.Builder, openrtb_ext.BidderPGAM: adtelligent.Builder, - openrtb_ext.BidderPlaywire: playwire.Builder, + openrtb_ext.BidderPlaywire: grid.Builder, openrtb_ext.BidderPlaywireOrtb: playwire_ortb.Builder, openrtb_ext.BidderPubmatic: pubmatic.Builder, openrtb_ext.BidderPubnative: pubnative.Builder, From 3f8b8196c69ef1699d6b95577782944ddea0afc7 Mon Sep 17 00:00:00 2001 From: tcreamer <tcreamer@playwire.com> Date: Thu, 5 Jan 2023 15:04:22 -0500 Subject: [PATCH 063/125] aliasing playwire as grid --- static/bidder-info/playwire.yaml | 6 ++++++ static/bidder-params/playwire.json | 14 +++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/static/bidder-info/playwire.yaml b/static/bidder-info/playwire.yaml index 8c25833a376..2ea70768a56 100644 --- a/static/bidder-info/playwire.yaml +++ b/static/bidder-info/playwire.yaml @@ -6,7 +6,13 @@ capabilities: mediaTypes: - banner - video + - native site: mediaTypes: - banner - video + - native +userSync: + redirect: + url: "https://x.bidswitch.net/check_uuid/{{.RedirectURL}}?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}" + userMacro: "${BSW_UUID}" \ No newline at end of file diff --git a/static/bidder-params/playwire.json b/static/bidder-params/playwire.json index dd4b9be09a7..83fcbe0adbf 100644 --- a/static/bidder-params/playwire.json +++ b/static/bidder-params/playwire.json @@ -7,7 +7,19 @@ "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": [] -} +} \ No newline at end of file From 137e35d09c07c5de5dc93aae99749a2ce0a70473 Mon Sep 17 00:00:00 2001 From: Yelyzaveta Tarabarova <ltarabarova@playwire.com> Date: Thu, 9 Mar 2023 01:19:43 +0200 Subject: [PATCH 064/125] add deployment to ecs action --- .github/workflows/aws/task-definition.json | 142 +++++++++++++++++++++ .github/workflows/dev-deploy.yml | 60 +++++++++ 2 files changed, 202 insertions(+) create mode 100644 .github/workflows/aws/task-definition.json create mode 100644 .github/workflows/dev-deploy.yml diff --git a/.github/workflows/aws/task-definition.json b/.github/workflows/aws/task-definition.json new file mode 100644 index 00000000000..3b5580bdea7 --- /dev/null +++ b/.github/workflows/aws/task-definition.json @@ -0,0 +1,142 @@ +{ + "taskDefinitionArn": "arn:aws:ecs:us-east-1:885182695675:task-definition/prebid-server:1", + "containerDefinitions": [ + { + "name": "prebid-server", + "image": "885182695675.dkr.ecr.us-east-1.amazonaws.com/prebid-server:latest", + "cpu": 0, + "portMappings": [ + { + "containerPort": 80, + "hostPort": 80, + "protocol": "tcp" + }, + { + "containerPort": 8000, + "hostPort": 8000, + "protocol": "tcp" + }, + { + "containerPort": 6060, + "hostPort": 6060, + "protocol": "tcp" + } + ], + "essential": true, + "environment": [ + { + "name": "PBS_STORED_REQUESTS_IN_MEMORY_CACHE_TYPE", + "value": "lru" + }, + { + "name": "PBS_CACHE_SCHEME", + "value": "https" + }, + { + "name": "PBS_STORED_REQUESTS_FILESYSTEM_ENABLED", + "value": "true" + }, + { + "name": "PBS_ADAPTERS_RUBICON_XAPI_PASSWORD", + "value": "KNXDFG67MN" + }, + { + "name": "PBS_GDPR_DEFAULT_VALUE", + "value": "0" + }, + { + "name": "PBS_ADAPTERS_RUBICON_DISABLED", + "value": "false" + }, + { + "name": "PBS_CACHE_HOST", + "value": "prebid.adnxs.com/pbc/v1/" + }, + { + "name": "PBS_STORED_REQUESTS_IN_MEMORY_CACHE_REQUEST_CACHE_SIZE_BYTES", + "value": "107374182" + }, + { + "name": "PBS_STORED_REQUESTS_HTTP_ENDPOINT", + "value": "https://config.playwire.com/prebid/" + }, + { + "name": "PBS_ADAPTERS_RUBICON_XAPI_USERNAME", + "value": "pb_playwire" + }, + { + "name": "PBS_ADAPTERS_RUBICON_USERSYNC_URL", + "value": "https://secure-assets.rubiconproject.com/utils/xapi/multi-sync.html?p=pbs-Playwire&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}" + }, + { + "name": "PBS_STORED_REQUESTS_IN_MEMORY_CACHE_IMP_CACHE_SIZE_BYTES", + "value": "107374182" + }, + { + "name": "PBS_STORED_REQUESTS_IN_MEMORY_CACHE_RESP_CACHE_SIZE_BYTES", + "value": "107374182" + } + ], + "environmentFiles": [], + "mountPoints": [], + "volumesFrom": [], + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-create-group": "true", + "awslogs-group": "/ecs/prebid-server", + "awslogs-region": "us-east-1", + "awslogs-stream-prefix": "ecs" + } + } + } + ], + "family": "prebid-server", + "executionRoleArn": "arn:aws:iam::885182695675:role/ecsTaskExecutionRole", + "networkMode": "awsvpc", + "revision": 1, + "volumes": [], + "status": "ACTIVE", + "requiresAttributes": [ + { + "name": "com.amazonaws.ecs.capability.logging-driver.awslogs" + }, + { + "name": "ecs.capability.execution-role-awslogs" + }, + { + "name": "com.amazonaws.ecs.capability.ecr-auth" + }, + { + "name": "com.amazonaws.ecs.capability.docker-remote-api.1.19" + }, + { + "name": "ecs.capability.execution-role-ecr-pull" + }, + { + "name": "com.amazonaws.ecs.capability.docker-remote-api.1.18" + }, + { + "name": "ecs.capability.task-eni" + }, + { + "name": "com.amazonaws.ecs.capability.docker-remote-api.1.29" + } + ], + "placementConstraints": [], + "compatibilities": [ + "EC2", + "FARGATE" + ], + "runtimePlatform": { + "cpuArchitecture": "X86_64", + "operatingSystemFamily": "LINUX" + }, + "requiresCompatibilities": [ + "FARGATE" + ], + "cpu": "512", + "memory": "1024", + "registeredAt": "2023-02-28T12:22:24.314000+02:00", + "registeredBy": "arn:aws:iam::885182695675:user/ltarabarova" +} diff --git a/.github/workflows/dev-deploy.yml b/.github/workflows/dev-deploy.yml new file mode 100644 index 00000000000..1811b75e3b6 --- /dev/null +++ b/.github/workflows/dev-deploy.yml @@ -0,0 +1,60 @@ +name: DEV Deployment to ECS + +on: + push: + branches: ["ci-cd"] + +env: + AWS_REGION: "us-east-1" + AWS_ROLE: arn:aws:iam::885182695675:role/PrebidDeploymentDevRole + REPO_NAME: prebid-server + +jobs: + deploy: + name: Deploy + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1-node16 + with: + role-to-assume: ${{ env.AWS_ROLE }} + aws-region: ${{ env.AWS_REGION }} + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push the image to Amazon ECR + id: build-image + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: ${{ env.REPO_NAME }} + IMAGE_TAG: ${{ github.sha }} + run: | + # Build a docker container and push it to ECR + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . + echo "Pushing image to ECR..." + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" + - name: Fill in the new image ID in the Amazon ECS task definition + id: task-def + uses: aws-actions/amazon-ecs-render-task-definition@v1 + with: + task-definition: ./.github/workflows/task-definition.json + container-name: prebid-server + image: ${{ steps.build-image.outputs.image }} + + - name: Deploy Amazon ECS task definition + uses: aws-actions/amazon-ecs-deploy-task-definition@v1 + with: + task-definition: ${{ steps.task-def.outputs.task-definition }} + service: prebid-server-dev + cluster: prebid-server-dev-cluster + wait-for-service-stability: true \ No newline at end of file From 051c50bfd2f474b9a37cb8b81d9778b39015aeff Mon Sep 17 00:00:00 2001 From: tcreamer <tcreamer@playwire.com> Date: Thu, 30 Mar 2023 17:33:59 -0400 Subject: [PATCH 065/125] encode url quotes, gitignore, larger timeout --- .gitignore | 2 ++ stored_requests/backends/http_fetcher/fetcher.go | 6 +++--- stored_requests/data/by_id/stored_requests/playwire.json | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 514e217b46c..ccd3f1a0aa1 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,5 @@ analytics/filesystem/testFiles/ # autogenerated mac file .DS_Store + +.env* \ No newline at end of file diff --git a/stored_requests/backends/http_fetcher/fetcher.go b/stored_requests/backends/http_fetcher/fetcher.go index 60d3bcfabd3..896b09d02c1 100644 --- a/stored_requests/backends/http_fetcher/fetcher.go +++ b/stored_requests/backends/http_fetcher/fetcher.go @@ -218,11 +218,11 @@ func (fetcher *HttpFetcher) FetchCategories(ctx context.Context, primaryAdServer func buildRequest(endpoint string, requestIDs []string, impIDs []string) (*http.Request, error) { if len(requestIDs) > 0 && len(impIDs) > 0 { - return http.NewRequest("GET", endpoint+"request-ids=[\""+strings.Join(requestIDs, "\",\"")+"\"]&imp-ids=[\""+strings.Join(impIDs, "\",\"")+"\"]", nil) + return http.NewRequest("GET", endpoint+"request-ids=[%22"+strings.Join(requestIDs, "%22,%22")+"%22]&imp-ids=[%22"+strings.Join(impIDs, "%22,%22")+"%22]", nil) } else if len(requestIDs) > 0 { - return http.NewRequest("GET", endpoint+"request-ids=[\""+strings.Join(requestIDs, "\",\"")+"\"]", nil) + return http.NewRequest("GET", endpoint+"request-ids=[%22"+strings.Join(requestIDs, "%22,%22")+"%22]", nil) } else { - return http.NewRequest("GET", endpoint+"imp-ids=[\""+strings.Join(impIDs, "\",\"")+"\"]", nil) + return http.NewRequest("GET", endpoint+"imp-ids=[%22"+strings.Join(impIDs, "%22,%22")+"%22]", nil) } } diff --git a/stored_requests/data/by_id/stored_requests/playwire.json b/stored_requests/data/by_id/stored_requests/playwire.json index 9c5eb1aadf7..72daa47564b 100644 --- a/stored_requests/data/by_id/stored_requests/playwire.json +++ b/stored_requests/data/by_id/stored_requests/playwire.json @@ -1,5 +1,5 @@ { - "tmax": 1000, + "tmax": 2500, "cur": [ "USD" ], From 87d48fd36075a861593f78ca541cc0b44fdcb1ac Mon Sep 17 00:00:00 2001 From: tcreamer <tcreamer@playwire.com> Date: Tue, 4 Apr 2023 09:10:24 -0400 Subject: [PATCH 066/125] timeout 2s --- stored_requests/data/by_id/stored_requests/playwire.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stored_requests/data/by_id/stored_requests/playwire.json b/stored_requests/data/by_id/stored_requests/playwire.json index 72daa47564b..655f56d2656 100644 --- a/stored_requests/data/by_id/stored_requests/playwire.json +++ b/stored_requests/data/by_id/stored_requests/playwire.json @@ -1,5 +1,5 @@ { - "tmax": 2500, + "tmax": 2000, "cur": [ "USD" ], From bfe07a1cf13a801a8d7efc502f37eb039bf50899 Mon Sep 17 00:00:00 2001 From: tcreamer <tcreamer@playwire.com> Date: Tue, 4 Apr 2023 09:12:08 -0400 Subject: [PATCH 067/125] remove empty line --- stored_requests/data/by_id/stored_requests/playwire.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stored_requests/data/by_id/stored_requests/playwire.json b/stored_requests/data/by_id/stored_requests/playwire.json index 655f56d2656..9af1c5c6531 100644 --- a/stored_requests/data/by_id/stored_requests/playwire.json +++ b/stored_requests/data/by_id/stored_requests/playwire.json @@ -27,4 +27,4 @@ "aliases": { "oftmedia": "appnexus" } } } -} +} \ No newline at end of file From 276c0d39ace4442203eb7ff80902bd6fde91cf13 Mon Sep 17 00:00:00 2001 From: tcreamer <tcreamer@playwire.com> Date: Tue, 4 Apr 2023 09:12:27 -0400 Subject: [PATCH 068/125] format JSOn --- stored_requests/data/by_id/stored_requests/playwire.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/stored_requests/data/by_id/stored_requests/playwire.json b/stored_requests/data/by_id/stored_requests/playwire.json index 9af1c5c6531..03f5a4ed79c 100644 --- a/stored_requests/data/by_id/stored_requests/playwire.json +++ b/stored_requests/data/by_id/stored_requests/playwire.json @@ -1,8 +1,6 @@ { "tmax": 2000, - "cur": [ - "USD" - ], + "cur": ["USD"], "ext": { "prebid": { "targeting": { @@ -27,4 +25,4 @@ "aliases": { "oftmedia": "appnexus" } } } -} \ No newline at end of file +} From 051079f375832b4eb1b70140f1e89821fa2849b5 Mon Sep 17 00:00:00 2001 From: Yelyzaveta Tarabarova <ltarabarova@playwire.com> Date: Thu, 13 Apr 2023 15:07:14 +0300 Subject: [PATCH 069/125] remove task definition from repository --- .github/workflows/aws/task-definition.json | 142 --------------------- .github/workflows/dev-deploy.yml | 8 +- 2 files changed, 6 insertions(+), 144 deletions(-) delete mode 100644 .github/workflows/aws/task-definition.json diff --git a/.github/workflows/aws/task-definition.json b/.github/workflows/aws/task-definition.json deleted file mode 100644 index 3b5580bdea7..00000000000 --- a/.github/workflows/aws/task-definition.json +++ /dev/null @@ -1,142 +0,0 @@ -{ - "taskDefinitionArn": "arn:aws:ecs:us-east-1:885182695675:task-definition/prebid-server:1", - "containerDefinitions": [ - { - "name": "prebid-server", - "image": "885182695675.dkr.ecr.us-east-1.amazonaws.com/prebid-server:latest", - "cpu": 0, - "portMappings": [ - { - "containerPort": 80, - "hostPort": 80, - "protocol": "tcp" - }, - { - "containerPort": 8000, - "hostPort": 8000, - "protocol": "tcp" - }, - { - "containerPort": 6060, - "hostPort": 6060, - "protocol": "tcp" - } - ], - "essential": true, - "environment": [ - { - "name": "PBS_STORED_REQUESTS_IN_MEMORY_CACHE_TYPE", - "value": "lru" - }, - { - "name": "PBS_CACHE_SCHEME", - "value": "https" - }, - { - "name": "PBS_STORED_REQUESTS_FILESYSTEM_ENABLED", - "value": "true" - }, - { - "name": "PBS_ADAPTERS_RUBICON_XAPI_PASSWORD", - "value": "KNXDFG67MN" - }, - { - "name": "PBS_GDPR_DEFAULT_VALUE", - "value": "0" - }, - { - "name": "PBS_ADAPTERS_RUBICON_DISABLED", - "value": "false" - }, - { - "name": "PBS_CACHE_HOST", - "value": "prebid.adnxs.com/pbc/v1/" - }, - { - "name": "PBS_STORED_REQUESTS_IN_MEMORY_CACHE_REQUEST_CACHE_SIZE_BYTES", - "value": "107374182" - }, - { - "name": "PBS_STORED_REQUESTS_HTTP_ENDPOINT", - "value": "https://config.playwire.com/prebid/" - }, - { - "name": "PBS_ADAPTERS_RUBICON_XAPI_USERNAME", - "value": "pb_playwire" - }, - { - "name": "PBS_ADAPTERS_RUBICON_USERSYNC_URL", - "value": "https://secure-assets.rubiconproject.com/utils/xapi/multi-sync.html?p=pbs-Playwire&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}" - }, - { - "name": "PBS_STORED_REQUESTS_IN_MEMORY_CACHE_IMP_CACHE_SIZE_BYTES", - "value": "107374182" - }, - { - "name": "PBS_STORED_REQUESTS_IN_MEMORY_CACHE_RESP_CACHE_SIZE_BYTES", - "value": "107374182" - } - ], - "environmentFiles": [], - "mountPoints": [], - "volumesFrom": [], - "logConfiguration": { - "logDriver": "awslogs", - "options": { - "awslogs-create-group": "true", - "awslogs-group": "/ecs/prebid-server", - "awslogs-region": "us-east-1", - "awslogs-stream-prefix": "ecs" - } - } - } - ], - "family": "prebid-server", - "executionRoleArn": "arn:aws:iam::885182695675:role/ecsTaskExecutionRole", - "networkMode": "awsvpc", - "revision": 1, - "volumes": [], - "status": "ACTIVE", - "requiresAttributes": [ - { - "name": "com.amazonaws.ecs.capability.logging-driver.awslogs" - }, - { - "name": "ecs.capability.execution-role-awslogs" - }, - { - "name": "com.amazonaws.ecs.capability.ecr-auth" - }, - { - "name": "com.amazonaws.ecs.capability.docker-remote-api.1.19" - }, - { - "name": "ecs.capability.execution-role-ecr-pull" - }, - { - "name": "com.amazonaws.ecs.capability.docker-remote-api.1.18" - }, - { - "name": "ecs.capability.task-eni" - }, - { - "name": "com.amazonaws.ecs.capability.docker-remote-api.1.29" - } - ], - "placementConstraints": [], - "compatibilities": [ - "EC2", - "FARGATE" - ], - "runtimePlatform": { - "cpuArchitecture": "X86_64", - "operatingSystemFamily": "LINUX" - }, - "requiresCompatibilities": [ - "FARGATE" - ], - "cpu": "512", - "memory": "1024", - "registeredAt": "2023-02-28T12:22:24.314000+02:00", - "registeredBy": "arn:aws:iam::885182695675:user/ltarabarova" -} diff --git a/.github/workflows/dev-deploy.yml b/.github/workflows/dev-deploy.yml index 1811b75e3b6..059d7ed9c54 100644 --- a/.github/workflows/dev-deploy.yml +++ b/.github/workflows/dev-deploy.yml @@ -43,11 +43,15 @@ jobs: echo "Pushing image to ECR..." docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" + - name: Download task definition + run: | + aws ecs describe-task-definition --task-definition my-task-definition-family --query taskDefinition > task-definition.json + - name: Fill in the new image ID in the Amazon ECS task definition id: task-def uses: aws-actions/amazon-ecs-render-task-definition@v1 with: - task-definition: ./.github/workflows/task-definition.json + task-definition: task-definition.json container-name: prebid-server image: ${{ steps.build-image.outputs.image }} @@ -55,6 +59,6 @@ jobs: uses: aws-actions/amazon-ecs-deploy-task-definition@v1 with: task-definition: ${{ steps.task-def.outputs.task-definition }} - service: prebid-server-dev + service: prebid-dev-service cluster: prebid-server-dev-cluster wait-for-service-stability: true \ No newline at end of file From 442b49ea76916c25b2089afec973f566f35f45b0 Mon Sep 17 00:00:00 2001 From: Yelyzaveta Tarabarova <ltarabarova@playwire.com> Date: Mon, 24 Apr 2023 21:20:21 +0300 Subject: [PATCH 070/125] fix permissions --- .github/workflows/dev-deploy.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dev-deploy.yml b/.github/workflows/dev-deploy.yml index 059d7ed9c54..1f1e1455627 100644 --- a/.github/workflows/dev-deploy.yml +++ b/.github/workflows/dev-deploy.yml @@ -3,7 +3,8 @@ name: DEV Deployment to ECS on: push: branches: ["ci-cd"] - +# on: +# workflow_dispatch env: AWS_REGION: "us-east-1" AWS_ROLE: arn:aws:iam::885182695675:role/PrebidDeploymentDevRole From 7ced59381f1656c95290c3f3dab300c3966ee4e6 Mon Sep 17 00:00:00 2001 From: Yelyzaveta Tarabarova <ltarabarova@playwire.com> Date: Mon, 24 Apr 2023 21:43:01 +0300 Subject: [PATCH 071/125] fix task definition name --- .github/workflows/dev-deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dev-deploy.yml b/.github/workflows/dev-deploy.yml index 1f1e1455627..01b64ee151f 100644 --- a/.github/workflows/dev-deploy.yml +++ b/.github/workflows/dev-deploy.yml @@ -46,7 +46,7 @@ jobs: echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" - name: Download task definition run: | - aws ecs describe-task-definition --task-definition my-task-definition-family --query taskDefinition > task-definition.json + aws ecs describe-task-definition --task-definition prebid-server --query taskDefinition > task-definition.json - name: Fill in the new image ID in the Amazon ECS task definition id: task-def From 4f73a78c5843071c02e6b39e517ce27f2572c720 Mon Sep 17 00:00:00 2001 From: Yelyzaveta Tarabarova <ltarabarova@playwire.com> Date: Mon, 24 Apr 2023 21:57:03 +0300 Subject: [PATCH 072/125] fix permissions --- .github/workflows/dev-deploy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/dev-deploy.yml b/.github/workflows/dev-deploy.yml index 01b64ee151f..32e45d4e4f8 100644 --- a/.github/workflows/dev-deploy.yml +++ b/.github/workflows/dev-deploy.yml @@ -5,6 +5,7 @@ on: branches: ["ci-cd"] # on: # workflow_dispatch + env: AWS_REGION: "us-east-1" AWS_ROLE: arn:aws:iam::885182695675:role/PrebidDeploymentDevRole From 4868b3c67f20448d00c2687a94e7c80ecbc73d1d Mon Sep 17 00:00:00 2001 From: Yelyzaveta Tarabarova <ltarabarova@playwire.com> Date: Mon, 24 Apr 2023 22:10:03 +0300 Subject: [PATCH 073/125] add manual trigger --- .github/workflows/dev-deploy.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/dev-deploy.yml b/.github/workflows/dev-deploy.yml index 32e45d4e4f8..4a4b958c7ab 100644 --- a/.github/workflows/dev-deploy.yml +++ b/.github/workflows/dev-deploy.yml @@ -1,10 +1,7 @@ name: DEV Deployment to ECS on: - push: - branches: ["ci-cd"] -# on: -# workflow_dispatch + workflow_dispatch env: AWS_REGION: "us-east-1" From 85aeadbf0e3a5616f3e5e3d9af698f1707615b3b Mon Sep 17 00:00:00 2001 From: Derek Bruneau <dbruneau@playwire.com> Date: Tue, 4 Feb 2020 15:13:03 -0500 Subject: [PATCH 074/125] Merge pull request #1 from intergi/chore/heroku-setup Adding Procfile for Heroku and updating Go package (cherry picked from commit 4e5fc024033cb40b756013fd2348f00ea7904cc6) --- Procfile | 1 + go.mod | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 Procfile diff --git a/Procfile b/Procfile new file mode 100644 index 00000000000..7a7dfbb28e9 --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: PBS_PORT=$PORT bin/prebid-server diff --git a/go.mod b/go.mod index 90f8750721e..aaa3a311b8f 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/prebid/prebid-server +module github.com/intergi/prebid-server go 1.19 From dbfe4326fe403a1149bf37c5433cf8b98413830c Mon Sep 17 00:00:00 2001 From: Derek Bruneau <dbruneau@playwire.com> Date: Mon, 13 Apr 2020 15:27:02 -0400 Subject: [PATCH 075/125] Merge pull request #2 from intergi/fix/module-name Reverting module name to prevent Go from downloading local packages from GitHub (cherry picked from commit 1451e261922c9d4c8b00be0d0e6e96abd7e95b99) --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index aaa3a311b8f..90f8750721e 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/intergi/prebid-server +module github.com/prebid/prebid-server go 1.19 From bc8c1873ee14e8b1d4059ac8e3f1196e5af23ea7 Mon Sep 17 00:00:00 2001 From: Derek Bruneau <dbruneau@playwire.com> Date: Tue, 14 Apr 2020 10:01:38 -0400 Subject: [PATCH 076/125] Merge pull request #3 from intergi/chore/lock-go-version Adding magic comment so that Heroku uses Go 1.12 (cherry picked from commit 005a7938d5a8c3aa895742cc215e6263685e5189) --- go.mod | 3 +++ 1 file changed, 3 insertions(+) diff --git a/go.mod b/go.mod index 90f8750721e..dc10121c71d 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,9 @@ module github.com/prebid/prebid-server go 1.19 +// Magic comment that determines which Go version Heroku uses. +// +heroku goVersion go1.12 + require ( github.com/DATA-DOG/go-sqlmock v1.5.0 github.com/IABTechLab/adscert v0.34.0 From 7dff6273b9a9149318bffc75150a2a114f63cd85 Mon Sep 17 00:00:00 2001 From: Derek Bruneau <dbruneau@playwire.com> Date: Tue, 28 Apr 2020 11:22:01 -0400 Subject: [PATCH 077/125] Merge pull request #4 from intergi/chore/more-logging Configuring instance to log more info on Heroku (cherry picked from commit 4900ac3cee16b20bc090b542ed3c4955279c6b05) --- Procfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Procfile b/Procfile index 7a7dfbb28e9..1944727d5e8 100644 --- a/Procfile +++ b/Procfile @@ -1 +1 @@ -web: PBS_PORT=$PORT bin/prebid-server +web: PBS_PORT=$PORT bin/prebid-server -stderrthreshold=INFO From 9e539ebaea80b1c2b94efabca835c320d6e6d82d Mon Sep 17 00:00:00 2001 From: Derek Bruneau <dbruneau@playwire.com> Date: Wed, 26 Aug 2020 13:09:55 -0400 Subject: [PATCH 078/125] Merge pull request #5 from intergi/chessApp chess app stored request add units (cherry picked from commit 5ad2d3974a4c25b565244bccb5acca6c6b6c3f2e) --- .../data/by_id/stored_imps/.gitignore | 3 -- .../by_id/stored_imps/content_top_ios.json | 42 +++++++++++++++++ .../data/by_id/stored_imps/game_over_ios.json | 46 +++++++++++++++++++ .../by_id/stored_imps/in_article_ios.json | 46 +++++++++++++++++++ .../by_id/stored_imps/play_screen_ios.json | 46 +++++++++++++++++++ 5 files changed, 180 insertions(+), 3 deletions(-) delete mode 100644 stored_requests/data/by_id/stored_imps/.gitignore create mode 100644 stored_requests/data/by_id/stored_imps/content_top_ios.json create mode 100644 stored_requests/data/by_id/stored_imps/game_over_ios.json create mode 100644 stored_requests/data/by_id/stored_imps/in_article_ios.json create mode 100644 stored_requests/data/by_id/stored_imps/play_screen_ios.json diff --git a/stored_requests/data/by_id/stored_imps/.gitignore b/stored_requests/data/by_id/stored_imps/.gitignore deleted file mode 100644 index 9a3be781f63..00000000000 --- a/stored_requests/data/by_id/stored_imps/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Ignore everything in this directory, except for this file -* -!.gitignore diff --git a/stored_requests/data/by_id/stored_imps/content_top_ios.json b/stored_requests/data/by_id/stored_imps/content_top_ios.json new file mode 100644 index 00000000000..f6fde708a0f --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/content_top_ios.json @@ -0,0 +1,42 @@ +{ + "id": "content_top_ios", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "appnexus": { + "placementId": "19689655" + }, + "districtm": { + "placementId": "19765947" + }, + "emx_digital": { + "tagid": "107133" + }, + "openx": { + "unit": "541169197", + "delDomain": "playwire-d.openx.net" + }, + "pubmatic": { + "publisherId": "158326" + }, + "rhythmone": { + "placementId": "213224", + "zone": "1r", + "path": "mvo" + }, + "rubicon": { + "accountId": "15526", + "siteId": "335554", + "zoneId": "1764488" + }, + "sonobi": { + "ad_unit": "12eb86143abb80d6a9c6" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/game_over_ios.json b/stored_requests/data/by_id/stored_imps/game_over_ios.json new file mode 100644 index 00000000000..e9b326e71a6 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/game_over_ios.json @@ -0,0 +1,46 @@ +{ + "id": "game_over_ios", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "appnexus": { + "placementId": "19689655" + }, + "districtm": { + "placementId": "19765947" + }, + "emx_digital": { + "tagid": "105251" + }, + "openx": { + "unit": "541169197", + "delDomain": "playwire-d.openx.net" + }, + "pubmatic": { + "publisherId": "158326" + }, + "rhythmone": { + "placementId": "213224", + "zone": "1r", + "path": "mvo" + }, + "rubicon": { + "accountId": "15526", + "siteId": "335554", + "zoneId": "1764488" + }, + "sonobi": { + "ad_unit": "12eb86143abb80d6a9c6" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/in_article_ios.json b/stored_requests/data/by_id/stored_imps/in_article_ios.json new file mode 100644 index 00000000000..6000306813e --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/in_article_ios.json @@ -0,0 +1,46 @@ +{ + "id": "in_article_ios", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "appnexus": { + "placementId": "19689655" + }, + "districtm": { + "placementId": "19765947" + }, + "emx_digital": { + "tagid": "105251" + }, + "openx": { + "unit": "541169197", + "delDomain": "playwire-d.openx.net" + }, + "pubmatic": { + "publisherId": "158326" + }, + "rhythmone": { + "placementId": "213224", + "zone": "1r", + "path": "mvo" + }, + "rubicon": { + "accountId": "15526", + "siteId": "335554", + "zoneId": "1764488" + }, + "sonobi": { + "ad_unit": "12eb86143abb80d6a9c6" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/play_screen_ios.json b/stored_requests/data/by_id/stored_imps/play_screen_ios.json new file mode 100644 index 00000000000..185f80579f1 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/play_screen_ios.json @@ -0,0 +1,46 @@ +{ + "id": "play_screen_ios", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "appnexus": { + "placementId": "19689655" + }, + "districtm": { + "placementId": "19765947" + }, + "emx_digital": { + "tagid": "105251" + }, + "openx": { + "unit": "541169197", + "delDomain": "playwire-d.openx.net" + }, + "pubmatic": { + "publisherId": "158326" + }, + "rhythmone": { + "placementId": "213224", + "zone": "1r", + "path": "mvo" + }, + "rubicon": { + "accountId": "15526", + "siteId": "335554", + "zoneId": "1764488" + }, + "sonobi": { + "ad_unit": "12eb86143abb80d6a9c6" + } + } +} \ No newline at end of file From dd474d0c869732cd5b32fe0dbd5461aab40c03c8 Mon Sep 17 00:00:00 2001 From: Derek Bruneau <dbruneau@playwire.com> Date: Fri, 28 Aug 2020 09:59:04 -0400 Subject: [PATCH 079/125] Merge pull request #6 from intergi/chessApp Chess app (cherry picked from commit 26a6e9dfceb8faa8f99ab06eaad044b2bf874c4c) --- .../data/by_id/stored_imps/content_top_ios.json | 12 ++++++------ .../data/by_id/stored_imps/game_over_ios.json | 12 ++++++------ .../data/by_id/stored_imps/in_article_ios.json | 12 ++++++------ .../data/by_id/stored_imps/play_screen_ios.json | 12 ++++++------ .../data/by_id/stored_imps/playwire.json | 16 ++++++++++++++++ 5 files changed, 40 insertions(+), 24 deletions(-) create mode 100644 stored_requests/data/by_id/stored_imps/playwire.json diff --git a/stored_requests/data/by_id/stored_imps/content_top_ios.json b/stored_requests/data/by_id/stored_imps/content_top_ios.json index f6fde708a0f..276e7bef33b 100644 --- a/stored_requests/data/by_id/stored_imps/content_top_ios.json +++ b/stored_requests/data/by_id/stored_imps/content_top_ios.json @@ -10,10 +10,10 @@ }, "ext": { "appnexus": { - "placementId": "19689655" + "placement_id": 19689655 }, "districtm": { - "placementId": "19765947" + "placement_id": 19765947 }, "emx_digital": { "tagid": "107133" @@ -31,12 +31,12 @@ "path": "mvo" }, "rubicon": { - "accountId": "15526", - "siteId": "335554", - "zoneId": "1764488" + "accountId": 15526, + "siteId": 335554, + "zoneId": 1764488 }, "sonobi": { - "ad_unit": "12eb86143abb80d6a9c6" + "TagID": "12eb86143abb80d6a9c6" } } } \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/game_over_ios.json b/stored_requests/data/by_id/stored_imps/game_over_ios.json index e9b326e71a6..386915a5395 100644 --- a/stored_requests/data/by_id/stored_imps/game_over_ios.json +++ b/stored_requests/data/by_id/stored_imps/game_over_ios.json @@ -14,10 +14,10 @@ }, "ext": { "appnexus": { - "placementId": "19689655" + "placement_id": 19689655 }, "districtm": { - "placementId": "19765947" + "placement_id": 19765947 }, "emx_digital": { "tagid": "105251" @@ -35,12 +35,12 @@ "path": "mvo" }, "rubicon": { - "accountId": "15526", - "siteId": "335554", - "zoneId": "1764488" + "accountId": 15526, + "siteId": 335554, + "zoneId": 1764488 }, "sonobi": { - "ad_unit": "12eb86143abb80d6a9c6" + "TagID": "12eb86143abb80d6a9c6" } } } \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/in_article_ios.json b/stored_requests/data/by_id/stored_imps/in_article_ios.json index 6000306813e..a00834c95bb 100644 --- a/stored_requests/data/by_id/stored_imps/in_article_ios.json +++ b/stored_requests/data/by_id/stored_imps/in_article_ios.json @@ -14,10 +14,10 @@ }, "ext": { "appnexus": { - "placementId": "19689655" + "placement_id": 19689655 }, "districtm": { - "placementId": "19765947" + "placement_id": 19765947 }, "emx_digital": { "tagid": "105251" @@ -35,12 +35,12 @@ "path": "mvo" }, "rubicon": { - "accountId": "15526", - "siteId": "335554", - "zoneId": "1764488" + "accountId": 15526, + "siteId": 335554, + "zoneId": 1764488 }, "sonobi": { - "ad_unit": "12eb86143abb80d6a9c6" + "TagID": "12eb86143abb80d6a9c6" } } } \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/play_screen_ios.json b/stored_requests/data/by_id/stored_imps/play_screen_ios.json index 185f80579f1..eb7e0da5992 100644 --- a/stored_requests/data/by_id/stored_imps/play_screen_ios.json +++ b/stored_requests/data/by_id/stored_imps/play_screen_ios.json @@ -14,10 +14,10 @@ }, "ext": { "appnexus": { - "placementId": "19689655" + "placement_id": 19689655 }, "districtm": { - "placementId": "19765947" + "placement_id": 19765947 }, "emx_digital": { "tagid": "105251" @@ -35,12 +35,12 @@ "path": "mvo" }, "rubicon": { - "accountId": "15526", - "siteId": "335554", - "zoneId": "1764488" + "accountId": 15526, + "siteId": 335554, + "zoneId": 1764488 }, "sonobi": { - "ad_unit": "12eb86143abb80d6a9c6" + "TagID": "12eb86143abb80d6a9c6" } } } \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/playwire.json b/stored_requests/data/by_id/stored_imps/playwire.json new file mode 100644 index 00000000000..6eaae21818b --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/playwire.json @@ -0,0 +1,16 @@ +{ + "id": "playwire", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "appnexus": { + "placement_id": 19689655 + } + } +} \ No newline at end of file From 9f900ac1302c0f025e3fc153fcf58baf05f06cc8 Mon Sep 17 00:00:00 2001 From: Derek Bruneau <dbruneau@playwire.com> Date: Fri, 28 Aug 2020 10:42:46 -0400 Subject: [PATCH 080/125] Merge pull request #7 from intergi/chessApp Chess app (cherry picked from commit 514893cf02be0c32bb99e7f75b9fce9ee2cb157e) --- stored_requests/data/by_id/stored_requests/.gitignore | 3 --- .../data/by_id/{stored_imps => stored_requests}/playwire.json | 0 2 files changed, 3 deletions(-) delete mode 100644 stored_requests/data/by_id/stored_requests/.gitignore rename stored_requests/data/by_id/{stored_imps => stored_requests}/playwire.json (100%) diff --git a/stored_requests/data/by_id/stored_requests/.gitignore b/stored_requests/data/by_id/stored_requests/.gitignore deleted file mode 100644 index 9a3be781f63..00000000000 --- a/stored_requests/data/by_id/stored_requests/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Ignore everything in this directory, except for this file -* -!.gitignore diff --git a/stored_requests/data/by_id/stored_imps/playwire.json b/stored_requests/data/by_id/stored_requests/playwire.json similarity index 100% rename from stored_requests/data/by_id/stored_imps/playwire.json rename to stored_requests/data/by_id/stored_requests/playwire.json From f9fac68d9b4c7dfaebbdaaf130dd5ceea59a5bdc Mon Sep 17 00:00:00 2001 From: Derek Bruneau <dbruneau@playwire.com> Date: Fri, 28 Aug 2020 11:01:18 -0400 Subject: [PATCH 081/125] Merge pull request #8 from intergi/chessApp remove districtm, specify timeout in stored_request (cherry picked from commit 59659db9d84ae14fc41546f1dd88885ee2e5ba16) --- .../data/by_id/stored_imps/content_top_ios.json | 3 --- .../data/by_id/stored_imps/game_over_ios.json | 3 --- .../data/by_id/stored_imps/in_article_ios.json | 3 --- .../data/by_id/stored_imps/play_screen_ios.json | 3 --- .../data/by_id/stored_requests/playwire.json | 15 +-------------- 5 files changed, 1 insertion(+), 26 deletions(-) diff --git a/stored_requests/data/by_id/stored_imps/content_top_ios.json b/stored_requests/data/by_id/stored_imps/content_top_ios.json index 276e7bef33b..f57df9a7bf5 100644 --- a/stored_requests/data/by_id/stored_imps/content_top_ios.json +++ b/stored_requests/data/by_id/stored_imps/content_top_ios.json @@ -12,9 +12,6 @@ "appnexus": { "placement_id": 19689655 }, - "districtm": { - "placement_id": 19765947 - }, "emx_digital": { "tagid": "107133" }, diff --git a/stored_requests/data/by_id/stored_imps/game_over_ios.json b/stored_requests/data/by_id/stored_imps/game_over_ios.json index 386915a5395..5177a12aa9a 100644 --- a/stored_requests/data/by_id/stored_imps/game_over_ios.json +++ b/stored_requests/data/by_id/stored_imps/game_over_ios.json @@ -16,9 +16,6 @@ "appnexus": { "placement_id": 19689655 }, - "districtm": { - "placement_id": 19765947 - }, "emx_digital": { "tagid": "105251" }, diff --git a/stored_requests/data/by_id/stored_imps/in_article_ios.json b/stored_requests/data/by_id/stored_imps/in_article_ios.json index a00834c95bb..f1e4b2fff3a 100644 --- a/stored_requests/data/by_id/stored_imps/in_article_ios.json +++ b/stored_requests/data/by_id/stored_imps/in_article_ios.json @@ -16,9 +16,6 @@ "appnexus": { "placement_id": 19689655 }, - "districtm": { - "placement_id": 19765947 - }, "emx_digital": { "tagid": "105251" }, diff --git a/stored_requests/data/by_id/stored_imps/play_screen_ios.json b/stored_requests/data/by_id/stored_imps/play_screen_ios.json index eb7e0da5992..3208a74f50e 100644 --- a/stored_requests/data/by_id/stored_imps/play_screen_ios.json +++ b/stored_requests/data/by_id/stored_imps/play_screen_ios.json @@ -16,9 +16,6 @@ "appnexus": { "placement_id": 19689655 }, - "districtm": { - "placement_id": 19765947 - }, "emx_digital": { "tagid": "105251" }, diff --git a/stored_requests/data/by_id/stored_requests/playwire.json b/stored_requests/data/by_id/stored_requests/playwire.json index 6eaae21818b..263efbe2eff 100644 --- a/stored_requests/data/by_id/stored_requests/playwire.json +++ b/stored_requests/data/by_id/stored_requests/playwire.json @@ -1,16 +1,3 @@ { - "id": "playwire", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "appnexus": { - "placement_id": 19689655 - } - } + "tmax": 1000 } \ No newline at end of file From c921c6a7f7b3a57c5f5de96164e8f72a589ba663 Mon Sep 17 00:00:00 2001 From: tcreamer1 <tcreamer@playwire.com> Date: Fri, 28 Aug 2020 11:48:39 -0400 Subject: [PATCH 082/125] Merge pull request #9 from intergi/chessApp try rubicon with display params for ios testing (cherry picked from commit c02ccdc7d9dd25fa408272168ee4eefb0a66a670) --- stored_requests/data/by_id/stored_imps/in_article_ios.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stored_requests/data/by_id/stored_imps/in_article_ios.json b/stored_requests/data/by_id/stored_imps/in_article_ios.json index f1e4b2fff3a..b08ed3b8c12 100644 --- a/stored_requests/data/by_id/stored_imps/in_article_ios.json +++ b/stored_requests/data/by_id/stored_imps/in_article_ios.json @@ -33,8 +33,8 @@ }, "rubicon": { "accountId": 15526, - "siteId": 335554, - "zoneId": 1764488 + "siteId": 110932, + "zoneId": 523774 }, "sonobi": { "TagID": "12eb86143abb80d6a9c6" From e1193d23d1e5ed67d2284fd7d2a573bef120222a Mon Sep 17 00:00:00 2001 From: tcreamer1 <tcreamer@playwire.com> Date: Fri, 28 Aug 2020 12:17:45 -0400 Subject: [PATCH 083/125] Merge pull request #10 from intergi/chessApp Chess app (cherry picked from commit 648623a6c4b1e84024d9b463c5afb9da137f2ed1) --- stored_requests/data/by_id/stored_imps/in_article_ios.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stored_requests/data/by_id/stored_imps/in_article_ios.json b/stored_requests/data/by_id/stored_imps/in_article_ios.json index b08ed3b8c12..6e0a46f4950 100644 --- a/stored_requests/data/by_id/stored_imps/in_article_ios.json +++ b/stored_requests/data/by_id/stored_imps/in_article_ios.json @@ -14,7 +14,7 @@ }, "ext": { "appnexus": { - "placement_id": 19689655 + "placement_id": 19689654 }, "emx_digital": { "tagid": "105251" @@ -33,8 +33,8 @@ }, "rubicon": { "accountId": 15526, - "siteId": 110932, - "zoneId": 523774 + "siteId": 335554, + "zoneId": 1764488 }, "sonobi": { "TagID": "12eb86143abb80d6a9c6" From 302e7e257ddbdbd6d05768501ce88f68d77568bd Mon Sep 17 00:00:00 2001 From: Derek Bruneau <dbruneau@playwire.com> Date: Mon, 31 Aug 2020 10:03:20 -0400 Subject: [PATCH 084/125] Merge pull request #11 from intergi/chessApp try ad unit in stored_requests foldre (cherry picked from commit 5c08bb42e67ef3dcfc25dc1877f18d48bdca11a0) --- .../by_id/{stored_imps => stored_requests}/in_article_ios.json | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename stored_requests/data/by_id/{stored_imps => stored_requests}/in_article_ios.json (100%) diff --git a/stored_requests/data/by_id/stored_imps/in_article_ios.json b/stored_requests/data/by_id/stored_requests/in_article_ios.json similarity index 100% rename from stored_requests/data/by_id/stored_imps/in_article_ios.json rename to stored_requests/data/by_id/stored_requests/in_article_ios.json From 8e07ab9052a0fdc0a5e59466bc71c67269f150f2 Mon Sep 17 00:00:00 2001 From: tcreamer1 <tcreamer@playwire.com> Date: Mon, 31 Aug 2020 10:13:10 -0400 Subject: [PATCH 085/125] Merge pull request #12 from intergi/chessApp try in_article_ios in both folders (cherry picked from commit ea5de87f9123ce09ce88d0aad8160be105f42a95) --- .../by_id/stored_imps/in_article_ios.json | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 stored_requests/data/by_id/stored_imps/in_article_ios.json diff --git a/stored_requests/data/by_id/stored_imps/in_article_ios.json b/stored_requests/data/by_id/stored_imps/in_article_ios.json new file mode 100644 index 00000000000..6e0a46f4950 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/in_article_ios.json @@ -0,0 +1,43 @@ +{ + "id": "in_article_ios", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "appnexus": { + "placement_id": 19689654 + }, + "emx_digital": { + "tagid": "105251" + }, + "openx": { + "unit": "541169197", + "delDomain": "playwire-d.openx.net" + }, + "pubmatic": { + "publisherId": "158326" + }, + "rhythmone": { + "placementId": "213224", + "zone": "1r", + "path": "mvo" + }, + "rubicon": { + "accountId": 15526, + "siteId": 335554, + "zoneId": 1764488 + }, + "sonobi": { + "TagID": "12eb86143abb80d6a9c6" + } + } +} \ No newline at end of file From 5900e573ec1da70298b21ad9073fe49a0017a70f Mon Sep 17 00:00:00 2001 From: tcreamer1 <tcreamer@playwire.com> Date: Wed, 26 May 2021 08:39:53 -0400 Subject: [PATCH 086/125] Merge pull request #20 from intergi/stored_requests update bidder params in stored requests (cherry picked from commit a696693f6bcf4c7d9d96172dc62a4a5ffe2635d4) --- .../stored_imps/content_top_android.json | 43 +++++++++++++++++++ .../by_id/stored_imps/content_top_ios.json | 8 +++- .../data/by_id/stored_imps/game_over_ios.json | 8 +++- .../by_id/stored_imps/in_article_ios.json | 8 +++- .../by_id/stored_imps/play_screen_ios.json | 8 +++- 5 files changed, 67 insertions(+), 8 deletions(-) create mode 100644 stored_requests/data/by_id/stored_imps/content_top_android.json diff --git a/stored_requests/data/by_id/stored_imps/content_top_android.json b/stored_requests/data/by_id/stored_imps/content_top_android.json new file mode 100644 index 00000000000..edf7f988489 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/content_top_android.json @@ -0,0 +1,43 @@ +{ + "id": "content_top_ios", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "appnexus": { + "placement_id": 19689655 + }, + "emx_digital": { + "tagid": "107133" + }, + "openx": { + "unit": "541169197", + "delDomain": "playwire-d.openx.net" + }, + "pubmatic": { + "publisherId": "158326", + "adSlot": "1957467" + }, + "rhythmone": { + "placementId": "213696", + "zone": "1r", + "path": "mvo" + }, + "rubicon": { + "accountId": 15526, + "siteId": 335554, + "zoneId": 1764488 + }, + "sonobi": { + "TagID": "12eb86143abb80d6a9c6" + }, + "oftmedia": { + "placementId": "19606578" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/content_top_ios.json b/stored_requests/data/by_id/stored_imps/content_top_ios.json index f57df9a7bf5..5582ccce573 100644 --- a/stored_requests/data/by_id/stored_imps/content_top_ios.json +++ b/stored_requests/data/by_id/stored_imps/content_top_ios.json @@ -20,10 +20,11 @@ "delDomain": "playwire-d.openx.net" }, "pubmatic": { - "publisherId": "158326" + "publisherId": "158326", + "adSlot": "1957464" }, "rhythmone": { - "placementId": "213224", + "placementId": "213696", "zone": "1r", "path": "mvo" }, @@ -34,6 +35,9 @@ }, "sonobi": { "TagID": "12eb86143abb80d6a9c6" + }, + "oftmedia": { + "placementId": "19606578" } } } \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/game_over_ios.json b/stored_requests/data/by_id/stored_imps/game_over_ios.json index 5177a12aa9a..ff960468c7d 100644 --- a/stored_requests/data/by_id/stored_imps/game_over_ios.json +++ b/stored_requests/data/by_id/stored_imps/game_over_ios.json @@ -24,10 +24,11 @@ "delDomain": "playwire-d.openx.net" }, "pubmatic": { - "publisherId": "158326" + "publisherId": "158326", + "adSlot": "1957464" }, "rhythmone": { - "placementId": "213224", + "placementId": "213696", "zone": "1r", "path": "mvo" }, @@ -38,6 +39,9 @@ }, "sonobi": { "TagID": "12eb86143abb80d6a9c6" + }, + "oftmedia": { + "placementId": "19606578" } } } \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/in_article_ios.json b/stored_requests/data/by_id/stored_imps/in_article_ios.json index 6e0a46f4950..49643218c70 100644 --- a/stored_requests/data/by_id/stored_imps/in_article_ios.json +++ b/stored_requests/data/by_id/stored_imps/in_article_ios.json @@ -24,10 +24,11 @@ "delDomain": "playwire-d.openx.net" }, "pubmatic": { - "publisherId": "158326" + "publisherId": "158326", + "adSlot": "1957464" }, "rhythmone": { - "placementId": "213224", + "placementId": "213696", "zone": "1r", "path": "mvo" }, @@ -38,6 +39,9 @@ }, "sonobi": { "TagID": "12eb86143abb80d6a9c6" + }, + "oftmedia": { + "placementId": "19606578" } } } \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/play_screen_ios.json b/stored_requests/data/by_id/stored_imps/play_screen_ios.json index 3208a74f50e..75bf799f43d 100644 --- a/stored_requests/data/by_id/stored_imps/play_screen_ios.json +++ b/stored_requests/data/by_id/stored_imps/play_screen_ios.json @@ -24,10 +24,11 @@ "delDomain": "playwire-d.openx.net" }, "pubmatic": { - "publisherId": "158326" + "publisherId": "158326", + "adSlot": "1957464" }, "rhythmone": { - "placementId": "213224", + "placementId": "213696", "zone": "1r", "path": "mvo" }, @@ -38,6 +39,9 @@ }, "sonobi": { "TagID": "12eb86143abb80d6a9c6" + }, + "oftmedia": { + "placementId": "19606578" } } } \ No newline at end of file From 9a302e1f832ff36e9631d3dc3f62d8042f5cb889 Mon Sep 17 00:00:00 2001 From: tcreamer1 <tcreamer@playwire.com> Date: Wed, 26 May 2021 08:51:43 -0400 Subject: [PATCH 087/125] Merge pull request #21 from intergi/stored_requests oftmedia as int, store ext data (cherry picked from commit a189524e5d9ce0efbecec0686bbfc3d47a9ff796) --- .../stored_imps/content_top_android.json | 2 +- .../by_id/stored_imps/content_top_ios.json | 2 +- .../data/by_id/stored_imps/game_over_ios.json | 2 +- .../by_id/stored_imps/in_article_ios.json | 2 +- .../by_id/stored_imps/play_screen_ios.json | 2 +- .../data/by_id/stored_requests/playwire.json | 28 +++++++++++++++++-- 6 files changed, 31 insertions(+), 7 deletions(-) diff --git a/stored_requests/data/by_id/stored_imps/content_top_android.json b/stored_requests/data/by_id/stored_imps/content_top_android.json index edf7f988489..67cc417d456 100644 --- a/stored_requests/data/by_id/stored_imps/content_top_android.json +++ b/stored_requests/data/by_id/stored_imps/content_top_android.json @@ -37,7 +37,7 @@ "TagID": "12eb86143abb80d6a9c6" }, "oftmedia": { - "placementId": "19606578" + "placementId": 19606578 } } } \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/content_top_ios.json b/stored_requests/data/by_id/stored_imps/content_top_ios.json index 5582ccce573..c87159ef3f5 100644 --- a/stored_requests/data/by_id/stored_imps/content_top_ios.json +++ b/stored_requests/data/by_id/stored_imps/content_top_ios.json @@ -37,7 +37,7 @@ "TagID": "12eb86143abb80d6a9c6" }, "oftmedia": { - "placementId": "19606578" + "placementId": 19606578 } } } \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/game_over_ios.json b/stored_requests/data/by_id/stored_imps/game_over_ios.json index ff960468c7d..39b82459cc3 100644 --- a/stored_requests/data/by_id/stored_imps/game_over_ios.json +++ b/stored_requests/data/by_id/stored_imps/game_over_ios.json @@ -41,7 +41,7 @@ "TagID": "12eb86143abb80d6a9c6" }, "oftmedia": { - "placementId": "19606578" + "placementId": 19606578 } } } \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/in_article_ios.json b/stored_requests/data/by_id/stored_imps/in_article_ios.json index 49643218c70..e05b5be4ea3 100644 --- a/stored_requests/data/by_id/stored_imps/in_article_ios.json +++ b/stored_requests/data/by_id/stored_imps/in_article_ios.json @@ -41,7 +41,7 @@ "TagID": "12eb86143abb80d6a9c6" }, "oftmedia": { - "placementId": "19606578" + "placementId": 19606578 } } } \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/play_screen_ios.json b/stored_requests/data/by_id/stored_imps/play_screen_ios.json index 75bf799f43d..c893b6efe79 100644 --- a/stored_requests/data/by_id/stored_imps/play_screen_ios.json +++ b/stored_requests/data/by_id/stored_imps/play_screen_ios.json @@ -41,7 +41,7 @@ "TagID": "12eb86143abb80d6a9c6" }, "oftmedia": { - "placementId": "19606578" + "placementId": 19606578 } } } \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_requests/playwire.json b/stored_requests/data/by_id/stored_requests/playwire.json index 263efbe2eff..351529ad6e2 100644 --- a/stored_requests/data/by_id/stored_requests/playwire.json +++ b/stored_requests/data/by_id/stored_requests/playwire.json @@ -1,3 +1,27 @@ { - "tmax": 1000 -} \ No newline at end of file + "tmax": 1000, + "ext": { + "prebid": { + "targeting": { + "includewinners": true, + "includebidderkeys": false, + "mediatypepricegranularity": { + "banner": { + "ranges": [ + { + "max": 50, + "min": 0, + "increment": 0.01 + } + ], + "precision": 3 + } + } + }, + "cache": { + "bids": {} + }, + "aliases": { "oftmedia": "appnexus" } + } + } +} From 6282e4ad7b2432ec64dda4a48569df1327ccc52c Mon Sep 17 00:00:00 2001 From: tcreamer1 <tcreamer@playwire.com> Date: Wed, 26 May 2021 10:06:24 -0400 Subject: [PATCH 088/125] Merge pull request #22 from intergi/stored_requests fix id (cherry picked from commit 0f49b4dc52ef9fc76d41318174c1a03f1c8a114b) --- stored_requests/data/by_id/stored_imps/content_top_android.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stored_requests/data/by_id/stored_imps/content_top_android.json b/stored_requests/data/by_id/stored_imps/content_top_android.json index 67cc417d456..36fae36e735 100644 --- a/stored_requests/data/by_id/stored_imps/content_top_android.json +++ b/stored_requests/data/by_id/stored_imps/content_top_android.json @@ -1,5 +1,5 @@ { - "id": "content_top_ios", + "id": "content_top_android", "banner": { "format": [ { From eccfebf21cc38df68ac4dcacadc6959912d47262 Mon Sep 17 00:00:00 2001 From: Derek Bruneau <derek.bruneau@gmail.com> Date: Mon, 9 Sep 2024 11:14:08 -0400 Subject: [PATCH 089/125] Updating stored requests (partially cherry picked from commit e4774f69ea3f6a0b13bd1e1a33e6edaa0c67ba5e) --- stored_requests/data/by_id/stored_imps/content_top_ios.json | 6 +++--- stored_requests/data/by_id/stored_requests/playwire.json | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/stored_requests/data/by_id/stored_imps/content_top_ios.json b/stored_requests/data/by_id/stored_imps/content_top_ios.json index c87159ef3f5..301e5f80fd2 100644 --- a/stored_requests/data/by_id/stored_imps/content_top_ios.json +++ b/stored_requests/data/by_id/stored_imps/content_top_ios.json @@ -3,8 +3,8 @@ "banner": { "format": [ { - "w": 320, - "h": 50 + "w": 300, + "h": 250 } ] }, @@ -21,7 +21,7 @@ }, "pubmatic": { "publisherId": "158326", - "adSlot": "1957464" + "adSlot": "3029490" }, "rhythmone": { "placementId": "213696", diff --git a/stored_requests/data/by_id/stored_requests/playwire.json b/stored_requests/data/by_id/stored_requests/playwire.json index 351529ad6e2..9c5eb1aadf7 100644 --- a/stored_requests/data/by_id/stored_requests/playwire.json +++ b/stored_requests/data/by_id/stored_requests/playwire.json @@ -1,5 +1,8 @@ { "tmax": 1000, + "cur": [ + "USD" + ], "ext": { "prebid": { "targeting": { From 90444d9936ca5b877d7d54d19b387e13a94bffa1 Mon Sep 17 00:00:00 2001 From: Derek Bruneau <derek.bruneau@gmail.com> Date: Mon, 9 Sep 2024 11:16:13 -0400 Subject: [PATCH 090/125] add sizes to stored requests (cherry picked from commit 1a946ede46b427f27e9959898354ec4fb8c55f66) --- .../data/by_id/stored_imps/content_top_android.json | 4 ++++ stored_requests/data/by_id/stored_imps/content_top_ios.json | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/stored_requests/data/by_id/stored_imps/content_top_android.json b/stored_requests/data/by_id/stored_imps/content_top_android.json index 36fae36e735..8ca6dcc6d39 100644 --- a/stored_requests/data/by_id/stored_imps/content_top_android.json +++ b/stored_requests/data/by_id/stored_imps/content_top_android.json @@ -5,6 +5,10 @@ { "w": 320, "h": 50 + }, + { + "w": 300, + "h": 250 } ] }, diff --git a/stored_requests/data/by_id/stored_imps/content_top_ios.json b/stored_requests/data/by_id/stored_imps/content_top_ios.json index 301e5f80fd2..4df75274d33 100644 --- a/stored_requests/data/by_id/stored_imps/content_top_ios.json +++ b/stored_requests/data/by_id/stored_imps/content_top_ios.json @@ -2,6 +2,10 @@ "id": "content_top_ios", "banner": { "format": [ + { + "w": 320, + "h": 50 + }, { "w": 300, "h": 250 From b6ea40ec94c113bd3fd52323cac4daf0de00a0bb Mon Sep 17 00:00:00 2001 From: tcreamer1 <tcreamer@playwire.com> Date: Thu, 8 Jul 2021 13:26:10 -0400 Subject: [PATCH 091/125] Merge pull request #26 from intergi/test/upgradePrebidServer update go version to use (cherry picked from commit fa12f2190380e53b27efd6e8ac3654343220c98f) --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index dc10121c71d..9c214c538b4 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/prebid/prebid-server go 1.19 // Magic comment that determines which Go version Heroku uses. -// +heroku goVersion go1.12 +// +heroku goVersion go1.16 require ( github.com/DATA-DOG/go-sqlmock v1.5.0 From 90666b87b5354d70c8de4183d4d3af229e3c5bf1 Mon Sep 17 00:00:00 2001 From: tcreamer1 <tcreamer@playwire.com> Date: Thu, 22 Jul 2021 15:11:57 -0400 Subject: [PATCH 092/125] Merge pull request #29 from intergi/newAdUnits New ad units (cherry picked from commit 7b213d06b9be259ca69162b4d5d03c82fc01e28a) --- .../stored_imps/content_top_android.json | 4 -- ...nt_top_ios.json => game_over_android.json} | 12 ++--- .../data/by_id/stored_imps/game_over_ios.json | 8 ++-- .../by_id/stored_imps/in_article_android.json | 47 +++++++++++++++++++ .../by_id/stored_imps/in_article_ios.json | 8 ++-- .../by_id/stored_imps/play_screen_ios.json | 4 -- 6 files changed, 61 insertions(+), 22 deletions(-) rename stored_requests/data/by_id/stored_imps/{content_top_ios.json => game_over_android.json} (93%) create mode 100644 stored_requests/data/by_id/stored_imps/in_article_android.json diff --git a/stored_requests/data/by_id/stored_imps/content_top_android.json b/stored_requests/data/by_id/stored_imps/content_top_android.json index 8ca6dcc6d39..36fae36e735 100644 --- a/stored_requests/data/by_id/stored_imps/content_top_android.json +++ b/stored_requests/data/by_id/stored_imps/content_top_android.json @@ -5,10 +5,6 @@ { "w": 320, "h": 50 - }, - { - "w": 300, - "h": 250 } ] }, diff --git a/stored_requests/data/by_id/stored_imps/content_top_ios.json b/stored_requests/data/by_id/stored_imps/game_over_android.json similarity index 93% rename from stored_requests/data/by_id/stored_imps/content_top_ios.json rename to stored_requests/data/by_id/stored_imps/game_over_android.json index 4df75274d33..b4ccfbdfa18 100644 --- a/stored_requests/data/by_id/stored_imps/content_top_ios.json +++ b/stored_requests/data/by_id/stored_imps/game_over_android.json @@ -1,14 +1,14 @@ { - "id": "content_top_ios", + "id": "game_over_android", "banner": { "format": [ - { - "w": 320, - "h": 50 - }, { "w": 300, "h": 250 + }, + { + "w": 320, + "h": 50 } ] }, @@ -25,7 +25,7 @@ }, "pubmatic": { "publisherId": "158326", - "adSlot": "3029490" + "adSlot": "1957467" }, "rhythmone": { "placementId": "213696", diff --git a/stored_requests/data/by_id/stored_imps/game_over_ios.json b/stored_requests/data/by_id/stored_imps/game_over_ios.json index 39b82459cc3..2cd3aff8262 100644 --- a/stored_requests/data/by_id/stored_imps/game_over_ios.json +++ b/stored_requests/data/by_id/stored_imps/game_over_ios.json @@ -2,13 +2,13 @@ "id": "game_over_ios", "banner": { "format": [ - { - "w": 320, - "h": 50 - }, { "w": 300, "h": 250 + }, + { + "w": 320, + "h": 50 } ] }, diff --git a/stored_requests/data/by_id/stored_imps/in_article_android.json b/stored_requests/data/by_id/stored_imps/in_article_android.json new file mode 100644 index 00000000000..3f0ca3d138f --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/in_article_android.json @@ -0,0 +1,47 @@ +{ + "id": "in_article_android", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "appnexus": { + "placement_id": 19689655 + }, + "emx_digital": { + "tagid": "107133" + }, + "openx": { + "unit": "541169197", + "delDomain": "playwire-d.openx.net" + }, + "pubmatic": { + "publisherId": "158326", + "adSlot": "1957467" + }, + "rhythmone": { + "placementId": "213696", + "zone": "1r", + "path": "mvo" + }, + "rubicon": { + "accountId": 15526, + "siteId": 335554, + "zoneId": 1764488 + }, + "sonobi": { + "TagID": "12eb86143abb80d6a9c6" + }, + "oftmedia": { + "placementId": 19606578 + } + } +} diff --git a/stored_requests/data/by_id/stored_imps/in_article_ios.json b/stored_requests/data/by_id/stored_imps/in_article_ios.json index e05b5be4ea3..56989f02fc8 100644 --- a/stored_requests/data/by_id/stored_imps/in_article_ios.json +++ b/stored_requests/data/by_id/stored_imps/in_article_ios.json @@ -2,13 +2,13 @@ "id": "in_article_ios", "banner": { "format": [ - { - "w": 320, - "h": 50 - }, { "w": 300, "h": 250 + }, + { + "w": 320, + "h": 50 } ] }, diff --git a/stored_requests/data/by_id/stored_imps/play_screen_ios.json b/stored_requests/data/by_id/stored_imps/play_screen_ios.json index c893b6efe79..496024e0837 100644 --- a/stored_requests/data/by_id/stored_imps/play_screen_ios.json +++ b/stored_requests/data/by_id/stored_imps/play_screen_ios.json @@ -5,10 +5,6 @@ { "w": 320, "h": 50 - }, - { - "w": 300, - "h": 250 } ] }, From 26abf0616aad310fb94543de21716b01829d1ce1 Mon Sep 17 00:00:00 2001 From: tcreamer1 <tcreamer@playwire.com> Date: Thu, 22 Jul 2021 15:16:41 -0400 Subject: [PATCH 093/125] Merge pull request #30 from intergi/triplelift add trilelift bid params (cherry picked from commit 509c31316742fa0d3b62f35f4277988f66c74216) --- .../data/by_id/stored_imps/content_top_android.json | 3 +++ stored_requests/data/by_id/stored_imps/play_screen_ios.json | 3 +++ 2 files changed, 6 insertions(+) diff --git a/stored_requests/data/by_id/stored_imps/content_top_android.json b/stored_requests/data/by_id/stored_imps/content_top_android.json index 36fae36e735..a9ab92407fa 100644 --- a/stored_requests/data/by_id/stored_imps/content_top_android.json +++ b/stored_requests/data/by_id/stored_imps/content_top_android.json @@ -38,6 +38,9 @@ }, "oftmedia": { "placementId": 19606578 + }, + "triplelift": { + "inventoryCode": "pwm_android_hdx_pb" } } } \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/play_screen_ios.json b/stored_requests/data/by_id/stored_imps/play_screen_ios.json index 496024e0837..6fd0ac82f1d 100644 --- a/stored_requests/data/by_id/stored_imps/play_screen_ios.json +++ b/stored_requests/data/by_id/stored_imps/play_screen_ios.json @@ -38,6 +38,9 @@ }, "oftmedia": { "placementId": 19606578 + }, + "triplelift": { + "inventoryCode": "pwm_android_hdx_pb" } } } \ No newline at end of file From f2e97b4cfc7b251df168cbfbf2df94d0f039a8a2 Mon Sep 17 00:00:00 2001 From: tcreamer1 <tcreamer@playwire.com> Date: Fri, 30 Jul 2021 10:27:07 -0400 Subject: [PATCH 094/125] fix ios play screen triplelift params (cherry picked from commit 2e2ac52dc4b70dd5743a67b9769ee7b25fdad4a8) --- stored_requests/data/by_id/stored_imps/play_screen_ios.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stored_requests/data/by_id/stored_imps/play_screen_ios.json b/stored_requests/data/by_id/stored_imps/play_screen_ios.json index 6fd0ac82f1d..dd4bc8cf1e0 100644 --- a/stored_requests/data/by_id/stored_imps/play_screen_ios.json +++ b/stored_requests/data/by_id/stored_imps/play_screen_ios.json @@ -40,7 +40,7 @@ "placementId": 19606578 }, "triplelift": { - "inventoryCode": "pwm_android_hdx_pb" + "inventoryCode": "pwm_ios_hdx_pb" } } } \ No newline at end of file From a5192feb80294542fe3dacd0681d042860fbe1a4 Mon Sep 17 00:00:00 2001 From: tcreamer1 <tcreamer@playwire.com> Date: Mon, 2 Aug 2021 09:14:46 -0400 Subject: [PATCH 095/125] updated params for each bidder per size (cherry picked from commit 2c1f701cbf9fe68ab24aafe1a989ab60cbf3f3e6) --- .../by_id/stored_imps/content_top_android.json | 18 +++++++++--------- .../by_id/stored_imps/game_over_android.json | 17 ++++++++++------- .../data/by_id/stored_imps/game_over_ios.json | 15 +++++++++------ .../by_id/stored_imps/in_article_android.json | 17 ++++++++++------- .../data/by_id/stored_imps/in_article_ios.json | 15 +++++++++------ .../by_id/stored_imps/play_screen_ios.json | 18 +++++++++--------- 6 files changed, 56 insertions(+), 44 deletions(-) diff --git a/stored_requests/data/by_id/stored_imps/content_top_android.json b/stored_requests/data/by_id/stored_imps/content_top_android.json index a9ab92407fa..c2b29e38b8b 100644 --- a/stored_requests/data/by_id/stored_imps/content_top_android.json +++ b/stored_requests/data/by_id/stored_imps/content_top_android.json @@ -15,13 +15,16 @@ "emx_digital": { "tagid": "107133" }, + "oftmedia": { + "placementId": 19606578 + }, "openx": { - "unit": "541169197", + "unit": "544086382", "delDomain": "playwire-d.openx.net" }, "pubmatic": { "publisherId": "158326", - "adSlot": "1957467" + "adSlot": "3029233" }, "rhythmone": { "placementId": "213696", @@ -29,15 +32,12 @@ "path": "mvo" }, "rubicon": { - "accountId": 15526, - "siteId": 335554, - "zoneId": 1764488 + "accountId": 12556, + "siteId": 376280, + "zoneId": 2061954 }, "sonobi": { - "TagID": "12eb86143abb80d6a9c6" - }, - "oftmedia": { - "placementId": 19606578 + "TagID": "5200294cfc91869f267d" }, "triplelift": { "inventoryCode": "pwm_android_hdx_pb" diff --git a/stored_requests/data/by_id/stored_imps/game_over_android.json b/stored_requests/data/by_id/stored_imps/game_over_android.json index b4ccfbdfa18..01237cb4348 100644 --- a/stored_requests/data/by_id/stored_imps/game_over_android.json +++ b/stored_requests/data/by_id/stored_imps/game_over_android.json @@ -19,8 +19,11 @@ "emx_digital": { "tagid": "107133" }, + "oftmedia": { + "placementId": 19606578 + }, "openx": { - "unit": "541169197", + "unit": "544086382", "delDomain": "playwire-d.openx.net" }, "pubmatic": { @@ -33,15 +36,15 @@ "path": "mvo" }, "rubicon": { - "accountId": 15526, - "siteId": 335554, - "zoneId": 1764488 + "accountId": 12556, + "siteId": 376280, + "zoneId": 2061954 }, "sonobi": { - "TagID": "12eb86143abb80d6a9c6" + "TagID": "e67f243a24590037a75c" }, - "oftmedia": { - "placementId": 19606578 + "triplelift": { + "inventoryCode": "pwm_android_320x50_300x250_pb" } } } \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/game_over_ios.json b/stored_requests/data/by_id/stored_imps/game_over_ios.json index 2cd3aff8262..d20dab17d39 100644 --- a/stored_requests/data/by_id/stored_imps/game_over_ios.json +++ b/stored_requests/data/by_id/stored_imps/game_over_ios.json @@ -19,8 +19,11 @@ "emx_digital": { "tagid": "105251" }, + "oftmedia": { + "placementId": 19606578 + }, "openx": { - "unit": "541169197", + "unit": "544086381", "delDomain": "playwire-d.openx.net" }, "pubmatic": { @@ -33,15 +36,15 @@ "path": "mvo" }, "rubicon": { - "accountId": 15526, - "siteId": 335554, - "zoneId": 1764488 + "accountId": 12556, + "siteId": 376280, + "zoneId": 2061954 }, "sonobi": { "TagID": "12eb86143abb80d6a9c6" }, - "oftmedia": { - "placementId": 19606578 + "triplelift": { + "inventoryCode": "pwm_ios_320x50_300x250_pb" } } } \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/in_article_android.json b/stored_requests/data/by_id/stored_imps/in_article_android.json index 3f0ca3d138f..a6106c6e399 100644 --- a/stored_requests/data/by_id/stored_imps/in_article_android.json +++ b/stored_requests/data/by_id/stored_imps/in_article_android.json @@ -19,8 +19,11 @@ "emx_digital": { "tagid": "107133" }, + "oftmedia": { + "placementId": 19606578 + }, "openx": { - "unit": "541169197", + "unit": "544086382", "delDomain": "playwire-d.openx.net" }, "pubmatic": { @@ -33,15 +36,15 @@ "path": "mvo" }, "rubicon": { - "accountId": 15526, - "siteId": 335554, - "zoneId": 1764488 + "accountId": 12556, + "siteId": 376280, + "zoneId": 2061954 }, "sonobi": { - "TagID": "12eb86143abb80d6a9c6" + "TagID": "e67f243a24590037a75c" }, - "oftmedia": { - "placementId": 19606578 + "triplelift": { + "inventoryCode": "pwm_android_320x50_300x250_pb" } } } diff --git a/stored_requests/data/by_id/stored_imps/in_article_ios.json b/stored_requests/data/by_id/stored_imps/in_article_ios.json index 56989f02fc8..2e6f110d14e 100644 --- a/stored_requests/data/by_id/stored_imps/in_article_ios.json +++ b/stored_requests/data/by_id/stored_imps/in_article_ios.json @@ -19,8 +19,11 @@ "emx_digital": { "tagid": "105251" }, + "oftmedia": { + "placementId": 19606578 + }, "openx": { - "unit": "541169197", + "unit": "544086381", "delDomain": "playwire-d.openx.net" }, "pubmatic": { @@ -33,15 +36,15 @@ "path": "mvo" }, "rubicon": { - "accountId": 15526, - "siteId": 335554, - "zoneId": 1764488 + "accountId": 12556, + "siteId": 376280, + "zoneId": 2061954 }, "sonobi": { "TagID": "12eb86143abb80d6a9c6" }, - "oftmedia": { - "placementId": 19606578 + "triplelift": { + "inventoryCode": "pwm_ios_320x50_300x250_pb" } } } \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/play_screen_ios.json b/stored_requests/data/by_id/stored_imps/play_screen_ios.json index dd4bc8cf1e0..047ce6ac612 100644 --- a/stored_requests/data/by_id/stored_imps/play_screen_ios.json +++ b/stored_requests/data/by_id/stored_imps/play_screen_ios.json @@ -15,13 +15,16 @@ "emx_digital": { "tagid": "105251" }, + "oftmedia": { + "placementId": 19606578 + }, "openx": { - "unit": "541169197", + "unit": "544086381", "delDomain": "playwire-d.openx.net" }, "pubmatic": { "publisherId": "158326", - "adSlot": "1957464" + "adSlot": "3029490" }, "rhythmone": { "placementId": "213696", @@ -29,15 +32,12 @@ "path": "mvo" }, "rubicon": { - "accountId": 15526, - "siteId": 335554, - "zoneId": 1764488 + "accountId": 12556, + "siteId": 376280, + "zoneId": 2061954 }, "sonobi": { - "TagID": "12eb86143abb80d6a9c6" - }, - "oftmedia": { - "placementId": 19606578 + "TagID": "cc1338cc8e2c6e7864dd" }, "triplelift": { "inventoryCode": "pwm_ios_hdx_pb" From afbdadb172e1b36084f4fb74901e09ede2519d97 Mon Sep 17 00:00:00 2001 From: tcreamer1 <tcreamer@playwire.com> Date: Mon, 30 Aug 2021 16:22:41 -0400 Subject: [PATCH 096/125] add test units (cherry picked from commit 15a53537d06576406496316cc25003454aeab91d) --- .../stored_imps/content_top_android_test.json | 46 +++++++++++++++++ .../stored_imps/game_over_android_test.json | 50 +++++++++++++++++++ .../by_id/stored_imps/game_over_ios_test.json | 50 +++++++++++++++++++ .../stored_imps/in_article_android_test.json | 50 +++++++++++++++++++ .../stored_imps/in_article_ios_test.json | 50 +++++++++++++++++++ .../stored_imps/play_screen_ios_test.json | 46 +++++++++++++++++ 6 files changed, 292 insertions(+) create mode 100644 stored_requests/data/by_id/stored_imps/content_top_android_test.json create mode 100644 stored_requests/data/by_id/stored_imps/game_over_android_test.json create mode 100644 stored_requests/data/by_id/stored_imps/game_over_ios_test.json create mode 100644 stored_requests/data/by_id/stored_imps/in_article_android_test.json create mode 100644 stored_requests/data/by_id/stored_imps/in_article_ios_test.json create mode 100644 stored_requests/data/by_id/stored_imps/play_screen_ios_test.json diff --git a/stored_requests/data/by_id/stored_imps/content_top_android_test.json b/stored_requests/data/by_id/stored_imps/content_top_android_test.json new file mode 100644 index 00000000000..f736e71321f --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/content_top_android_test.json @@ -0,0 +1,46 @@ +{ + "id": "content_top_android", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "appnexus": { + "placement_id": 19689655 + }, + "emx_digital": { + "tagid": "107133" + }, + "oftmedia": { + "placementId": 19606578 + }, + "openx": { + "unit": "544086382", + "delDomain": "playwire-d.openx.net" + }, + "pubmatic": { + "publisherId": "158326", + "adSlot": "3029233" + }, + "rhythmone": { + "placementId": "213696", + "zone": "1r", + "path": "mvo" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2061954 + }, + "sonobi": { + "TagID": "5200294cfc91869f267d" + }, + "triplelift": { + "inventoryCode": "playwire_app_hdx_android_prebid_TEST" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/game_over_android_test.json b/stored_requests/data/by_id/stored_imps/game_over_android_test.json new file mode 100644 index 00000000000..3f3b7831473 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/game_over_android_test.json @@ -0,0 +1,50 @@ +{ + "id": "game_over_android", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "appnexus": { + "placement_id": 19689655 + }, + "emx_digital": { + "tagid": "107133" + }, + "oftmedia": { + "placementId": 19606578 + }, + "openx": { + "unit": "544086382", + "delDomain": "playwire-d.openx.net" + }, + "pubmatic": { + "publisherId": "158326", + "adSlot": "1957467" + }, + "rhythmone": { + "placementId": "213696", + "zone": "1r", + "path": "mvo" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2061954 + }, + "sonobi": { + "TagID": "e67f243a24590037a75c" + }, + "triplelift": { + "inventoryCode": "playwire_app_hdx_android_prebid_TEST" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/game_over_ios_test.json b/stored_requests/data/by_id/stored_imps/game_over_ios_test.json new file mode 100644 index 00000000000..e860c73b1d2 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/game_over_ios_test.json @@ -0,0 +1,50 @@ +{ + "id": "game_over_ios", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "appnexus": { + "placement_id": 19689655 + }, + "emx_digital": { + "tagid": "105251" + }, + "oftmedia": { + "placementId": 19606578 + }, + "openx": { + "unit": "544086381", + "delDomain": "playwire-d.openx.net" + }, + "pubmatic": { + "publisherId": "158326", + "adSlot": "1957464" + }, + "rhythmone": { + "placementId": "213696", + "zone": "1r", + "path": "mvo" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2061954 + }, + "sonobi": { + "TagID": "12eb86143abb80d6a9c6" + }, + "triplelift": { + "inventoryCode": "playwire_app_hdx_ios_prebid_TEST" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/in_article_android_test.json b/stored_requests/data/by_id/stored_imps/in_article_android_test.json new file mode 100644 index 00000000000..44cf0a7faf4 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/in_article_android_test.json @@ -0,0 +1,50 @@ +{ + "id": "in_article_android", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "appnexus": { + "placement_id": 19689655 + }, + "emx_digital": { + "tagid": "107133" + }, + "oftmedia": { + "placementId": 19606578 + }, + "openx": { + "unit": "544086382", + "delDomain": "playwire-d.openx.net" + }, + "pubmatic": { + "publisherId": "158326", + "adSlot": "1957467" + }, + "rhythmone": { + "placementId": "213696", + "zone": "1r", + "path": "mvo" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2061954 + }, + "sonobi": { + "TagID": "e67f243a24590037a75c" + }, + "triplelift": { + "inventoryCode": "playwire_app_hdx_android_prebid_TEST" + } + } +} diff --git a/stored_requests/data/by_id/stored_imps/in_article_ios_test.json b/stored_requests/data/by_id/stored_imps/in_article_ios_test.json new file mode 100644 index 00000000000..c127a8236a0 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/in_article_ios_test.json @@ -0,0 +1,50 @@ +{ + "id": "in_article_ios", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "appnexus": { + "placement_id": 19689654 + }, + "emx_digital": { + "tagid": "105251" + }, + "oftmedia": { + "placementId": 19606578 + }, + "openx": { + "unit": "544086381", + "delDomain": "playwire-d.openx.net" + }, + "pubmatic": { + "publisherId": "158326", + "adSlot": "1957464" + }, + "rhythmone": { + "placementId": "213696", + "zone": "1r", + "path": "mvo" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2061954 + }, + "sonobi": { + "TagID": "12eb86143abb80d6a9c6" + }, + "triplelift": { + "inventoryCode": "playwire_app_hdx_ios_prebid_TEST" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/play_screen_ios_test.json b/stored_requests/data/by_id/stored_imps/play_screen_ios_test.json new file mode 100644 index 00000000000..c4a7c12b750 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/play_screen_ios_test.json @@ -0,0 +1,46 @@ +{ + "id": "play_screen_ios", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "appnexus": { + "placement_id": 19689655 + }, + "emx_digital": { + "tagid": "105251" + }, + "oftmedia": { + "placementId": 19606578 + }, + "openx": { + "unit": "544086381", + "delDomain": "playwire-d.openx.net" + }, + "pubmatic": { + "publisherId": "158326", + "adSlot": "3029490" + }, + "rhythmone": { + "placementId": "213696", + "zone": "1r", + "path": "mvo" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2061954 + }, + "sonobi": { + "TagID": "cc1338cc8e2c6e7864dd" + }, + "triplelift": { + "inventoryCode": "playwire_app_hdx_ios_prebid_TEST" + } + } +} \ No newline at end of file From 625c8b5d7ad56781dd131e0d115630f313f99391 Mon Sep 17 00:00:00 2001 From: tcreamer1 <tcream24@yahoo.com> Date: Mon, 24 Oct 2022 13:37:59 -0400 Subject: [PATCH 097/125] Merge pull request #34 from intergi/removeBidders remove emx and oft bidders (cherry picked from commit 748eb010b6d321679603dbf17a05f7446a449fdc) --- .../data/by_id/stored_imps/content_top_android.json | 8 +------- .../data/by_id/stored_imps/content_top_android_test.json | 8 +------- .../data/by_id/stored_imps/game_over_android.json | 8 +------- .../data/by_id/stored_imps/game_over_android_test.json | 8 +------- stored_requests/data/by_id/stored_imps/game_over_ios.json | 8 +------- .../data/by_id/stored_imps/game_over_ios_test.json | 8 +------- .../data/by_id/stored_imps/in_article_android.json | 6 ------ .../data/by_id/stored_imps/in_article_android_test.json | 6 ------ .../data/by_id/stored_imps/in_article_ios.json | 8 +------- .../data/by_id/stored_imps/in_article_ios_test.json | 8 +------- .../data/by_id/stored_imps/play_screen_ios.json | 8 +------- .../data/by_id/stored_imps/play_screen_ios_test.json | 8 +------- .../data/by_id/stored_requests/in_article_ios.json | 5 +---- 13 files changed, 11 insertions(+), 86 deletions(-) diff --git a/stored_requests/data/by_id/stored_imps/content_top_android.json b/stored_requests/data/by_id/stored_imps/content_top_android.json index c2b29e38b8b..354f79d988f 100644 --- a/stored_requests/data/by_id/stored_imps/content_top_android.json +++ b/stored_requests/data/by_id/stored_imps/content_top_android.json @@ -12,12 +12,6 @@ "appnexus": { "placement_id": 19689655 }, - "emx_digital": { - "tagid": "107133" - }, - "oftmedia": { - "placementId": 19606578 - }, "openx": { "unit": "544086382", "delDomain": "playwire-d.openx.net" @@ -43,4 +37,4 @@ "inventoryCode": "pwm_android_hdx_pb" } } -} \ No newline at end of file +} diff --git a/stored_requests/data/by_id/stored_imps/content_top_android_test.json b/stored_requests/data/by_id/stored_imps/content_top_android_test.json index f736e71321f..891916bad7c 100644 --- a/stored_requests/data/by_id/stored_imps/content_top_android_test.json +++ b/stored_requests/data/by_id/stored_imps/content_top_android_test.json @@ -12,12 +12,6 @@ "appnexus": { "placement_id": 19689655 }, - "emx_digital": { - "tagid": "107133" - }, - "oftmedia": { - "placementId": 19606578 - }, "openx": { "unit": "544086382", "delDomain": "playwire-d.openx.net" @@ -43,4 +37,4 @@ "inventoryCode": "playwire_app_hdx_android_prebid_TEST" } } -} \ No newline at end of file +} diff --git a/stored_requests/data/by_id/stored_imps/game_over_android.json b/stored_requests/data/by_id/stored_imps/game_over_android.json index 01237cb4348..f9bacc08f0e 100644 --- a/stored_requests/data/by_id/stored_imps/game_over_android.json +++ b/stored_requests/data/by_id/stored_imps/game_over_android.json @@ -16,12 +16,6 @@ "appnexus": { "placement_id": 19689655 }, - "emx_digital": { - "tagid": "107133" - }, - "oftmedia": { - "placementId": 19606578 - }, "openx": { "unit": "544086382", "delDomain": "playwire-d.openx.net" @@ -47,4 +41,4 @@ "inventoryCode": "pwm_android_320x50_300x250_pb" } } -} \ No newline at end of file +} diff --git a/stored_requests/data/by_id/stored_imps/game_over_android_test.json b/stored_requests/data/by_id/stored_imps/game_over_android_test.json index 3f3b7831473..45a63673913 100644 --- a/stored_requests/data/by_id/stored_imps/game_over_android_test.json +++ b/stored_requests/data/by_id/stored_imps/game_over_android_test.json @@ -16,12 +16,6 @@ "appnexus": { "placement_id": 19689655 }, - "emx_digital": { - "tagid": "107133" - }, - "oftmedia": { - "placementId": 19606578 - }, "openx": { "unit": "544086382", "delDomain": "playwire-d.openx.net" @@ -47,4 +41,4 @@ "inventoryCode": "playwire_app_hdx_android_prebid_TEST" } } -} \ No newline at end of file +} diff --git a/stored_requests/data/by_id/stored_imps/game_over_ios.json b/stored_requests/data/by_id/stored_imps/game_over_ios.json index d20dab17d39..8b9111dcefe 100644 --- a/stored_requests/data/by_id/stored_imps/game_over_ios.json +++ b/stored_requests/data/by_id/stored_imps/game_over_ios.json @@ -16,12 +16,6 @@ "appnexus": { "placement_id": 19689655 }, - "emx_digital": { - "tagid": "105251" - }, - "oftmedia": { - "placementId": 19606578 - }, "openx": { "unit": "544086381", "delDomain": "playwire-d.openx.net" @@ -47,4 +41,4 @@ "inventoryCode": "pwm_ios_320x50_300x250_pb" } } -} \ No newline at end of file +} diff --git a/stored_requests/data/by_id/stored_imps/game_over_ios_test.json b/stored_requests/data/by_id/stored_imps/game_over_ios_test.json index e860c73b1d2..78b3496a834 100644 --- a/stored_requests/data/by_id/stored_imps/game_over_ios_test.json +++ b/stored_requests/data/by_id/stored_imps/game_over_ios_test.json @@ -16,12 +16,6 @@ "appnexus": { "placement_id": 19689655 }, - "emx_digital": { - "tagid": "105251" - }, - "oftmedia": { - "placementId": 19606578 - }, "openx": { "unit": "544086381", "delDomain": "playwire-d.openx.net" @@ -47,4 +41,4 @@ "inventoryCode": "playwire_app_hdx_ios_prebid_TEST" } } -} \ No newline at end of file +} diff --git a/stored_requests/data/by_id/stored_imps/in_article_android.json b/stored_requests/data/by_id/stored_imps/in_article_android.json index a6106c6e399..6977485d867 100644 --- a/stored_requests/data/by_id/stored_imps/in_article_android.json +++ b/stored_requests/data/by_id/stored_imps/in_article_android.json @@ -16,12 +16,6 @@ "appnexus": { "placement_id": 19689655 }, - "emx_digital": { - "tagid": "107133" - }, - "oftmedia": { - "placementId": 19606578 - }, "openx": { "unit": "544086382", "delDomain": "playwire-d.openx.net" diff --git a/stored_requests/data/by_id/stored_imps/in_article_android_test.json b/stored_requests/data/by_id/stored_imps/in_article_android_test.json index 44cf0a7faf4..b4dd2a860c6 100644 --- a/stored_requests/data/by_id/stored_imps/in_article_android_test.json +++ b/stored_requests/data/by_id/stored_imps/in_article_android_test.json @@ -16,12 +16,6 @@ "appnexus": { "placement_id": 19689655 }, - "emx_digital": { - "tagid": "107133" - }, - "oftmedia": { - "placementId": 19606578 - }, "openx": { "unit": "544086382", "delDomain": "playwire-d.openx.net" diff --git a/stored_requests/data/by_id/stored_imps/in_article_ios.json b/stored_requests/data/by_id/stored_imps/in_article_ios.json index 2e6f110d14e..cd488fc1302 100644 --- a/stored_requests/data/by_id/stored_imps/in_article_ios.json +++ b/stored_requests/data/by_id/stored_imps/in_article_ios.json @@ -16,12 +16,6 @@ "appnexus": { "placement_id": 19689654 }, - "emx_digital": { - "tagid": "105251" - }, - "oftmedia": { - "placementId": 19606578 - }, "openx": { "unit": "544086381", "delDomain": "playwire-d.openx.net" @@ -47,4 +41,4 @@ "inventoryCode": "pwm_ios_320x50_300x250_pb" } } -} \ No newline at end of file +} diff --git a/stored_requests/data/by_id/stored_imps/in_article_ios_test.json b/stored_requests/data/by_id/stored_imps/in_article_ios_test.json index c127a8236a0..4545cca603e 100644 --- a/stored_requests/data/by_id/stored_imps/in_article_ios_test.json +++ b/stored_requests/data/by_id/stored_imps/in_article_ios_test.json @@ -16,12 +16,6 @@ "appnexus": { "placement_id": 19689654 }, - "emx_digital": { - "tagid": "105251" - }, - "oftmedia": { - "placementId": 19606578 - }, "openx": { "unit": "544086381", "delDomain": "playwire-d.openx.net" @@ -47,4 +41,4 @@ "inventoryCode": "playwire_app_hdx_ios_prebid_TEST" } } -} \ No newline at end of file +} diff --git a/stored_requests/data/by_id/stored_imps/play_screen_ios.json b/stored_requests/data/by_id/stored_imps/play_screen_ios.json index 047ce6ac612..60ae0da576f 100644 --- a/stored_requests/data/by_id/stored_imps/play_screen_ios.json +++ b/stored_requests/data/by_id/stored_imps/play_screen_ios.json @@ -12,12 +12,6 @@ "appnexus": { "placement_id": 19689655 }, - "emx_digital": { - "tagid": "105251" - }, - "oftmedia": { - "placementId": 19606578 - }, "openx": { "unit": "544086381", "delDomain": "playwire-d.openx.net" @@ -43,4 +37,4 @@ "inventoryCode": "pwm_ios_hdx_pb" } } -} \ No newline at end of file +} diff --git a/stored_requests/data/by_id/stored_imps/play_screen_ios_test.json b/stored_requests/data/by_id/stored_imps/play_screen_ios_test.json index c4a7c12b750..9535c49733e 100644 --- a/stored_requests/data/by_id/stored_imps/play_screen_ios_test.json +++ b/stored_requests/data/by_id/stored_imps/play_screen_ios_test.json @@ -12,12 +12,6 @@ "appnexus": { "placement_id": 19689655 }, - "emx_digital": { - "tagid": "105251" - }, - "oftmedia": { - "placementId": 19606578 - }, "openx": { "unit": "544086381", "delDomain": "playwire-d.openx.net" @@ -43,4 +37,4 @@ "inventoryCode": "playwire_app_hdx_ios_prebid_TEST" } } -} \ No newline at end of file +} diff --git a/stored_requests/data/by_id/stored_requests/in_article_ios.json b/stored_requests/data/by_id/stored_requests/in_article_ios.json index 6e0a46f4950..da0fe99555c 100644 --- a/stored_requests/data/by_id/stored_requests/in_article_ios.json +++ b/stored_requests/data/by_id/stored_requests/in_article_ios.json @@ -16,9 +16,6 @@ "appnexus": { "placement_id": 19689654 }, - "emx_digital": { - "tagid": "105251" - }, "openx": { "unit": "541169197", "delDomain": "playwire-d.openx.net" @@ -40,4 +37,4 @@ "TagID": "12eb86143abb80d6a9c6" } } -} \ No newline at end of file +} From 737778d657f6b9804f41aba569b0eb8ff0e4eca3 Mon Sep 17 00:00:00 2001 From: tcreamer1 <tcream24@yahoo.com> Date: Mon, 31 Oct 2022 15:07:03 -0400 Subject: [PATCH 098/125] Merge pull request #38 from intergi/ad-units-reconfig-new-updated-bidders New size based ad units added, updated ids for bidders (cherry picked from commit bbb207919a3566bb3a1f7147fbbaa8943f71c5c7) --- .../stored_imps/banner_300x250_android.json | 47 +++++++++++++++++++ .../by_id/stored_imps/banner_300x250_ios.json | 47 +++++++++++++++++++ .../stored_imps/banner_320x100_android.json | 47 +++++++++++++++++++ .../by_id/stored_imps/banner_320x100_ios.json | 47 +++++++++++++++++++ .../stored_imps/banner_320x50_android.json | 47 +++++++++++++++++++ .../by_id/stored_imps/banner_320x50_ios.json | 47 +++++++++++++++++++ .../stored_imps/banner_728x90_android.json | 47 +++++++++++++++++++ .../by_id/stored_imps/banner_728x90_ios.json | 47 +++++++++++++++++++ .../interstitial_1024x768_android.json | 39 +++++++++++++++ .../interstitial_1024x768_ios.json | 39 +++++++++++++++ .../interstitial_320x480_android.json | 39 +++++++++++++++ .../stored_imps/interstitial_320x480_ios.json | 39 +++++++++++++++ .../interstitial_480x320_android.json | 40 ++++++++++++++++ .../stored_imps/interstitial_480x320_ios.json | 39 +++++++++++++++ .../interstitial_480x640v_android.json | 40 ++++++++++++++++ .../interstitial_480x640v_ios.json | 39 +++++++++++++++ .../interstitial_640x480v_android.json | 39 +++++++++++++++ .../interstitial_640x480v_ios.json | 39 +++++++++++++++ .../interstitial_768x1024_android.json | 39 +++++++++++++++ .../interstitial_768x1024_ios.json | 39 +++++++++++++++ 20 files changed, 846 insertions(+) create mode 100644 stored_requests/data/by_id/stored_imps/banner_300x250_android.json create mode 100644 stored_requests/data/by_id/stored_imps/banner_300x250_ios.json create mode 100644 stored_requests/data/by_id/stored_imps/banner_320x100_android.json create mode 100644 stored_requests/data/by_id/stored_imps/banner_320x100_ios.json create mode 100644 stored_requests/data/by_id/stored_imps/banner_320x50_android.json create mode 100644 stored_requests/data/by_id/stored_imps/banner_320x50_ios.json create mode 100644 stored_requests/data/by_id/stored_imps/banner_728x90_android.json create mode 100644 stored_requests/data/by_id/stored_imps/banner_728x90_ios.json create mode 100644 stored_requests/data/by_id/stored_imps/interstitial_1024x768_android.json create mode 100644 stored_requests/data/by_id/stored_imps/interstitial_1024x768_ios.json create mode 100644 stored_requests/data/by_id/stored_imps/interstitial_320x480_android.json create mode 100644 stored_requests/data/by_id/stored_imps/interstitial_320x480_ios.json create mode 100644 stored_requests/data/by_id/stored_imps/interstitial_480x320_android.json create mode 100644 stored_requests/data/by_id/stored_imps/interstitial_480x320_ios.json create mode 100644 stored_requests/data/by_id/stored_imps/interstitial_480x640v_android.json create mode 100644 stored_requests/data/by_id/stored_imps/interstitial_480x640v_ios.json create mode 100644 stored_requests/data/by_id/stored_imps/interstitial_640x480v_android.json create mode 100644 stored_requests/data/by_id/stored_imps/interstitial_640x480v_ios.json create mode 100644 stored_requests/data/by_id/stored_imps/interstitial_768x1024_android.json create mode 100644 stored_requests/data/by_id/stored_imps/interstitial_768x1024_ios.json diff --git a/stored_requests/data/by_id/stored_imps/banner_300x250_android.json b/stored_requests/data/by_id/stored_imps/banner_300x250_android.json new file mode 100644 index 00000000000..14720534145 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/banner_300x250_android.json @@ -0,0 +1,47 @@ +{ + "id": "banner_300x250_android", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165611" + }, + "triplelift": { + "inventoryCode": "pwm_android_300x250_pb" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369430 + }, + "sonobi": { + "TagID": "1b22f1a1d1a47a68b67b" + }, + "rhythmone": { + "placementId": "247568", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "544086382", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773200 + }, + "yieldmo": { + "placementId": "2986450036092510476" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/banner_300x250_ios.json b/stored_requests/data/by_id/stored_imps/banner_300x250_ios.json new file mode 100644 index 00000000000..b8d8ff53531 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/banner_300x250_ios.json @@ -0,0 +1,47 @@ +{ + "id": "banner_300x250_ios", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165608" + }, + "triplelift": { + "inventoryCode": "pwm_ios_300x250_pb" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369428 + }, + "sonobi": { + "TagID": "bc7f500b477b40d44f67" + }, + "rhythmone": { + "placementId": "247567", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "544086381", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773199 + }, + "yieldmo": { + "placementId": "2986450035966681355" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/banner_320x100_android.json b/stored_requests/data/by_id/stored_imps/banner_320x100_android.json new file mode 100644 index 00000000000..0cedc937d0e --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/banner_320x100_android.json @@ -0,0 +1,47 @@ +{ + "id": "banner_320x100_android", + "banner": { + "format": [ + { + "w": 320, + "h": 100 + } + ] + }, + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165611" + }, + "triplelift": { + "inventoryCode": "pwm_android_hdx_pb" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369430 + }, + "sonobi": { + "TagID": "c94aa51bb23253f97ee8" + }, + "rhythmone": { + "placementId": "247568", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "544086382", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773200 + }, + "yieldmo": { + "placementId": "2986450036092510476" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/banner_320x100_ios.json b/stored_requests/data/by_id/stored_imps/banner_320x100_ios.json new file mode 100644 index 00000000000..b8703a55ab3 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/banner_320x100_ios.json @@ -0,0 +1,47 @@ +{ + "id": "banner_320x100_ios", + "banner": { + "format": [ + { + "w": 320, + "h": 100 + } + ] + }, + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165608" + }, + "triplelift": { + "inventoryCode": "pwm_ios_320x50_300x250_pb" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369428 + }, + "sonobi": { + "TagID": "589fed9631ab52cbe0dc" + }, + "rhythmone": { + "placementId": "247567", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "544086381", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773199 + }, + "yieldmo": { + "placementId": "2986450035966681355" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/banner_320x50_android.json b/stored_requests/data/by_id/stored_imps/banner_320x50_android.json new file mode 100644 index 00000000000..dd83d868d24 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/banner_320x50_android.json @@ -0,0 +1,47 @@ +{ + "id": "banner_320x50_android", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165611" + }, + "triplelift": { + "inventoryCode": "pwm_android_320x50_300x250_pb" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369430 + }, + "sonobi": { + "TagID": "5200294cfc91869f267d" + }, + "rhythmone": { + "placementId": "247568", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "544086382", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773200 + }, + "yieldmo": { + "placementId": "2986450036092510476" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/banner_320x50_ios.json b/stored_requests/data/by_id/stored_imps/banner_320x50_ios.json new file mode 100644 index 00000000000..8d7ff85c220 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/banner_320x50_ios.json @@ -0,0 +1,47 @@ +{ + "id": "banner_320x50_ios", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165608" + }, + "triplelift": { + "inventoryCode": "pwm_ios_320x50_300x250_pb" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369428 + }, + "sonobi": { + "TagID": "cc1338cc8e2c6e7864dd" + }, + "rhythmone": { + "placementId": "247567", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "544086381", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773199 + }, + "yieldmo": { + "placementId": "2986450035966681355" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/banner_728x90_android.json b/stored_requests/data/by_id/stored_imps/banner_728x90_android.json new file mode 100644 index 00000000000..516eb0f7557 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/banner_728x90_android.json @@ -0,0 +1,47 @@ +{ + "id": "banner_728x90_android", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165611" + }, + "triplelift": { + "inventoryCode": "pwm_android_320x50_300x250_pb" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369430 + }, + "sonobi": { + "TagID": "5defb02096c7ca6be2ac" + }, + "rhythmone": { + "placementId": "247568", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "544086382", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773200 + }, + "yieldmo": { + "placementId": "2986450036092510476" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/banner_728x90_ios.json b/stored_requests/data/by_id/stored_imps/banner_728x90_ios.json new file mode 100644 index 00000000000..fa98f44e0c7 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/banner_728x90_ios.json @@ -0,0 +1,47 @@ +{ + "id": "banner_728x90_ios", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165608" + }, + "triplelift": { + "inventoryCode": "pwm_ios_320x50_300x250_pb" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369428 + }, + "sonobi": { + "TagID": "b79e15de7e04b90543d3" + }, + "rhythmone": { + "placementId": "247567", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "544086381", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773199 + }, + "yieldmo": { + "placementId": "2986450035966681355" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/interstitial_1024x768_android.json b/stored_requests/data/by_id/stored_imps/interstitial_1024x768_android.json new file mode 100644 index 00000000000..c537c83cda4 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/interstitial_1024x768_android.json @@ -0,0 +1,39 @@ +{ + "id": "interstitial_1024x768_android", + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165611" + }, + "triplelift": { + "inventoryCode": "Playwire_RON_Interstitial_Android" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369430 + }, + "sonobi": { + "TagID": "e75048f43948741bad48" + }, + "rhythmone": { + "placementId": "247568", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "558236878", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773200 + }, + "yieldmo": { + "placementId": "3124076396956033378" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/interstitial_1024x768_ios.json b/stored_requests/data/by_id/stored_imps/interstitial_1024x768_ios.json new file mode 100644 index 00000000000..d2c2197d06b --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/interstitial_1024x768_ios.json @@ -0,0 +1,39 @@ +{ + "id": "interstitial_1024x768_ios", + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165608" + }, + "triplelift": { + "inventoryCode": "Playwire_RON_Interstitial_IOS" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369428 + }, + "sonobi": { + "TagID": "e94911505445cf11c143" + }, + "rhythmone": { + "placementId": "247567", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "558236877", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773199 + }, + "yieldmo": { + "placementId": "3124076397123805539" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/interstitial_320x480_android.json b/stored_requests/data/by_id/stored_imps/interstitial_320x480_android.json new file mode 100644 index 00000000000..fce6ca03e5e --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/interstitial_320x480_android.json @@ -0,0 +1,39 @@ +{ + "id": "interstitial_320x480_android", + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165611" + }, + "triplelift": { + "inventoryCode": "Playwire_RON_Interstitial_Android" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369430 + }, + "sonobi": { + "TagID": "e75048f43948741bad48" + }, + "rhythmone": { + "placementId": "247568", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "558236878", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773200 + }, + "yieldmo": { + "placementId": "3124076396956033378" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/interstitial_320x480_ios.json b/stored_requests/data/by_id/stored_imps/interstitial_320x480_ios.json new file mode 100644 index 00000000000..4e2c80b58ba --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/interstitial_320x480_ios.json @@ -0,0 +1,39 @@ +{ + "id": "interstitial_320x480_ios", + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165608" + }, + "triplelift": { + "inventoryCode": "Playwire_RON_Interstitial_IOS" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369428 + }, + "sonobi": { + "TagID": "e94911505445cf11c143" + }, + "rhythmone": { + "placementId": "247567", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "558236877", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773199 + }, + "yieldmo": { + "placementId": "3124076397123805539" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/interstitial_480x320_android.json b/stored_requests/data/by_id/stored_imps/interstitial_480x320_android.json new file mode 100644 index 00000000000..95b38c83f0a --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/interstitial_480x320_android.json @@ -0,0 +1,40 @@ +{ + "id": "interstitial_480x320_android", + "interstitial": {}, + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165611" + }, + "triplelift": { + "inventoryCode": "Playwire_RON_Interstitial_Android" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369430 + }, + "sonobi": { + "TagID": "e75048f43948741bad48" + }, + "rhythmone": { + "placementId": "247568", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "558236878", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773200 + }, + "yieldmo": { + "placementId": "3124076396956033378" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/interstitial_480x320_ios.json b/stored_requests/data/by_id/stored_imps/interstitial_480x320_ios.json new file mode 100644 index 00000000000..08208df1fbe --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/interstitial_480x320_ios.json @@ -0,0 +1,39 @@ +{ + "id": "interstitial_480x320_ios", + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165608" + }, + "triplelift": { + "inventoryCode": "Playwire_RON_Interstitial_IOS" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369428 + }, + "sonobi": { + "TagID": "e94911505445cf11c143" + }, + "rhythmone": { + "placementId": "247567", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "558236877", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773199 + }, + "yieldmo": { + "placementId": "3124076397123805539" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/interstitial_480x640v_android.json b/stored_requests/data/by_id/stored_imps/interstitial_480x640v_android.json new file mode 100644 index 00000000000..f644bc07bdb --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/interstitial_480x640v_android.json @@ -0,0 +1,40 @@ +{ + "id": "interstitial_480x640v_android", + "interstitial": {}, + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165611" + }, + "triplelift": { + "inventoryCode": "Playwire_RON_Interstitial_Android" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369430 + }, + "sonobi": { + "TagID": "e75048f43948741bad48" + }, + "rhythmone": { + "placementId": "247568", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "558236878", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773200 + }, + "yieldmo": { + "placementId": "3124076396956033378" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/interstitial_480x640v_ios.json b/stored_requests/data/by_id/stored_imps/interstitial_480x640v_ios.json new file mode 100644 index 00000000000..d9c44258297 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/interstitial_480x640v_ios.json @@ -0,0 +1,39 @@ +{ + "id": "interstitial_480x640v_ios", + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165608" + }, + "triplelift": { + "inventoryCode": "Playwire_RON_Interstitial_IOS" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369428 + }, + "sonobi": { + "TagID": "e94911505445cf11c143" + }, + "rhythmone": { + "placementId": "247567", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "558236877", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773199 + }, + "yieldmo": { + "placementId": "3124076397123805539" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/interstitial_640x480v_android.json b/stored_requests/data/by_id/stored_imps/interstitial_640x480v_android.json new file mode 100644 index 00000000000..17cfeb67154 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/interstitial_640x480v_android.json @@ -0,0 +1,39 @@ +{ + "id": "interstitial_640x480v_android", + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165611" + }, + "triplelift": { + "inventoryCode": "Playwire_RON_Interstitial_Android" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369430 + }, + "sonobi": { + "TagID": "e75048f43948741bad48" + }, + "rhythmone": { + "placementId": "247568", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "558236878", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773200 + }, + "yieldmo": { + "placementId": "3124076396956033378" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/interstitial_640x480v_ios.json b/stored_requests/data/by_id/stored_imps/interstitial_640x480v_ios.json new file mode 100644 index 00000000000..00d54e76304 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/interstitial_640x480v_ios.json @@ -0,0 +1,39 @@ +{ + "id": "interstitial_640x480v_ios", + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165608" + }, + "triplelift": { + "inventoryCode": "Playwire_RON_Interstitial_IOS" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369428 + }, + "sonobi": { + "TagID": "e94911505445cf11c143" + }, + "rhythmone": { + "placementId": "247567", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "558236877", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773199 + }, + "yieldmo": { + "placementId": "3124076397123805539" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/interstitial_768x1024_android.json b/stored_requests/data/by_id/stored_imps/interstitial_768x1024_android.json new file mode 100644 index 00000000000..22366ee3d0e --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/interstitial_768x1024_android.json @@ -0,0 +1,39 @@ +{ + "id": "interstitial_768x1024_android", + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165611" + }, + "triplelift": { + "inventoryCode": "Playwire_RON_Interstitial_Android" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369430 + }, + "sonobi": { + "TagID": "e75048f43948741bad48" + }, + "rhythmone": { + "placementId": "247568", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "558236878", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773200 + }, + "yieldmo": { + "placementId": "3124076396956033378" + } + } +} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/interstitial_768x1024_ios.json b/stored_requests/data/by_id/stored_imps/interstitial_768x1024_ios.json new file mode 100644 index 00000000000..9a792dde409 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/interstitial_768x1024_ios.json @@ -0,0 +1,39 @@ +{ + "id": "interstitial_768x1024_ios", + "ext": { + "pubmatic": { + "publisherId": "158326", + "adSlot": "2165608" + }, + "triplelift": { + "inventoryCode": "Playwire_RON_Interstitial_IOS" + }, + "between": { + "host": "lbs-us-east1.ads", + "publisher_id": "44634" + }, + "rubicon": { + "accountId": 12556, + "siteId": 376280, + "zoneId": 2369428 + }, + "sonobi": { + "TagID": "e94911505445cf11c143" + }, + "rhythmone": { + "placementId": "247567", + "zone": "1r", + "path": "mvo" + }, + "openx": { + "unit": "558236877", + "delDomain": "playwire-d.openx.net" + }, + "appnexus": { + "placement_id": 24773199 + }, + "yieldmo": { + "placementId": "3124076397123805539" + } + } +} \ No newline at end of file From 52fcc7e4f5e33ae0a49363bd1a4c3cc69bc02db8 Mon Sep 17 00:00:00 2001 From: tcreamer <tcreamer@playwire.com> Date: Thu, 5 Jan 2023 12:44:36 -0500 Subject: [PATCH 099/125] update heroku go version (cherry picked from commit dc955fb8c67f63e991be192c93a91174c3ad2624) --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 9c214c538b4..556c6659666 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/prebid/prebid-server go 1.19 // Magic comment that determines which Go version Heroku uses. -// +heroku goVersion go1.16 +// +heroku goVersion go1.19 require ( github.com/DATA-DOG/go-sqlmock v1.5.0 From 717333e733c7d78d48a436ee4185fc967ba4ea62 Mon Sep 17 00:00:00 2001 From: tcreamer1 <tcream24@yahoo.com> Date: Wed, 23 Jun 2021 20:48:47 -0400 Subject: [PATCH 100/125] Merge pull request #1 from tcreamer1/playwireAdapter whitelabeling grid names for 0.236.0 (cherry picked from commit b2b8a356f4c9f9d24b5138b932398b7968a379c9) --- adapters/playwire/playwire.go | 185 ++++++++++++++++++ adapters/playwire/playwire_test.go | 20 ++ .../playwiretest/exemplary/simple-banner.json | 87 ++++++++ .../playwiretest/exemplary/simple-video.json | 87 ++++++++ .../playwiretest/exemplary/with_gpid.json | 100 ++++++++++ .../playwiretest/params/race/banner.json | 4 + .../supplemental/bad_bidder_request.json | 33 ++++ .../supplemental/bad_ext_request.json | 30 +++ .../supplemental/bad_response.json | 63 ++++++ .../supplemental/empty_uid_request.json | 33 ++++ .../supplemental/no_imp_request.json | 13 ++ .../playwiretest/supplemental/status_204.json | 58 ++++++ .../playwiretest/supplemental/status_400.json | 63 ++++++ .../playwiretest/supplemental/status_418.json | 63 ++++++ adapters/playwire/usersync.go | 12 ++ adapters/playwire/usersync_test.go | 29 +++ exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_playwire.go | 6 + static/bidder-info/playwire.yaml | 12 ++ static/bidder-params/playwire.json | 13 ++ 21 files changed, 915 insertions(+) create mode 100644 adapters/playwire/playwire.go create mode 100644 adapters/playwire/playwire_test.go create mode 100644 adapters/playwire/playwiretest/exemplary/simple-banner.json create mode 100644 adapters/playwire/playwiretest/exemplary/simple-video.json create mode 100644 adapters/playwire/playwiretest/exemplary/with_gpid.json create mode 100644 adapters/playwire/playwiretest/params/race/banner.json create mode 100644 adapters/playwire/playwiretest/supplemental/bad_bidder_request.json create mode 100644 adapters/playwire/playwiretest/supplemental/bad_ext_request.json create mode 100644 adapters/playwire/playwiretest/supplemental/bad_response.json create mode 100644 adapters/playwire/playwiretest/supplemental/empty_uid_request.json create mode 100644 adapters/playwire/playwiretest/supplemental/no_imp_request.json create mode 100644 adapters/playwire/playwiretest/supplemental/status_204.json create mode 100644 adapters/playwire/playwiretest/supplemental/status_400.json create mode 100644 adapters/playwire/playwiretest/supplemental/status_418.json create mode 100644 adapters/playwire/usersync.go create mode 100644 adapters/playwire/usersync_test.go create mode 100644 openrtb_ext/imp_playwire.go create mode 100644 static/bidder-info/playwire.yaml create mode 100644 static/bidder-params/playwire.json diff --git a/adapters/playwire/playwire.go b/adapters/playwire/playwire.go new file mode 100644 index 00000000000..f0ec7817da2 --- /dev/null +++ b/adapters/playwire/playwire.go @@ -0,0 +1,185 @@ +package playwire + +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 PlaywireAdapter struct { + endpoint string +} + +type ExtImpDataAdServer struct { + Name string `json:"name"` + AdSlot string `json:"adslot"` +} + +type ExtImpData struct { + PbAdslot string `json:"pbadslot,omitempty"` + AdServer *ExtImpDataAdServer `json:"adserver,omitempty"` +} + +type ExtImp struct { + Prebid *openrtb_ext.ExtImpPrebid `json:"prebid,omitempty"` + Bidder json.RawMessage `json:"bidder"` + Data *ExtImpData `json:"data,omitempty"` + Gpid string `json:"gpid,omitempty"` +} + +func processImp(imp *openrtb2.Imp) error { + // get the playwire extension + var ext adapters.ExtImpBidder + var playwireExt openrtb_ext.ExtImpPlaywire + if err := json.Unmarshal(imp.Ext, &ext); err != nil { + return err + } + if err := json.Unmarshal(ext.Bidder, &playwireExt); err != nil { + return err + } + + if playwireExt.Uid == 0 { + err := &errortypes.BadInput{ + Message: "uid is empty", + } + return err + } + // no error + return nil +} + +func setImpExtData(imp openrtb2.Imp) openrtb2.Imp { + var ext ExtImp + if err := json.Unmarshal(imp.Ext, &ext); err != nil { + return imp + } + if ext.Data != nil && ext.Data.AdServer != nil && ext.Data.AdServer.AdSlot != "" { + ext.Gpid = ext.Data.AdServer.AdSlot + extJSON, err := json.Marshal(ext) + if err == nil { + imp.Ext = extJSON + } + } + return imp +} + +// MakeRequests makes the HTTP requests which should be made to fetch bids. +func (a *PlaywireAdapter) 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 { + if err := processImp(&imp); err == nil { + validImps = append(validImps, setImpExtData(imp)) + } else { + errors = append(errors, err) + } + } + if len(validImps) == 0 { + err := &errortypes.BadInput{ + Message: "No valid impressions for playwire", + } + errors = append(errors, err) + return nil, errors + } + requestCopy.Imp = validImps + + reqJSON, err := json.Marshal(requestCopy) + if err != nil { + errors = append(errors, err) + return nil, errors + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + + return []*adapters.RequestData{{ + Method: "POST", + Uri: a.endpoint, + Body: reqJSON, + Headers: headers, + }}, errors +} + +// MakeBids unpacks the server's response into Bids. +func (a *PlaywireAdapter) 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 bidResp openrtb2.BidResponse + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) + + for _, sb := range bidResp.SeatBid { + for i := range sb.Bid { + bidType, err := getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp) + if err != nil { + return nil, []error{err} + } + + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &sb.Bid[i], + BidType: bidType, + }) + } + } + return bidResponse, nil + +} + +// Builder builds a new instance of the Playwire adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &PlaywireAdapter{ + endpoint: config.Endpoint, + } + return bidder, 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 + } + + if imp.Video != nil { + return openrtb_ext.BidTypeVideo, nil + } + + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unknown impression type for ID: \"%s\"", impID), + } + } + } + + // This shouldnt happen. Lets handle it just incase by returning an error. + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Failed to find impression for ID: \"%s\"", impID), + } +} diff --git a/adapters/playwire/playwire_test.go b/adapters/playwire/playwire_test.go new file mode 100644 index 00000000000..e2f6f1990d6 --- /dev/null +++ b/adapters/playwire/playwire_test.go @@ -0,0 +1,20 @@ +package playwire + +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.BidderPlaywire, config.Adapter{ + Endpoint: "http://localhost/prebid"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "playwiretest", bidder) +} diff --git a/adapters/playwire/playwiretest/exemplary/simple-banner.json b/adapters/playwire/playwiretest/exemplary/simple-banner.json new file mode 100644 index 00000000000..a1603fd4b6c --- /dev/null +++ b/adapters/playwire/playwiretest/exemplary/simple-banner.json @@ -0,0 +1,87 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "uid": 1 + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "uid": 1 + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "seat": "playwire", + "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/playwire/playwiretest/exemplary/simple-video.json b/adapters/playwire/playwiretest/exemplary/simple-video.json new file mode 100644 index 00000000000..fc15013a8c0 --- /dev/null +++ b/adapters/playwire/playwiretest/exemplary/simple-video.json @@ -0,0 +1,87 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "uid": 1 + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "uid": 1 + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "seat": "playwire", + "bid": [{ + "id": "randomid", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "12345678", + "adm": "some-test-ad-vast", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300 + }] + }], + "cur": "USD" + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "randomid", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad-vast", + "adid": "12345678", + "cid": "987", + "crid": "12345678", + "w": 300, + "h": 250 + }, + "type": "video" + }] + }] +} diff --git a/adapters/playwire/playwiretest/exemplary/with_gpid.json b/adapters/playwire/playwiretest/exemplary/with_gpid.json new file mode 100644 index 00000000000..386f827cf99 --- /dev/null +++ b/adapters/playwire/playwiretest/exemplary/with_gpid.json @@ -0,0 +1,100 @@ +{ + "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 + }, + "data": { + "adserver": { + "name": "some_name", + "adslot": "some_slot" + } + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "uid": 1 + }, + "data": { + "adserver": { + "name": "some_name", + "adslot": "some_slot" + } + }, + "gpid": "some_slot" + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "seat": "playwire", + "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/playwire/playwiretest/params/race/banner.json b/adapters/playwire/playwiretest/params/race/banner.json new file mode 100644 index 00000000000..7e347f11b45 --- /dev/null +++ b/adapters/playwire/playwiretest/params/race/banner.json @@ -0,0 +1,4 @@ +{ + "uid": 1 +} + diff --git a/adapters/playwire/playwiretest/supplemental/bad_bidder_request.json b/adapters/playwire/playwiretest/supplemental/bad_bidder_request.json new file mode 100644 index 00000000000..cb9c9333070 --- /dev/null +++ b/adapters/playwire/playwiretest/supplemental/bad_bidder_request.json @@ -0,0 +1,33 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": "some not exist" + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb_ext.ExtImpPlaywire", + "comparison": "literal" + }, + { + "value": "No valid impressions for playwire", + "comparison": "literal" + } + ], + "httpCalls":[], + "expectedBidResponses": [] +} diff --git a/adapters/playwire/playwiretest/supplemental/bad_ext_request.json b/adapters/playwire/playwiretest/supplemental/bad_ext_request.json new file mode 100644 index 00000000000..76cce111948 --- /dev/null +++ b/adapters/playwire/playwiretest/supplemental/bad_ext_request.json @@ -0,0 +1,30 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": "any" + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type adapters.ExtImpBidder", + "comparison": "literal" + }, + { + "value": "No valid impressions for playwire", + "comparison": "literal" + } + ], + "httpCalls": [] +} diff --git a/adapters/playwire/playwiretest/supplemental/bad_response.json b/adapters/playwire/playwiretest/supplemental/bad_response.json new file mode 100644 index 00000000000..a9d38368ab2 --- /dev/null +++ b/adapters/playwire/playwiretest/supplemental/bad_response.json @@ -0,0 +1,63 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "uid": 1 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "uid": 1 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": "{\"id\"data.lost" + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "comparison": "literal" + } + ] +} diff --git a/adapters/playwire/playwiretest/supplemental/empty_uid_request.json b/adapters/playwire/playwiretest/supplemental/empty_uid_request.json new file mode 100644 index 00000000000..e6b242942e9 --- /dev/null +++ b/adapters/playwire/playwiretest/supplemental/empty_uid_request.json @@ -0,0 +1,33 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": {} + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "uid is empty", + "comparison": "literal" + }, + { + "value": "No valid impressions for playwire", + "comparison": "literal" + } + ], + "httpCalls":[], + "expectedBidResponses": [] +} diff --git a/adapters/playwire/playwiretest/supplemental/no_imp_request.json b/adapters/playwire/playwiretest/supplemental/no_imp_request.json new file mode 100644 index 00000000000..c12686f7328 --- /dev/null +++ b/adapters/playwire/playwiretest/supplemental/no_imp_request.json @@ -0,0 +1,13 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [] + }, + "expectedMakeRequestsErrors": [ + { + "value": "No valid impressions for playwire", + "comparison": "literal" + } + ], + "httpCalls":[] +} diff --git a/adapters/playwire/playwiretest/supplemental/status_204.json b/adapters/playwire/playwiretest/supplemental/status_204.json new file mode 100644 index 00000000000..f935cbe85ae --- /dev/null +++ b/adapters/playwire/playwiretest/supplemental/status_204.json @@ -0,0 +1,58 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "uid": 1 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "uid": 1 + } + } + } + ] + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + + "expectedBidResponses": [] +} diff --git a/adapters/playwire/playwiretest/supplemental/status_400.json b/adapters/playwire/playwiretest/supplemental/status_400.json new file mode 100644 index 00000000000..629b1c07bd7 --- /dev/null +++ b/adapters/playwire/playwiretest/supplemental/status_400.json @@ -0,0 +1,63 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "uid": 1 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "uid": 1 + } + } + } + ] + } + }, + "mockResponse": { + "status": 400, + "body": {} + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/playwire/playwiretest/supplemental/status_418.json b/adapters/playwire/playwiretest/supplemental/status_418.json new file mode 100644 index 00000000000..0ca365c76ce --- /dev/null +++ b/adapters/playwire/playwiretest/supplemental/status_418.json @@ -0,0 +1,63 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "uid": 1 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "uid": 1 + } + } + } + ] + } + }, + "mockResponse": { + "status": 418, + "body": {} + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 418. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/playwire/usersync.go b/adapters/playwire/usersync.go new file mode 100644 index 00000000000..76cf9dc0329 --- /dev/null +++ b/adapters/playwire/usersync.go @@ -0,0 +1,12 @@ +package playwire + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewPlaywireSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("playwire", temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/playwire/usersync_test.go b/adapters/playwire/usersync_test.go new file mode 100644 index 00000000000..3006daf0b76 --- /dev/null +++ b/adapters/playwire/usersync_test.go @@ -0,0 +1,29 @@ +package playwire + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestPlaywireSyncer(t *testing.T) { + syncURL := "//not_localhost/synclocalhost%2Fsetuid%3Fbidder%3Dplaywire%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewPlaywireSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "0", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "//not_localhost/synclocalhost%2Fsetuid%3Fbidder%3Dplaywire%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/exchange/adapter_builders.go b/exchange/adapter_builders.go index 5ceaae1cdb8..265b82cff9a 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -111,6 +111,7 @@ import ( "github.com/prebid/prebid-server/adapters/orbidder" "github.com/prebid/prebid-server/adapters/outbrain" "github.com/prebid/prebid-server/adapters/pangle" + "github.com/prebid/prebid-server/adapters/playwire" "github.com/prebid/prebid-server/adapters/pubmatic" "github.com/prebid/prebid-server/adapters/pubnative" "github.com/prebid/prebid-server/adapters/pulsepoint" @@ -278,6 +279,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderOutbrain: outbrain.Builder, openrtb_ext.BidderPangle: pangle.Builder, openrtb_ext.BidderPGAM: adtelligent.Builder, + openrtb_ext.BidderPlaywire: playwire.Builder, openrtb_ext.BidderPubmatic: pubmatic.Builder, openrtb_ext.BidderPubnative: pubnative.Builder, openrtb_ext.BidderPulsepoint: pulsepoint.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index c5de0576718..7d0437788eb 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -197,6 +197,7 @@ const ( BidderOutbrain BidderName = "outbrain" BidderPangle BidderName = "pangle" BidderPGAM BidderName = "pgam" + BidderPlaywire BidderName = "playwire" BidderPubmatic BidderName = "pubmatic" BidderPubnative BidderName = "pubnative" BidderPulsepoint BidderName = "pulsepoint" @@ -367,6 +368,7 @@ func CoreBidderNames() []BidderName { BidderOutbrain, BidderPangle, BidderPGAM, + BidderPlaywire, BidderPubmatic, BidderPubnative, BidderPulsepoint, diff --git a/openrtb_ext/imp_playwire.go b/openrtb_ext/imp_playwire.go new file mode 100644 index 00000000000..4c1e6b36268 --- /dev/null +++ b/openrtb_ext/imp_playwire.go @@ -0,0 +1,6 @@ +package openrtb_ext + +// ExtImpPlaywire defines the contract for bidrequest.imp[i].ext.playwire +type ExtImpPlaywire struct { + Uid int `json:"uid"` +} diff --git a/static/bidder-info/playwire.yaml b/static/bidder-info/playwire.yaml new file mode 100644 index 00000000000..bdf5a83b27c --- /dev/null +++ b/static/bidder-info/playwire.yaml @@ -0,0 +1,12 @@ +maintainer: + email: "na@playwire.com" +gvlVendorID: 686 +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video \ No newline at end of file diff --git a/static/bidder-params/playwire.json b/static/bidder-params/playwire.json new file mode 100644 index 00000000000..dd4b9be09a7 --- /dev/null +++ b/static/bidder-params/playwire.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Playwire Adapter Params", + "description": "A schema which validates params accepted by the Playwire adapter", + "type": "object", + "properties": { + "uid": { + "type": "integer", + "description": "An ID which identifies this placement of the impression" + } + }, + "required": [] +} From 083393559ec3a0e3e0ced08d48d8b56f6a745a45 Mon Sep 17 00:00:00 2001 From: tcreamer1 <tcreamer@playwire.com> Date: Tue, 17 Nov 2020 16:28:42 -0500 Subject: [PATCH 101/125] Merge pull request #17 from intergi/revert-16-revert-13-ortb_bidders Re-open ortb playwire adapter PR for 0.236.0 (cherry picked from commit f45f9b8be4ff2d3e78334f60af2ff268422e064c) --- adapters/playwire_ortb/params_test.go | 52 ++++++ adapters/playwire_ortb/playwire_ortb.go | 172 ++++++++++++++++++ adapters/playwire_ortb/playwire_ortb_test.go | 10 + .../playwire_ortbtest/exemplary/banner.json | 99 ++++++++++ .../playwire_ortbtest/params/race/banner.json | 3 + adapters/playwire_ortb/usersync.go | 12 ++ adapters/playwire_ortb/usersync_test.go | 35 ++++ static/bidder-info/engagebdr_ortb.yaml | 7 + static/bidder-info/gumgum_ortb.yaml | 7 + static/bidder-info/pulsepoint_ortb.yaml | 7 + static/bidder-params/engagebdr_ortb.json | 9 + static/bidder-params/gumgum_ortb.json | 9 + static/bidder-params/pulsepoint_ortb.json | 9 + 13 files changed, 431 insertions(+) create mode 100644 adapters/playwire_ortb/params_test.go create mode 100644 adapters/playwire_ortb/playwire_ortb.go create mode 100644 adapters/playwire_ortb/playwire_ortb_test.go create mode 100644 adapters/playwire_ortb/playwire_ortbtest/exemplary/banner.json create mode 100644 adapters/playwire_ortb/playwire_ortbtest/params/race/banner.json create mode 100644 adapters/playwire_ortb/usersync.go create mode 100644 adapters/playwire_ortb/usersync_test.go create mode 100644 static/bidder-info/engagebdr_ortb.yaml create mode 100644 static/bidder-info/gumgum_ortb.yaml create mode 100644 static/bidder-info/pulsepoint_ortb.yaml create mode 100644 static/bidder-params/engagebdr_ortb.json create mode 100644 static/bidder-params/gumgum_ortb.json create mode 100644 static/bidder-params/pulsepoint_ortb.json diff --git a/adapters/playwire_ortb/params_test.go b/adapters/playwire_ortb/params_test.go new file mode 100644 index 00000000000..4adfafbdc7f --- /dev/null +++ b/adapters/playwire_ortb/params_test.go @@ -0,0 +1,52 @@ +package playwire_ortb + +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.BidderGumGum, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected gumgum 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{ + `{"zone":"dc9d6be1"}`, +} + +var invalidParams = []string{ + `null`, + `nil`, + ``, + `{}`, + `[]`, + `true`, + `2`, + `{"zone":12345678}`, + `{"zone":""}`, + `{"placementId": 1}`, + `{"zone": true}`, + `{"placementId": 1, "zone":"1234567"}`, +} diff --git a/adapters/playwire_ortb/playwire_ortb.go b/adapters/playwire_ortb/playwire_ortb.go new file mode 100644 index 00000000000..befb679cc34 --- /dev/null +++ b/adapters/playwire_ortb/playwire_ortb.go @@ -0,0 +1,172 @@ +package playwire_ortb + +import ( + "encoding/json" + "fmt" + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" + "net/http" + "strconv" + "strings" +) + +// PlaywireORTBAdapter implements Bidder interface. +type PlaywireORTBAdapter struct { + URI string +} + +// MakeRequests makes the HTTP requests which should be made to fetch bids. +func (g *PlaywireORTBAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var validImps []openrtb.Imp + var trackingId string + + numRequests := len(request.Imp) + errs := make([]error, 0, numRequests) + + for i := 0; i < numRequests; i++ { + imp := request.Imp[i] + zone, err := preprocess(&imp) + if err != nil { + errs = append(errs, err) + } else if request.Imp[i].Banner != nil { + bannerCopy := *request.Imp[i].Banner + if bannerCopy.W == nil && bannerCopy.H == nil && len(bannerCopy.Format) > 0 { + format := bannerCopy.Format[0] + bannerCopy.W = &(format.W) + bannerCopy.H = &(format.H) + } + request.Imp[i].Banner = &bannerCopy + validImps = append(validImps, request.Imp[i]) + trackingId = zone + } else if request.Imp[i].Video != nil { + err := validateVideoParams(request.Imp[i].Video) + if err != nil { + errs = append(errs, err) + } else { + validImps = append(validImps, request.Imp[i]) + trackingId = zone + } + } + } + + if len(validImps) == 0 { + return nil, errs + } + + request.Imp = validImps + + if request.Site != nil { + siteCopy := *request.Site + siteCopy.ID = trackingId + request.Site = &siteCopy + } + + reqJSON, err := json.Marshal(request) + if err != nil { + errs = append(errs, err) + return nil, errs + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + return []*adapters.RequestData{{ + Method: "POST", + Uri: g.URI, + Body: reqJSON, + Headers: headers, + }}, errs +} + +// MakeBids unpacks the server's response into Bids. +func (g *PlaywireORTBAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + + if response.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Bad user input: HTTP status %d", response.StatusCode), + }} + } + + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Bad server response: HTTP status %d", response.StatusCode), + }} + } + var bidResp openrtb.BidResponse + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Bad server response: %d. ", err), + }} + } + + var errs []error + bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) + + for _, sb := range bidResp.SeatBid { + for i := range sb.Bid { + mediaType := getMediaTypeForImpID(sb.Bid[i].ImpID, internalRequest.Imp) + if mediaType == openrtb_ext.BidTypeVideo { + price := strconv.FormatFloat(sb.Bid[i].Price, 'f', -1, 64) + sb.Bid[i].AdM = strings.Replace(sb.Bid[i].AdM, "${AUCTION_PRICE}", price, -1) + } + + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &sb.Bid[i], + BidType: mediaType, + }) + } + } + + return bidResponse, errs +} + +func preprocess(imp *openrtb.Imp) (string, error) { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + err = &errortypes.BadInput{ + Message: err.Error(), + } + return "", err + } + + var gumgumExt openrtb_ext.ExtImpGumGum + if err := json.Unmarshal(bidderExt.Bidder, &gumgumExt); err != nil { + err = &errortypes.BadInput{ + Message: err.Error(), + } + return "", err + } + + zone := gumgumExt.Zone + return zone, nil +} + +func getMediaTypeForImpID(impID string, imps []openrtb.Imp) openrtb_ext.BidType { + for _, imp := range imps { + if imp.ID == impID && imp.Banner != nil { + return openrtb_ext.BidTypeBanner + } + } + return openrtb_ext.BidTypeVideo +} + +func validateVideoParams(video *openrtb.Video) (err error) { + // if video.W == 0 || video.H == 0 || video.MinDuration == 0 || video.MaxDuration == 0 || video.Placement == 0 || video.Linearity == 0 { + // return &errortypes.BadInput{ + // Message: "Invalid or missing video field(s)", + // } + // } + return nil +} + +// NewOrtbBidder configures bidder endpoint. +func NewOrtbBidder(endpoint string) *PlaywireORTBAdapter { + return &PlaywireORTBAdapter{ + URI: endpoint, + } +} \ No newline at end of file diff --git a/adapters/playwire_ortb/playwire_ortb_test.go b/adapters/playwire_ortb/playwire_ortb_test.go new file mode 100644 index 00000000000..6a45a92c22b --- /dev/null +++ b/adapters/playwire_ortb/playwire_ortb_test.go @@ -0,0 +1,10 @@ +package playwire_ortb + +import ( + "github.com/prebid/prebid-server/adapters/adapterstest" + "testing" +) + +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "gumgumtest", NewOrtbBidder("https://g2.gumgum.com/providers/prbds2s/bid")) +} diff --git a/adapters/playwire_ortb/playwire_ortbtest/exemplary/banner.json b/adapters/playwire_ortb/playwire_ortbtest/exemplary/banner.json new file mode 100644 index 00000000000..2fbd3da22da --- /dev/null +++ b/adapters/playwire_ortb/playwire_ortbtest/exemplary/banner.json @@ -0,0 +1,99 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 300 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "zone": "dc9d6be1" + } + } + } + ] + }, + + "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": { + "bidder": { + "zone": "dc9d6be1" + } + } + }] + } + }, + "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" + } + ] + } + ] +} diff --git a/adapters/playwire_ortb/playwire_ortbtest/params/race/banner.json b/adapters/playwire_ortb/playwire_ortbtest/params/race/banner.json new file mode 100644 index 00000000000..6e222304f36 --- /dev/null +++ b/adapters/playwire_ortb/playwire_ortbtest/params/race/banner.json @@ -0,0 +1,3 @@ +{ + "zone": "dc9d6be1" +} diff --git a/adapters/playwire_ortb/usersync.go b/adapters/playwire_ortb/usersync.go new file mode 100644 index 00000000000..5c9b1463e16 --- /dev/null +++ b/adapters/playwire_ortb/usersync.go @@ -0,0 +1,12 @@ +package playwire_ortb + +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", 61, temp, adapters.SyncTypeIframe) +} diff --git a/adapters/playwire_ortb/usersync_test.go b/adapters/playwire_ortb/usersync_test.go new file mode 100644 index 00000000000..89e1c06ca13 --- /dev/null +++ b/adapters/playwire_ortb/usersync_test.go @@ -0,0 +1,35 @@ +package playwire_ortb + +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{ + Value: "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.EqualValues(t, 61, syncer.GDPRVendorID()) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/static/bidder-info/engagebdr_ortb.yaml b/static/bidder-info/engagebdr_ortb.yaml new file mode 100644 index 00000000000..764b49023df --- /dev/null +++ b/static/bidder-info/engagebdr_ortb.yaml @@ -0,0 +1,7 @@ +maintainer: + email: "pubtech@na.com" +capabilities: + site: + mediaTypes: + - banner + - video diff --git a/static/bidder-info/gumgum_ortb.yaml b/static/bidder-info/gumgum_ortb.yaml new file mode 100644 index 00000000000..764b49023df --- /dev/null +++ b/static/bidder-info/gumgum_ortb.yaml @@ -0,0 +1,7 @@ +maintainer: + email: "pubtech@na.com" +capabilities: + site: + mediaTypes: + - banner + - video diff --git a/static/bidder-info/pulsepoint_ortb.yaml b/static/bidder-info/pulsepoint_ortb.yaml new file mode 100644 index 00000000000..764b49023df --- /dev/null +++ b/static/bidder-info/pulsepoint_ortb.yaml @@ -0,0 +1,7 @@ +maintainer: + email: "pubtech@na.com" +capabilities: + site: + mediaTypes: + - banner + - video diff --git a/static/bidder-params/engagebdr_ortb.json b/static/bidder-params/engagebdr_ortb.json new file mode 100644 index 00000000000..acc2d5c221b --- /dev/null +++ b/static/bidder-params/engagebdr_ortb.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "GumGum Adapter Params", + "description": "A schema which validates params accepted by the GumGum adapter", + "type": "object", + "properties": { + + } +} diff --git a/static/bidder-params/gumgum_ortb.json b/static/bidder-params/gumgum_ortb.json new file mode 100644 index 00000000000..acc2d5c221b --- /dev/null +++ b/static/bidder-params/gumgum_ortb.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "GumGum Adapter Params", + "description": "A schema which validates params accepted by the GumGum adapter", + "type": "object", + "properties": { + + } +} diff --git a/static/bidder-params/pulsepoint_ortb.json b/static/bidder-params/pulsepoint_ortb.json new file mode 100644 index 00000000000..acc2d5c221b --- /dev/null +++ b/static/bidder-params/pulsepoint_ortb.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "GumGum Adapter Params", + "description": "A schema which validates params accepted by the GumGum adapter", + "type": "object", + "properties": { + + } +} From 6c6667b58abd6f42b7da0b3ee74a3ff3d242a927 Mon Sep 17 00:00:00 2001 From: tcreamer1 <tcreamer@playwire.com> Date: Tue, 17 Nov 2020 16:29:18 -0500 Subject: [PATCH 102/125] Merge pull request #18 from intergi/ortb_bidders update go mod to read adapter import (cherry picked from commit 2c96c945403fb05753ba7c87e71e3392f6cc9a9e) --- adapters/playwire_ortb/go.mod | 1 + go.mod | 4 ++++ 2 files changed, 5 insertions(+) create mode 100644 adapters/playwire_ortb/go.mod diff --git a/adapters/playwire_ortb/go.mod b/adapters/playwire_ortb/go.mod new file mode 100644 index 00000000000..c96523beac1 --- /dev/null +++ b/adapters/playwire_ortb/go.mod @@ -0,0 +1 @@ +module github.com/prebid-server/adapters/playwire_ortb \ No newline at end of file diff --git a/go.mod b/go.mod index 556c6659666..1e03c68b762 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,8 @@ go 1.19 // Magic comment that determines which Go version Heroku uses. // +heroku goVersion go1.19 +replace github.com/prebid-server/adapters/playwire_ortb => ./adapters/playwire_ortb/ + require ( github.com/DATA-DOG/go-sqlmock v1.5.0 github.com/IABTechLab/adscert v0.34.0 @@ -21,6 +23,8 @@ require ( github.com/julienschmidt/httprouter v1.3.0 github.com/lib/pq v1.10.4 github.com/mitchellh/copystructure v1.2.0 + github.com/mxmCherry/openrtb v11.0.0+incompatible + github.com/prebid-server/adapters/playwire_ortb v0.0.0-00010101000000-000000000000 github.com/prebid/go-gdpr v1.11.0 github.com/prebid/go-gpp v0.1.1 github.com/prebid/openrtb/v17 v17.1.0 From 0d72f18d130bc699b6fca274f481171ac8d2e1e4 Mon Sep 17 00:00:00 2001 From: tcreamer1 <tcream24@yahoo.com> Date: Wed, 26 Oct 2022 14:35:28 -0400 Subject: [PATCH 103/125] Merge pull request #36 from intergi/fix/tests Fix/tests and setup for PlaywireOrtb (cherry picked from commit f3edcd7f3f3c13d064de8e07bb68fefca86a8ae1) --- exchange/adapter_builders.go | 1 + openrtb_ext/bidders.go | 2 ++ openrtb_ext/bidders_validate_test.go | 2 +- static/bidder-info/playwire_ortb.yaml | 12 ++++++++++++ static/bidder-params/playwire_ortb.json | 13 +++++++++++++ 5 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 static/bidder-info/playwire_ortb.yaml create mode 100644 static/bidder-params/playwire_ortb.json diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 265b82cff9a..1aa7ccae036 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -280,6 +280,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderPangle: pangle.Builder, openrtb_ext.BidderPGAM: adtelligent.Builder, openrtb_ext.BidderPlaywire: playwire.Builder, + openrtb_ext.BidderPlaywireOrtb: playwire_ortb.Builder, openrtb_ext.BidderPubmatic: pubmatic.Builder, openrtb_ext.BidderPubnative: pubnative.Builder, openrtb_ext.BidderPulsepoint: pulsepoint.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 7d0437788eb..001e284d0b3 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -198,6 +198,7 @@ const ( BidderPangle BidderName = "pangle" BidderPGAM BidderName = "pgam" BidderPlaywire BidderName = "playwire" + BidderPlaywireOrtb BidderName = "playwire_ortb" BidderPubmatic BidderName = "pubmatic" BidderPubnative BidderName = "pubnative" BidderPulsepoint BidderName = "pulsepoint" @@ -369,6 +370,7 @@ func CoreBidderNames() []BidderName { BidderPangle, BidderPGAM, BidderPlaywire, + BidderPlaywireOrtb, BidderPubmatic, BidderPubnative, BidderPulsepoint, diff --git a/openrtb_ext/bidders_validate_test.go b/openrtb_ext/bidders_validate_test.go index 530f260c761..8a8f9936147 100644 --- a/openrtb_ext/bidders_validate_test.go +++ b/openrtb_ext/bidders_validate_test.go @@ -55,7 +55,7 @@ func TestBidderUniquenessGatekeeping(t *testing.T) { } } - currentThreshold := 6 + currentThreshold := 11 measuredThreshold := minUniquePrefixLength(bidders) assert.NotZero(t, measuredThreshold, "BidderMap contains duplicate bidder name values.") diff --git a/static/bidder-info/playwire_ortb.yaml b/static/bidder-info/playwire_ortb.yaml new file mode 100644 index 00000000000..602f7d38b78 --- /dev/null +++ b/static/bidder-info/playwire_ortb.yaml @@ -0,0 +1,12 @@ +maintainer: + email: "na@playwire.com" +gvlVendorID: 686 +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video diff --git a/static/bidder-params/playwire_ortb.json b/static/bidder-params/playwire_ortb.json new file mode 100644 index 00000000000..dd4b9be09a7 --- /dev/null +++ b/static/bidder-params/playwire_ortb.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Playwire Adapter Params", + "description": "A schema which validates params accepted by the Playwire adapter", + "type": "object", + "properties": { + "uid": { + "type": "integer", + "description": "An ID which identifies this placement of the impression" + } + }, + "required": [] +} From 93239c299e33921f9ec990653406218641d2a599 Mon Sep 17 00:00:00 2001 From: tcreamer1 <tcream24@yahoo.com> Date: Thu, 5 Jan 2023 12:39:50 -0500 Subject: [PATCH 104/125] Merge pull request #39 from intergi/updgrade/0.236.0 Bringing more files into parity with master for 0.236.0 (cherry picked from commit 13a70811409d581b5226ca287ad7244b24b87f09) --- adapters/playwire/playwire.go | 4 +- adapters/playwire/usersync_test.go | 29 -- adapters/playwire_ortb/params_test.go | 73 ++-- adapters/playwire_ortb/playwire_ortb.go | 330 ++++++++++--------- adapters/playwire_ortb/playwire_ortb_test.go | 9 +- adapters/playwire_ortb/usersync.go | 12 - adapters/playwire_ortb/usersync_test.go | 35 -- exchange/adapter_builders.go | 4 + go.mod | 2 +- go.sum | 2 + openrtb_ext/bidders.go | 6 + static/bidder-info/engagebdr_ortb.yaml | 15 +- static/bidder-info/gumgum_ortb.yaml | 15 +- static/bidder-info/playwire.yaml | 10 +- static/bidder-info/playwire_ortb.yaml | 2 +- static/bidder-info/pulsepoint_ortb.yaml | 15 +- static/bidder-params/engagebdr_ortb.json | 10 +- static/bidder-params/gumgum_ortb.json | 10 +- static/bidder-params/playwire.json | 14 +- static/bidder-params/pulsepoint_ortb.json | 10 +- 20 files changed, 316 insertions(+), 291 deletions(-) delete mode 100644 adapters/playwire/usersync_test.go delete mode 100644 adapters/playwire_ortb/usersync.go delete mode 100644 adapters/playwire_ortb/usersync_test.go diff --git a/adapters/playwire/playwire.go b/adapters/playwire/playwire.go index f0ec7817da2..c14d6cb1e2d 100644 --- a/adapters/playwire/playwire.go +++ b/adapters/playwire/playwire.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/openrtb/v17/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -154,7 +154,7 @@ func (a *PlaywireAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa } // Builder builds a new instance of the Playwire adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &PlaywireAdapter{ endpoint: config.Endpoint, } diff --git a/adapters/playwire/usersync_test.go b/adapters/playwire/usersync_test.go deleted file mode 100644 index 3006daf0b76..00000000000 --- a/adapters/playwire/usersync_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package playwire - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/stretchr/testify/assert" -) - -func TestPlaywireSyncer(t *testing.T) { - syncURL := "//not_localhost/synclocalhost%2Fsetuid%3Fbidder%3Dplaywire%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewPlaywireSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "0", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "//not_localhost/synclocalhost%2Fsetuid%3Fbidder%3Dplaywire%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/playwire_ortb/params_test.go b/adapters/playwire_ortb/params_test.go index 4adfafbdc7f..48cab6c73ee 100644 --- a/adapters/playwire_ortb/params_test.go +++ b/adapters/playwire_ortb/params_test.go @@ -1,52 +1,53 @@ package playwire_ortb import ( - "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" - "testing" + "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.BidderGumGum, json.RawMessage(validParam)); err != nil { - t.Errorf("Schema rejected gumgum params: %s", validParam) - } - } + 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.BidderGumGum, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected gumgum 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) - } - } + 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{ - `{"zone":"dc9d6be1"}`, + `{"zone":"dc9d6be1"}`, } var invalidParams = []string{ - `null`, - `nil`, - ``, - `{}`, - `[]`, - `true`, - `2`, - `{"zone":12345678}`, - `{"zone":""}`, - `{"placementId": 1}`, - `{"zone": true}`, - `{"placementId": 1, "zone":"1234567"}`, + `null`, + `nil`, + ``, + `{}`, + `[]`, + `true`, + `2`, + `{"zone":12345678}`, + `{"zone":""}`, + `{"placementId": 1}`, + `{"zone": true}`, + `{"placementId": 1, "zone":"1234567"}`, } diff --git a/adapters/playwire_ortb/playwire_ortb.go b/adapters/playwire_ortb/playwire_ortb.go index befb679cc34..63d9578aeae 100644 --- a/adapters/playwire_ortb/playwire_ortb.go +++ b/adapters/playwire_ortb/playwire_ortb.go @@ -1,172 +1,196 @@ package playwire_ortb import ( - "encoding/json" - "fmt" - "github.com/mxmCherry/openrtb" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - "net/http" - "strconv" - "strings" + "encoding/json" + "fmt" + "net/http" + "strconv" + "strings" + + "github.com/prebid/openrtb/v17/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" ) -// PlaywireORTBAdapter implements Bidder interface. -type PlaywireORTBAdapter struct { - URI string +// PlaywireOrtbAdapter implements Bidder interface. +type PlaywireOrtbAdapter struct { + URI string } // MakeRequests makes the HTTP requests which should be made to fetch bids. -func (g *PlaywireORTBAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - var validImps []openrtb.Imp - var trackingId string - - numRequests := len(request.Imp) - errs := make([]error, 0, numRequests) - - for i := 0; i < numRequests; i++ { - imp := request.Imp[i] - zone, err := preprocess(&imp) - if err != nil { - errs = append(errs, err) - } else if request.Imp[i].Banner != nil { - bannerCopy := *request.Imp[i].Banner - if bannerCopy.W == nil && bannerCopy.H == nil && len(bannerCopy.Format) > 0 { - format := bannerCopy.Format[0] - bannerCopy.W = &(format.W) - bannerCopy.H = &(format.H) - } - request.Imp[i].Banner = &bannerCopy - validImps = append(validImps, request.Imp[i]) - trackingId = zone - } else if request.Imp[i].Video != nil { - err := validateVideoParams(request.Imp[i].Video) - if err != nil { - errs = append(errs, err) - } else { - validImps = append(validImps, request.Imp[i]) - trackingId = zone - } - } - } - - if len(validImps) == 0 { - return nil, errs - } - - request.Imp = validImps - - if request.Site != nil { - siteCopy := *request.Site - siteCopy.ID = trackingId - request.Site = &siteCopy - } - - reqJSON, err := json.Marshal(request) - if err != nil { - errs = append(errs, err) - return nil, errs - } - - headers := http.Header{} - headers.Add("Content-Type", "application/json;charset=utf-8") - headers.Add("Accept", "application/json") - return []*adapters.RequestData{{ - Method: "POST", - Uri: g.URI, - Body: reqJSON, - Headers: headers, - }}, errs +func (g *PlaywireOrtbAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var validImps []openrtb2.Imp + var siteCopy openrtb2.Site + if request.Site != nil { + siteCopy = *request.Site + } + + numRequests := len(request.Imp) + errs := make([]error, 0, numRequests) + + for i := 0; i < numRequests; i++ { + imp := request.Imp[i] + playwire_ortbExt, err := preprocess(&imp) + if err != nil { + errs = append(errs, err) + } else { + if playwire_ortbExt.Zone != "" { + siteCopy.ID = playwire_ortbExt.Zone + } + + if playwire_ortbExt.PubID != 0 { + if siteCopy.Publisher != nil { + siteCopy.Publisher.ID = strconv.FormatFloat(playwire_ortbExt.PubID, 'f', -1, 64) + } else { + siteCopy.Publisher = &openrtb2.Publisher{ID: strconv.FormatFloat(playwire_ortbExt.PubID, 'f', -1, 64)} + } + } + + validImps = append(validImps, imp) + } + } + + if len(validImps) == 0 { + return nil, errs + } + + request.Imp = validImps + + if request.Site != nil { + request.Site = &siteCopy + } + + reqJSON, err := json.Marshal(request) + if err != nil { + errs = append(errs, err) + return nil, errs + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + return []*adapters.RequestData{{ + Method: "POST", + Uri: g.URI, + Body: reqJSON, + Headers: headers, + }}, errs } // MakeBids unpacks the server's response into Bids. -func (g *PlaywireORTBAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { - if response.StatusCode == http.StatusNoContent { - return nil, nil - } - - if response.StatusCode == http.StatusBadRequest { - return nil, []error{&errortypes.BadInput{ - Message: fmt.Sprintf("Bad user input: HTTP status %d", response.StatusCode), - }} - } - - if response.StatusCode != http.StatusOK { - return nil, []error{&errortypes.BadServerResponse{ - Message: fmt.Sprintf("Bad server response: HTTP status %d", response.StatusCode), - }} - } - var bidResp openrtb.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { - return nil, []error{&errortypes.BadServerResponse{ - Message: fmt.Sprintf("Bad server response: %d. ", err), - }} - } - - var errs []error - bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) - - for _, sb := range bidResp.SeatBid { - for i := range sb.Bid { - mediaType := getMediaTypeForImpID(sb.Bid[i].ImpID, internalRequest.Imp) - if mediaType == openrtb_ext.BidTypeVideo { - price := strconv.FormatFloat(sb.Bid[i].Price, 'f', -1, 64) - sb.Bid[i].AdM = strings.Replace(sb.Bid[i].AdM, "${AUCTION_PRICE}", price, -1) - } - - bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ - Bid: &sb.Bid[i], - BidType: mediaType, - }) - } - } - - return bidResponse, errs +func (g *PlaywireOrtbAdapter) 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", response.StatusCode), + }} + } + + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Bad server response: HTTP status %d", 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: %d. ", err), + }} + } + + var errs []error + bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) + + for _, sb := range bidResp.SeatBid { + for i := range sb.Bid { + mediaType := getMediaTypeForImpID(sb.Bid[i].ImpID, internalRequest.Imp) + if mediaType == openrtb_ext.BidTypeVideo { + price := strconv.FormatFloat(sb.Bid[i].Price, 'f', -1, 64) + sb.Bid[i].AdM = strings.Replace(sb.Bid[i].AdM, "${AUCTION_PRICE}", price, -1) + } + + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &sb.Bid[i], + BidType: mediaType, + }) + } + } + + return bidResponse, errs } -func preprocess(imp *openrtb.Imp) (string, error) { - var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { - err = &errortypes.BadInput{ - Message: err.Error(), - } - return "", err - } - - var gumgumExt openrtb_ext.ExtImpGumGum - if err := json.Unmarshal(bidderExt.Bidder, &gumgumExt); err != nil { - err = &errortypes.BadInput{ - Message: err.Error(), - } - return "", err - } - - zone := gumgumExt.Zone - return zone, nil +func preprocess(imp *openrtb2.Imp) (*openrtb_ext.ExtImpGumGum, error) { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + err = &errortypes.BadInput{ + Message: err.Error(), + } + return nil, err + } + + var playwire_ortbExt openrtb_ext.ExtImpGumGum + if err := json.Unmarshal(bidderExt.Bidder, &playwire_ortbExt); err != nil { + err = &errortypes.BadInput{ + Message: err.Error(), + } + return nil, err + } + + if imp.Banner != nil && imp.Banner.W == nil && imp.Banner.H == nil && len(imp.Banner.Format) > 0 { + bannerCopy := *imp.Banner + format := bannerCopy.Format[0] + bannerCopy.W = &(format.W) + bannerCopy.H = &(format.H) + imp.Banner = &bannerCopy + } + + if imp.Video != nil { + err := validateVideoParams(imp.Video) + if err != nil { + return nil, err + } + + if playwire_ortbExt.IrisID != "" { + videoCopy := *imp.Video + videoExt := openrtb_ext.ExtImpGumGumVideo{IrisID: playwire_ortbExt.IrisID} + videoCopy.Ext, err = json.Marshal(&videoExt) + if err != nil { + return nil, err + } + imp.Video = &videoCopy + } + } + + return &playwire_ortbExt, nil } -func getMediaTypeForImpID(impID string, imps []openrtb.Imp) openrtb_ext.BidType { - for _, imp := range imps { - if imp.ID == impID && imp.Banner != nil { - return openrtb_ext.BidTypeBanner - } - } - return openrtb_ext.BidTypeVideo +func getMediaTypeForImpID(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { + for _, imp := range imps { + if imp.ID == impID && imp.Banner != nil { + return openrtb_ext.BidTypeBanner + } + } + return openrtb_ext.BidTypeVideo } -func validateVideoParams(video *openrtb.Video) (err error) { - // if video.W == 0 || video.H == 0 || video.MinDuration == 0 || video.MaxDuration == 0 || video.Placement == 0 || video.Linearity == 0 { - // return &errortypes.BadInput{ - // Message: "Invalid or missing video field(s)", - // } - // } - return nil +func validateVideoParams(video *openrtb2.Video) (err error) { + if video.W == 0 || video.H == 0 || video.MinDuration == 0 || video.MaxDuration == 0 || video.Placement == 0 || video.Linearity == 0 { + return &errortypes.BadInput{ + Message: "Invalid or missing video field(s)", + } + } + return nil } -// NewOrtbBidder configures bidder endpoint. -func NewOrtbBidder(endpoint string) *PlaywireORTBAdapter { - return &PlaywireORTBAdapter{ - URI: endpoint, - } -} \ No newline at end of file +// Builder builds a new instance of the PlaywireOrtb adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + bidder := &PlaywireOrtbAdapter{ + URI: config.Endpoint, + } + return bidder, nil +} diff --git a/adapters/playwire_ortb/playwire_ortb_test.go b/adapters/playwire_ortb/playwire_ortb_test.go index 6a45a92c22b..e68c31cb4a5 100644 --- a/adapters/playwire_ortb/playwire_ortb_test.go +++ b/adapters/playwire_ortb/playwire_ortb_test.go @@ -6,5 +6,12 @@ import ( ) func TestJsonSamples(t *testing.T) { - adapterstest.RunJSONBidderTest(t, "gumgumtest", NewOrtbBidder("https://g2.gumgum.com/providers/prbds2s/bid")) + bidder, buildErr := Builder(openrtb_ext.BidderGumGum, config.Adapter{ + Endpoint: "https://g2.gumgum.com/providers/prbds2s/bid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "gumgumtest", bidder) } diff --git a/adapters/playwire_ortb/usersync.go b/adapters/playwire_ortb/usersync.go deleted file mode 100644 index 5c9b1463e16..00000000000 --- a/adapters/playwire_ortb/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package playwire_ortb - -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", 61, temp, adapters.SyncTypeIframe) -} diff --git a/adapters/playwire_ortb/usersync_test.go b/adapters/playwire_ortb/usersync_test.go deleted file mode 100644 index 89e1c06ca13..00000000000 --- a/adapters/playwire_ortb/usersync_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package playwire_ortb - -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{ - Value: "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.EqualValues(t, 61, syncer.GDPRVendorID()) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 1aa7ccae036..3669e0cac7c 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -1,6 +1,7 @@ package exchange import ( + "github.com/prebid-server/adapters/playwire_ortb" "github.com/prebid/prebid-server/adapters" ttx "github.com/prebid/prebid-server/adapters/33across" "github.com/prebid/prebid-server/adapters/aax" @@ -230,6 +231,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderDmx: dmx.Builder, openrtb_ext.BidderEmxDigital: emx_digital.Builder, openrtb_ext.BidderEngageBDR: engagebdr.Builder, + openrtb_ext.BidderEngageBDROrtb: playwire_ortb.Builder, openrtb_ext.BidderEPlanning: eplanning.Builder, openrtb_ext.BidderEpom: epom.Builder, openrtb_ext.BidderEVolution: evolution.Builder, @@ -240,6 +242,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderGrid: grid.Builder, openrtb_ext.BidderGroupm: pubmatic.Builder, openrtb_ext.BidderGumGum: gumgum.Builder, + openrtb_ext.BidderGumGumOrtb: playwire_ortb.Builder, openrtb_ext.BidderHuaweiAds: huaweiads.Builder, openrtb_ext.BidderImpactify: impactify.Builder, openrtb_ext.BidderImprovedigital: improvedigital.Builder, @@ -284,6 +287,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderPubmatic: pubmatic.Builder, openrtb_ext.BidderPubnative: pubnative.Builder, openrtb_ext.BidderPulsepoint: pulsepoint.Builder, + openrtb_ext.BidderPulsepointOrtb: playwire_ortb.Builder, openrtb_ext.BidderQuantumdex: apacdex.Builder, openrtb_ext.BidderRevcontent: revcontent.Builder, openrtb_ext.BidderRhythmone: rhythmone.Builder, diff --git a/go.mod b/go.mod index 1e03c68b762..2566111bf2e 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/julienschmidt/httprouter v1.3.0 github.com/lib/pq v1.10.4 github.com/mitchellh/copystructure v1.2.0 - github.com/mxmCherry/openrtb v11.0.0+incompatible + github.com/mxmCherry/openrtb/v15 v15.0.1 github.com/prebid-server/adapters/playwire_ortb v0.0.0-00010101000000-000000000000 github.com/prebid/go-gdpr v1.11.0 github.com/prebid/go-gpp v0.1.1 diff --git a/go.sum b/go.sum index 30f3017ba7f..56926b400ac 100644 --- a/go.sum +++ b/go.sum @@ -382,6 +382,8 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 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.1 h1:RnMj/n8Sv0wqaiWQCbmUwzeO6T4Bk3d9RokKvla1CDc= +github.com/mxmCherry/openrtb/v15 v15.0.1/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= diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 001e284d0b3..2fde9c5b96e 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -148,6 +148,7 @@ const ( BidderDmx BidderName = "dmx" BidderEmxDigital BidderName = "emx_digital" BidderEngageBDR BidderName = "engagebdr" + BidderEngageBDROrtb BidderName = "engagebdr_ortb" BidderEPlanning BidderName = "eplanning" BidderEpom BidderName = "epom" BidderEVolution BidderName = "e_volution" @@ -158,6 +159,7 @@ const ( BidderGrid BidderName = "grid" BidderGroupm BidderName = "groupm" BidderGumGum BidderName = "gumgum" + BidderGumGumOrtb BidderName = "gumgum_ortb" BidderHuaweiAds BidderName = "huaweiads" BidderImpactify BidderName = "impactify" BidderImprovedigital BidderName = "improvedigital" @@ -202,6 +204,7 @@ const ( BidderPubmatic BidderName = "pubmatic" BidderPubnative BidderName = "pubnative" BidderPulsepoint BidderName = "pulsepoint" + BidderPulsepointOrtb BidderName = "pulsepoint_ortb" BidderQuantumdex BidderName = "quantumdex" BidderRevcontent BidderName = "revcontent" BidderRhythmone BidderName = "rhythmone" @@ -320,6 +323,7 @@ func CoreBidderNames() []BidderName { BidderDmx, BidderEmxDigital, BidderEngageBDR, + BidderEngageBDROrtb, BidderEPlanning, BidderEpom, BidderEVolution, @@ -330,6 +334,7 @@ func CoreBidderNames() []BidderName { BidderGrid, BidderGroupm, BidderGumGum, + BidderGumGumOrtb, BidderHuaweiAds, BidderImpactify, BidderImprovedigital, @@ -374,6 +379,7 @@ func CoreBidderNames() []BidderName { BidderPubmatic, BidderPubnative, BidderPulsepoint, + BidderPulsepointOrtb, BidderQuantumdex, BidderRevcontent, BidderRhythmone, diff --git a/static/bidder-info/engagebdr_ortb.yaml b/static/bidder-info/engagebdr_ortb.yaml index 764b49023df..fa6e5bffb6d 100644 --- a/static/bidder-info/engagebdr_ortb.yaml +++ b/static/bidder-info/engagebdr_ortb.yaml @@ -1,7 +1,16 @@ +endpoint: "https://dsp.bnmla.com/bid?sspid=1000204" maintainer: - email: "pubtech@na.com" + email: "na@playwire.com" capabilities: + app: + mediaTypes: + - banner + - video + - audio + - native site: mediaTypes: - - banner - - video + - banner + - video + - audio + - native diff --git a/static/bidder-info/gumgum_ortb.yaml b/static/bidder-info/gumgum_ortb.yaml index 764b49023df..05b26f3f1b4 100644 --- a/static/bidder-info/gumgum_ortb.yaml +++ b/static/bidder-info/gumgum_ortb.yaml @@ -1,7 +1,16 @@ +endpoint: "https://g2.gumgum.com/zones/8ylgv2wd/bid" maintainer: - email: "pubtech@na.com" + email: "na@playwire.com" capabilities: + app: + mediaTypes: + - banner + - video + - audio + - native site: mediaTypes: - - banner - - video + - banner + - video + - audio + - native diff --git a/static/bidder-info/playwire.yaml b/static/bidder-info/playwire.yaml index bdf5a83b27c..2ea70768a56 100644 --- a/static/bidder-info/playwire.yaml +++ b/static/bidder-info/playwire.yaml @@ -1,12 +1,18 @@ +endpoint: "https://grid.bidswitch.net/sp_bid?sp=prebid" maintainer: email: "na@playwire.com" -gvlVendorID: 686 capabilities: app: mediaTypes: - banner - video + - native site: mediaTypes: - banner - - video \ No newline at end of file + - video + - native +userSync: + redirect: + url: "https://x.bidswitch.net/check_uuid/{{.RedirectURL}}?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}" + userMacro: "${BSW_UUID}" \ No newline at end of file diff --git a/static/bidder-info/playwire_ortb.yaml b/static/bidder-info/playwire_ortb.yaml index 602f7d38b78..8c25833a376 100644 --- a/static/bidder-info/playwire_ortb.yaml +++ b/static/bidder-info/playwire_ortb.yaml @@ -1,6 +1,6 @@ +endpoint: "https://grid.bidswitch.net/sp_bid?sp=prebid" maintainer: email: "na@playwire.com" -gvlVendorID: 686 capabilities: app: mediaTypes: diff --git a/static/bidder-info/pulsepoint_ortb.yaml b/static/bidder-info/pulsepoint_ortb.yaml index 764b49023df..3d28d0394b9 100644 --- a/static/bidder-info/pulsepoint_ortb.yaml +++ b/static/bidder-info/pulsepoint_ortb.yaml @@ -1,7 +1,16 @@ +endpoint: "http://rts.lga.contextweb.com/rt-seller/bid/openrtb/playwire" maintainer: - email: "pubtech@na.com" + email: "na@playwire.com" capabilities: + app: + mediaTypes: + - banner + - video + - audio + - native site: mediaTypes: - - banner - - video + - banner + - video + - audio + - native diff --git a/static/bidder-params/engagebdr_ortb.json b/static/bidder-params/engagebdr_ortb.json index acc2d5c221b..8849dc474dc 100644 --- a/static/bidder-params/engagebdr_ortb.json +++ b/static/bidder-params/engagebdr_ortb.json @@ -1,9 +1,13 @@ { "$schema": "http://json-schema.org/draft-04/schema#", - "title": "GumGum Adapter Params", - "description": "A schema which validates params accepted by the GumGum adapter", + "title": "Playwire ORTB Adapter Params", + "description": "A schema which validates params accepted by the Playwire ORTB adapter", "type": "object", "properties": { - + "sspid": { + "type": "string", + "description": "SSPID parameter", + "pattern": "^[0-9]+$" + } } } diff --git a/static/bidder-params/gumgum_ortb.json b/static/bidder-params/gumgum_ortb.json index acc2d5c221b..8849dc474dc 100644 --- a/static/bidder-params/gumgum_ortb.json +++ b/static/bidder-params/gumgum_ortb.json @@ -1,9 +1,13 @@ { "$schema": "http://json-schema.org/draft-04/schema#", - "title": "GumGum Adapter Params", - "description": "A schema which validates params accepted by the GumGum adapter", + "title": "Playwire ORTB Adapter Params", + "description": "A schema which validates params accepted by the Playwire ORTB adapter", "type": "object", "properties": { - + "sspid": { + "type": "string", + "description": "SSPID parameter", + "pattern": "^[0-9]+$" + } } } diff --git a/static/bidder-params/playwire.json b/static/bidder-params/playwire.json index dd4b9be09a7..83fcbe0adbf 100644 --- a/static/bidder-params/playwire.json +++ b/static/bidder-params/playwire.json @@ -7,7 +7,19 @@ "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": [] -} +} \ No newline at end of file diff --git a/static/bidder-params/pulsepoint_ortb.json b/static/bidder-params/pulsepoint_ortb.json index acc2d5c221b..8849dc474dc 100644 --- a/static/bidder-params/pulsepoint_ortb.json +++ b/static/bidder-params/pulsepoint_ortb.json @@ -1,9 +1,13 @@ { "$schema": "http://json-schema.org/draft-04/schema#", - "title": "GumGum Adapter Params", - "description": "A schema which validates params accepted by the GumGum adapter", + "title": "Playwire ORTB Adapter Params", + "description": "A schema which validates params accepted by the Playwire ORTB adapter", "type": "object", "properties": { - + "sspid": { + "type": "string", + "description": "SSPID parameter", + "pattern": "^[0-9]+$" + } } } From e8bd4feb8a5e88f58490935109e7506e78ff2dba Mon Sep 17 00:00:00 2001 From: tcreamer1 <tcream24@yahoo.com> Date: Thu, 5 Jan 2023 15:05:09 -0500 Subject: [PATCH 105/125] Merge pull request #41 from intergi/fix/playwireAdapter Replace playwire adapter with grid (cherry picked from commit b838015e98db92743cf5f53df6c3039514e6442d) --- adapters/playwire/playwire.go | 185 ------------------ adapters/playwire/playwire_test.go | 20 -- .../playwiretest/exemplary/simple-banner.json | 87 -------- .../playwiretest/exemplary/simple-video.json | 87 -------- .../playwiretest/exemplary/with_gpid.json | 100 ---------- .../playwiretest/params/race/banner.json | 4 - .../supplemental/bad_bidder_request.json | 33 ---- .../supplemental/bad_ext_request.json | 30 --- .../supplemental/bad_response.json | 63 ------ .../supplemental/empty_uid_request.json | 33 ---- .../supplemental/no_imp_request.json | 13 -- .../playwiretest/supplemental/status_204.json | 58 ------ .../playwiretest/supplemental/status_400.json | 63 ------ .../playwiretest/supplemental/status_418.json | 63 ------ adapters/playwire/usersync.go | 12 -- exchange/adapter_builders.go | 3 +- 16 files changed, 1 insertion(+), 853 deletions(-) delete mode 100644 adapters/playwire/playwire.go delete mode 100644 adapters/playwire/playwire_test.go delete mode 100644 adapters/playwire/playwiretest/exemplary/simple-banner.json delete mode 100644 adapters/playwire/playwiretest/exemplary/simple-video.json delete mode 100644 adapters/playwire/playwiretest/exemplary/with_gpid.json delete mode 100644 adapters/playwire/playwiretest/params/race/banner.json delete mode 100644 adapters/playwire/playwiretest/supplemental/bad_bidder_request.json delete mode 100644 adapters/playwire/playwiretest/supplemental/bad_ext_request.json delete mode 100644 adapters/playwire/playwiretest/supplemental/bad_response.json delete mode 100644 adapters/playwire/playwiretest/supplemental/empty_uid_request.json delete mode 100644 adapters/playwire/playwiretest/supplemental/no_imp_request.json delete mode 100644 adapters/playwire/playwiretest/supplemental/status_204.json delete mode 100644 adapters/playwire/playwiretest/supplemental/status_400.json delete mode 100644 adapters/playwire/playwiretest/supplemental/status_418.json delete mode 100644 adapters/playwire/usersync.go diff --git a/adapters/playwire/playwire.go b/adapters/playwire/playwire.go deleted file mode 100644 index c14d6cb1e2d..00000000000 --- a/adapters/playwire/playwire.go +++ /dev/null @@ -1,185 +0,0 @@ -package playwire - -import ( - "encoding/json" - "fmt" - "net/http" - - "github.com/prebid/openrtb/v17/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 PlaywireAdapter struct { - endpoint string -} - -type ExtImpDataAdServer struct { - Name string `json:"name"` - AdSlot string `json:"adslot"` -} - -type ExtImpData struct { - PbAdslot string `json:"pbadslot,omitempty"` - AdServer *ExtImpDataAdServer `json:"adserver,omitempty"` -} - -type ExtImp struct { - Prebid *openrtb_ext.ExtImpPrebid `json:"prebid,omitempty"` - Bidder json.RawMessage `json:"bidder"` - Data *ExtImpData `json:"data,omitempty"` - Gpid string `json:"gpid,omitempty"` -} - -func processImp(imp *openrtb2.Imp) error { - // get the playwire extension - var ext adapters.ExtImpBidder - var playwireExt openrtb_ext.ExtImpPlaywire - if err := json.Unmarshal(imp.Ext, &ext); err != nil { - return err - } - if err := json.Unmarshal(ext.Bidder, &playwireExt); err != nil { - return err - } - - if playwireExt.Uid == 0 { - err := &errortypes.BadInput{ - Message: "uid is empty", - } - return err - } - // no error - return nil -} - -func setImpExtData(imp openrtb2.Imp) openrtb2.Imp { - var ext ExtImp - if err := json.Unmarshal(imp.Ext, &ext); err != nil { - return imp - } - if ext.Data != nil && ext.Data.AdServer != nil && ext.Data.AdServer.AdSlot != "" { - ext.Gpid = ext.Data.AdServer.AdSlot - extJSON, err := json.Marshal(ext) - if err == nil { - imp.Ext = extJSON - } - } - return imp -} - -// MakeRequests makes the HTTP requests which should be made to fetch bids. -func (a *PlaywireAdapter) 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 { - if err := processImp(&imp); err == nil { - validImps = append(validImps, setImpExtData(imp)) - } else { - errors = append(errors, err) - } - } - if len(validImps) == 0 { - err := &errortypes.BadInput{ - Message: "No valid impressions for playwire", - } - errors = append(errors, err) - return nil, errors - } - requestCopy.Imp = validImps - - reqJSON, err := json.Marshal(requestCopy) - if err != nil { - errors = append(errors, err) - return nil, errors - } - - headers := http.Header{} - headers.Add("Content-Type", "application/json;charset=utf-8") - - return []*adapters.RequestData{{ - Method: "POST", - Uri: a.endpoint, - Body: reqJSON, - Headers: headers, - }}, errors -} - -// MakeBids unpacks the server's response into Bids. -func (a *PlaywireAdapter) 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 bidResp openrtb2.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { - return nil, []error{err} - } - - bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) - - for _, sb := range bidResp.SeatBid { - for i := range sb.Bid { - bidType, err := getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp) - if err != nil { - return nil, []error{err} - } - - bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ - Bid: &sb.Bid[i], - BidType: bidType, - }) - } - } - return bidResponse, nil - -} - -// Builder builds a new instance of the Playwire adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { - bidder := &PlaywireAdapter{ - endpoint: config.Endpoint, - } - return bidder, 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 - } - - if imp.Video != nil { - return openrtb_ext.BidTypeVideo, nil - } - - return "", &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unknown impression type for ID: \"%s\"", impID), - } - } - } - - // This shouldnt happen. Lets handle it just incase by returning an error. - return "", &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Failed to find impression for ID: \"%s\"", impID), - } -} diff --git a/adapters/playwire/playwire_test.go b/adapters/playwire/playwire_test.go deleted file mode 100644 index e2f6f1990d6..00000000000 --- a/adapters/playwire/playwire_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package playwire - -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.BidderPlaywire, config.Adapter{ - Endpoint: "http://localhost/prebid"}) - - if buildErr != nil { - t.Fatalf("Builder returned unexpected error %v", buildErr) - } - - adapterstest.RunJSONBidderTest(t, "playwiretest", bidder) -} diff --git a/adapters/playwire/playwiretest/exemplary/simple-banner.json b/adapters/playwire/playwiretest/exemplary/simple-banner.json deleted file mode 100644 index a1603fd4b6c..00000000000 --- a/adapters/playwire/playwiretest/exemplary/simple-banner.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "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 - } - } - }] - }, - - "httpCalls": [{ - "expectedRequest": { - "uri": "http://localhost/prebid", - "body": { - "id": "test-request-id", - "site": { - "page": "https://good.site/url" - }, - "imp": [{ - "id": "test-imp-id", - "banner": { - "format": [{ - "w": 300, - "h": 250 - }] - }, - "ext": { - "bidder": { - "uid": 1 - } - } - }] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [{ - "seat": "playwire", - "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/playwire/playwiretest/exemplary/simple-video.json b/adapters/playwire/playwiretest/exemplary/simple-video.json deleted file mode 100644 index fc15013a8c0..00000000000 --- a/adapters/playwire/playwiretest/exemplary/simple-video.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "site": { - "page": "https://good.site/url" - }, - "imp": [{ - "id": "test-imp-id", - "video": { - "mimes": ["video/mp4"], - "protocols": [2, 5], - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - "uid": 1 - } - } - }] - }, - - "httpCalls": [{ - "expectedRequest": { - "uri": "http://localhost/prebid", - "body": { - "id": "test-request-id", - "site": { - "page": "https://good.site/url" - }, - "imp": [{ - "id": "test-imp-id", - "video": { - "mimes": ["video/mp4"], - "protocols": [2, 5], - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - "uid": 1 - } - } - }] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [{ - "seat": "playwire", - "bid": [{ - "id": "randomid", - "impid": "test-imp-id", - "price": 0.500000, - "adid": "12345678", - "adm": "some-test-ad-vast", - "cid": "987", - "crid": "12345678", - "h": 250, - "w": 300 - }] - }], - "cur": "USD" - } - } - }], - - "expectedBidResponses": [{ - "currency": "USD", - "bids": [{ - "bid": { - "id": "randomid", - "impid": "test-imp-id", - "price": 0.5, - "adm": "some-test-ad-vast", - "adid": "12345678", - "cid": "987", - "crid": "12345678", - "w": 300, - "h": 250 - }, - "type": "video" - }] - }] -} diff --git a/adapters/playwire/playwiretest/exemplary/with_gpid.json b/adapters/playwire/playwiretest/exemplary/with_gpid.json deleted file mode 100644 index 386f827cf99..00000000000 --- a/adapters/playwire/playwiretest/exemplary/with_gpid.json +++ /dev/null @@ -1,100 +0,0 @@ -{ - "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 - }, - "data": { - "adserver": { - "name": "some_name", - "adslot": "some_slot" - } - } - } - }] - }, - - "httpCalls": [{ - "expectedRequest": { - "uri": "http://localhost/prebid", - "body": { - "id": "test-request-id", - "site": { - "page": "https://good.site/url" - }, - "imp": [{ - "id": "test-imp-id", - "banner": { - "format": [{ - "w": 300, - "h": 250 - }] - }, - "ext": { - "bidder": { - "uid": 1 - }, - "data": { - "adserver": { - "name": "some_name", - "adslot": "some_slot" - } - }, - "gpid": "some_slot" - } - }] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [{ - "seat": "playwire", - "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/playwire/playwiretest/params/race/banner.json b/adapters/playwire/playwiretest/params/race/banner.json deleted file mode 100644 index 7e347f11b45..00000000000 --- a/adapters/playwire/playwiretest/params/race/banner.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "uid": 1 -} - diff --git a/adapters/playwire/playwiretest/supplemental/bad_bidder_request.json b/adapters/playwire/playwiretest/supplemental/bad_bidder_request.json deleted file mode 100644 index cb9c9333070..00000000000 --- a/adapters/playwire/playwiretest/supplemental/bad_bidder_request.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": "some not exist" - } - } - ] - }, - "expectedMakeRequestsErrors": [ - { - "value": "json: cannot unmarshal string into Go value of type openrtb_ext.ExtImpPlaywire", - "comparison": "literal" - }, - { - "value": "No valid impressions for playwire", - "comparison": "literal" - } - ], - "httpCalls":[], - "expectedBidResponses": [] -} diff --git a/adapters/playwire/playwiretest/supplemental/bad_ext_request.json b/adapters/playwire/playwiretest/supplemental/bad_ext_request.json deleted file mode 100644 index 76cce111948..00000000000 --- a/adapters/playwire/playwiretest/supplemental/bad_ext_request.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": "any" - } - ] - }, - "expectedMakeRequestsErrors": [ - { - "value": "json: cannot unmarshal string into Go value of type adapters.ExtImpBidder", - "comparison": "literal" - }, - { - "value": "No valid impressions for playwire", - "comparison": "literal" - } - ], - "httpCalls": [] -} diff --git a/adapters/playwire/playwiretest/supplemental/bad_response.json b/adapters/playwire/playwiretest/supplemental/bad_response.json deleted file mode 100644 index a9d38368ab2..00000000000 --- a/adapters/playwire/playwiretest/supplemental/bad_response.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "uid": 1 - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://localhost/prebid", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "uid": 1 - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": "{\"id\"data.lost" - } - } - ], - - "expectedMakeBidsErrors": [ - { - "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", - "comparison": "literal" - } - ] -} diff --git a/adapters/playwire/playwiretest/supplemental/empty_uid_request.json b/adapters/playwire/playwiretest/supplemental/empty_uid_request.json deleted file mode 100644 index e6b242942e9..00000000000 --- a/adapters/playwire/playwiretest/supplemental/empty_uid_request.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": {} - } - } - ] - }, - "expectedMakeRequestsErrors": [ - { - "value": "uid is empty", - "comparison": "literal" - }, - { - "value": "No valid impressions for playwire", - "comparison": "literal" - } - ], - "httpCalls":[], - "expectedBidResponses": [] -} diff --git a/adapters/playwire/playwiretest/supplemental/no_imp_request.json b/adapters/playwire/playwiretest/supplemental/no_imp_request.json deleted file mode 100644 index c12686f7328..00000000000 --- a/adapters/playwire/playwiretest/supplemental/no_imp_request.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [] - }, - "expectedMakeRequestsErrors": [ - { - "value": "No valid impressions for playwire", - "comparison": "literal" - } - ], - "httpCalls":[] -} diff --git a/adapters/playwire/playwiretest/supplemental/status_204.json b/adapters/playwire/playwiretest/supplemental/status_204.json deleted file mode 100644 index f935cbe85ae..00000000000 --- a/adapters/playwire/playwiretest/supplemental/status_204.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "uid": 1 - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://localhost/prebid", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "uid": 1 - } - } - } - ] - } - }, - "mockResponse": { - "status": 204, - "body": {} - } - } - ], - - "expectedBidResponses": [] -} diff --git a/adapters/playwire/playwiretest/supplemental/status_400.json b/adapters/playwire/playwiretest/supplemental/status_400.json deleted file mode 100644 index 629b1c07bd7..00000000000 --- a/adapters/playwire/playwiretest/supplemental/status_400.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "uid": 1 - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://localhost/prebid", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "uid": 1 - } - } - } - ] - } - }, - "mockResponse": { - "status": 400, - "body": {} - } - } - ], - - "expectedMakeBidsErrors": [ - { - "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", - "comparison": "literal" - } - ] -} diff --git a/adapters/playwire/playwiretest/supplemental/status_418.json b/adapters/playwire/playwiretest/supplemental/status_418.json deleted file mode 100644 index 0ca365c76ce..00000000000 --- a/adapters/playwire/playwiretest/supplemental/status_418.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "uid": 1 - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://localhost/prebid", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "uid": 1 - } - } - } - ] - } - }, - "mockResponse": { - "status": 418, - "body": {} - } - } - ], - - "expectedMakeBidsErrors": [ - { - "value": "Unexpected status code: 418. Run with request.debug = 1 for more info", - "comparison": "literal" - } - ] -} diff --git a/adapters/playwire/usersync.go b/adapters/playwire/usersync.go deleted file mode 100644 index 76cf9dc0329..00000000000 --- a/adapters/playwire/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package playwire - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewPlaywireSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("playwire", temp, adapters.SyncTypeRedirect) -} diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 3669e0cac7c..1a53dd9e438 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -112,7 +112,6 @@ import ( "github.com/prebid/prebid-server/adapters/orbidder" "github.com/prebid/prebid-server/adapters/outbrain" "github.com/prebid/prebid-server/adapters/pangle" - "github.com/prebid/prebid-server/adapters/playwire" "github.com/prebid/prebid-server/adapters/pubmatic" "github.com/prebid/prebid-server/adapters/pubnative" "github.com/prebid/prebid-server/adapters/pulsepoint" @@ -282,7 +281,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderOutbrain: outbrain.Builder, openrtb_ext.BidderPangle: pangle.Builder, openrtb_ext.BidderPGAM: adtelligent.Builder, - openrtb_ext.BidderPlaywire: playwire.Builder, + openrtb_ext.BidderPlaywire: grid.Builder, openrtb_ext.BidderPlaywireOrtb: playwire_ortb.Builder, openrtb_ext.BidderPubmatic: pubmatic.Builder, openrtb_ext.BidderPubnative: pubnative.Builder, From 57672869cfbaff496a3f27e346edc74eda8849fb Mon Sep 17 00:00:00 2001 From: tcreamer1 <tcream24@yahoo.com> Date: Fri, 31 Mar 2023 09:06:51 -0400 Subject: [PATCH 106/125] Merge pull request #42 from intergi/encodeHttpStoredReqQuotes encode url quotes, gitignore, larger timeout (cherry picked from commit a7ffc9e22dcc10e6d0067281ddc36b4e2f7f4b1b) --- .gitignore | 2 ++ stored_requests/backends/http_fetcher/fetcher.go | 6 +++--- stored_requests/data/by_id/stored_requests/playwire.json | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 514e217b46c..ccd3f1a0aa1 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,5 @@ analytics/filesystem/testFiles/ # autogenerated mac file .DS_Store + +.env* \ No newline at end of file diff --git a/stored_requests/backends/http_fetcher/fetcher.go b/stored_requests/backends/http_fetcher/fetcher.go index 60d3bcfabd3..896b09d02c1 100644 --- a/stored_requests/backends/http_fetcher/fetcher.go +++ b/stored_requests/backends/http_fetcher/fetcher.go @@ -218,11 +218,11 @@ func (fetcher *HttpFetcher) FetchCategories(ctx context.Context, primaryAdServer func buildRequest(endpoint string, requestIDs []string, impIDs []string) (*http.Request, error) { if len(requestIDs) > 0 && len(impIDs) > 0 { - return http.NewRequest("GET", endpoint+"request-ids=[\""+strings.Join(requestIDs, "\",\"")+"\"]&imp-ids=[\""+strings.Join(impIDs, "\",\"")+"\"]", nil) + return http.NewRequest("GET", endpoint+"request-ids=[%22"+strings.Join(requestIDs, "%22,%22")+"%22]&imp-ids=[%22"+strings.Join(impIDs, "%22,%22")+"%22]", nil) } else if len(requestIDs) > 0 { - return http.NewRequest("GET", endpoint+"request-ids=[\""+strings.Join(requestIDs, "\",\"")+"\"]", nil) + return http.NewRequest("GET", endpoint+"request-ids=[%22"+strings.Join(requestIDs, "%22,%22")+"%22]", nil) } else { - return http.NewRequest("GET", endpoint+"imp-ids=[\""+strings.Join(impIDs, "\",\"")+"\"]", nil) + return http.NewRequest("GET", endpoint+"imp-ids=[%22"+strings.Join(impIDs, "%22,%22")+"%22]", nil) } } diff --git a/stored_requests/data/by_id/stored_requests/playwire.json b/stored_requests/data/by_id/stored_requests/playwire.json index 9c5eb1aadf7..72daa47564b 100644 --- a/stored_requests/data/by_id/stored_requests/playwire.json +++ b/stored_requests/data/by_id/stored_requests/playwire.json @@ -1,5 +1,5 @@ { - "tmax": 1000, + "tmax": 2500, "cur": [ "USD" ], From 1660d89a0d7e6f2caea1895c72b22c602b8da051 Mon Sep 17 00:00:00 2001 From: tcreamer1 <tcream24@yahoo.com> Date: Tue, 4 Apr 2023 09:19:58 -0400 Subject: [PATCH 107/125] Merge pull request #44 from intergi/develop timeout 2s (cherry picked from commit eb801438fcc3c077f5558526ef369f19c7eb369d) --- stored_requests/data/by_id/stored_requests/playwire.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/stored_requests/data/by_id/stored_requests/playwire.json b/stored_requests/data/by_id/stored_requests/playwire.json index 72daa47564b..03f5a4ed79c 100644 --- a/stored_requests/data/by_id/stored_requests/playwire.json +++ b/stored_requests/data/by_id/stored_requests/playwire.json @@ -1,8 +1,6 @@ { - "tmax": 2500, - "cur": [ - "USD" - ], + "tmax": 2000, + "cur": ["USD"], "ext": { "prebid": { "targeting": { From d14ed8705361094730c067779b341f600eea0fea Mon Sep 17 00:00:00 2001 From: ltarabarova <95255464+ltarabarova@users.noreply.github.com> Date: Mon, 24 Apr 2023 22:16:45 +0300 Subject: [PATCH 108/125] Merge pull request #45 from intergi/ci-cd CI/CD: Add dev deployment workflow (cherry picked from commit 2a293d1834e86f8fe8a7b778e0c1775bc39f53f5) --- .github/workflows/dev-deploy.yml | 63 ++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 .github/workflows/dev-deploy.yml diff --git a/.github/workflows/dev-deploy.yml b/.github/workflows/dev-deploy.yml new file mode 100644 index 00000000000..4a4b958c7ab --- /dev/null +++ b/.github/workflows/dev-deploy.yml @@ -0,0 +1,63 @@ +name: DEV Deployment to ECS + +on: + workflow_dispatch + +env: + AWS_REGION: "us-east-1" + AWS_ROLE: arn:aws:iam::885182695675:role/PrebidDeploymentDevRole + REPO_NAME: prebid-server + +jobs: + deploy: + name: Deploy + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1-node16 + with: + role-to-assume: ${{ env.AWS_ROLE }} + aws-region: ${{ env.AWS_REGION }} + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push the image to Amazon ECR + id: build-image + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: ${{ env.REPO_NAME }} + IMAGE_TAG: ${{ github.sha }} + run: | + # Build a docker container and push it to ECR + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . + echo "Pushing image to ECR..." + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" + - name: Download task definition + run: | + aws ecs describe-task-definition --task-definition prebid-server --query taskDefinition > task-definition.json + + - name: Fill in the new image ID in the Amazon ECS task definition + id: task-def + uses: aws-actions/amazon-ecs-render-task-definition@v1 + with: + task-definition: task-definition.json + container-name: prebid-server + image: ${{ steps.build-image.outputs.image }} + + - name: Deploy Amazon ECS task definition + uses: aws-actions/amazon-ecs-deploy-task-definition@v1 + with: + task-definition: ${{ steps.task-def.outputs.task-definition }} + service: prebid-dev-service + cluster: prebid-server-dev-cluster + wait-for-service-stability: true \ No newline at end of file From bc436478bbed761cbcbc752139dc9a450f0784b6 Mon Sep 17 00:00:00 2001 From: Derek Bruneau <derek.bruneau@gmail.com> Date: Tue, 10 Sep 2024 17:24:59 -0400 Subject: [PATCH 109/125] Revert "CI/CD: Add dev deployment workflow" --- .github/workflows/dev-deploy.yml | 63 -------------------------------- 1 file changed, 63 deletions(-) delete mode 100644 .github/workflows/dev-deploy.yml diff --git a/.github/workflows/dev-deploy.yml b/.github/workflows/dev-deploy.yml deleted file mode 100644 index 4a4b958c7ab..00000000000 --- a/.github/workflows/dev-deploy.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: DEV Deployment to ECS - -on: - workflow_dispatch - -env: - AWS_REGION: "us-east-1" - AWS_ROLE: arn:aws:iam::885182695675:role/PrebidDeploymentDevRole - REPO_NAME: prebid-server - -jobs: - deploy: - name: Deploy - runs-on: ubuntu-latest - permissions: - id-token: write - contents: read - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v1-node16 - with: - role-to-assume: ${{ env.AWS_ROLE }} - aws-region: ${{ env.AWS_REGION }} - - - name: Login to Amazon ECR - id: login-ecr - uses: aws-actions/amazon-ecr-login@v1 - - - name: Build, tag, and push the image to Amazon ECR - id: build-image - env: - ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} - ECR_REPOSITORY: ${{ env.REPO_NAME }} - IMAGE_TAG: ${{ github.sha }} - run: | - # Build a docker container and push it to ECR - docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . - echo "Pushing image to ECR..." - docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG - echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" - - name: Download task definition - run: | - aws ecs describe-task-definition --task-definition prebid-server --query taskDefinition > task-definition.json - - - name: Fill in the new image ID in the Amazon ECS task definition - id: task-def - uses: aws-actions/amazon-ecs-render-task-definition@v1 - with: - task-definition: task-definition.json - container-name: prebid-server - image: ${{ steps.build-image.outputs.image }} - - - name: Deploy Amazon ECS task definition - uses: aws-actions/amazon-ecs-deploy-task-definition@v1 - with: - task-definition: ${{ steps.task-def.outputs.task-definition }} - service: prebid-dev-service - cluster: prebid-server-dev-cluster - wait-for-service-stability: true \ No newline at end of file From 81294ab24c77921681b4c48582008d59a20f98af Mon Sep 17 00:00:00 2001 From: Derek Bruneau <derek.bruneau@gmail.com> Date: Wed, 11 Sep 2024 11:52:30 -0400 Subject: [PATCH 110/125] Removing unused 'playwire' and '*_ortb' adapters --- adapters/playwire_ortb/go.mod | 1 - adapters/playwire_ortb/params_test.go | 53 ----- adapters/playwire_ortb/playwire_ortb.go | 196 ------------------ adapters/playwire_ortb/playwire_ortb_test.go | 17 -- .../playwire_ortbtest/exemplary/banner.json | 99 --------- .../playwire_ortbtest/params/race/banner.json | 3 - exchange/adapter_builders.go | 6 - go.mod | 4 - go.sum | 2 - openrtb_ext/bidders.go | 10 - openrtb_ext/bidders_validate_test.go | 2 +- openrtb_ext/imp_playwire.go | 6 - static/bidder-info/engagebdr_ortb.yaml | 16 -- static/bidder-info/gumgum_ortb.yaml | 16 -- static/bidder-info/playwire.yaml | 18 -- static/bidder-info/playwire_ortb.yaml | 12 -- static/bidder-info/pulsepoint_ortb.yaml | 16 -- static/bidder-params/engagebdr_ortb.json | 13 -- static/bidder-params/gumgum_ortb.json | 13 -- static/bidder-params/playwire.json | 25 --- static/bidder-params/playwire_ortb.json | 13 -- static/bidder-params/pulsepoint_ortb.json | 13 -- 22 files changed, 1 insertion(+), 553 deletions(-) delete mode 100644 adapters/playwire_ortb/go.mod delete mode 100644 adapters/playwire_ortb/params_test.go delete mode 100644 adapters/playwire_ortb/playwire_ortb.go delete mode 100644 adapters/playwire_ortb/playwire_ortb_test.go delete mode 100644 adapters/playwire_ortb/playwire_ortbtest/exemplary/banner.json delete mode 100644 adapters/playwire_ortb/playwire_ortbtest/params/race/banner.json delete mode 100644 openrtb_ext/imp_playwire.go delete mode 100644 static/bidder-info/engagebdr_ortb.yaml delete mode 100644 static/bidder-info/gumgum_ortb.yaml delete mode 100644 static/bidder-info/playwire.yaml delete mode 100644 static/bidder-info/playwire_ortb.yaml delete mode 100644 static/bidder-info/pulsepoint_ortb.yaml delete mode 100644 static/bidder-params/engagebdr_ortb.json delete mode 100644 static/bidder-params/gumgum_ortb.json delete mode 100644 static/bidder-params/playwire.json delete mode 100644 static/bidder-params/playwire_ortb.json delete mode 100644 static/bidder-params/pulsepoint_ortb.json diff --git a/adapters/playwire_ortb/go.mod b/adapters/playwire_ortb/go.mod deleted file mode 100644 index c96523beac1..00000000000 --- a/adapters/playwire_ortb/go.mod +++ /dev/null @@ -1 +0,0 @@ -module github.com/prebid-server/adapters/playwire_ortb \ No newline at end of file diff --git a/adapters/playwire_ortb/params_test.go b/adapters/playwire_ortb/params_test.go deleted file mode 100644 index 48cab6c73ee..00000000000 --- a/adapters/playwire_ortb/params_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package playwire_ortb - -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.BidderGumGum, json.RawMessage(validParam)); err != nil { - t.Errorf("Schema rejected gumgum 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{ - `{"zone":"dc9d6be1"}`, -} - -var invalidParams = []string{ - `null`, - `nil`, - ``, - `{}`, - `[]`, - `true`, - `2`, - `{"zone":12345678}`, - `{"zone":""}`, - `{"placementId": 1}`, - `{"zone": true}`, - `{"placementId": 1, "zone":"1234567"}`, -} diff --git a/adapters/playwire_ortb/playwire_ortb.go b/adapters/playwire_ortb/playwire_ortb.go deleted file mode 100644 index 63d9578aeae..00000000000 --- a/adapters/playwire_ortb/playwire_ortb.go +++ /dev/null @@ -1,196 +0,0 @@ -package playwire_ortb - -import ( - "encoding/json" - "fmt" - "net/http" - "strconv" - "strings" - - "github.com/prebid/openrtb/v17/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" -) - -// PlaywireOrtbAdapter implements Bidder interface. -type PlaywireOrtbAdapter struct { - URI string -} - -// MakeRequests makes the HTTP requests which should be made to fetch bids. -func (g *PlaywireOrtbAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - var validImps []openrtb2.Imp - var siteCopy openrtb2.Site - if request.Site != nil { - siteCopy = *request.Site - } - - numRequests := len(request.Imp) - errs := make([]error, 0, numRequests) - - for i := 0; i < numRequests; i++ { - imp := request.Imp[i] - playwire_ortbExt, err := preprocess(&imp) - if err != nil { - errs = append(errs, err) - } else { - if playwire_ortbExt.Zone != "" { - siteCopy.ID = playwire_ortbExt.Zone - } - - if playwire_ortbExt.PubID != 0 { - if siteCopy.Publisher != nil { - siteCopy.Publisher.ID = strconv.FormatFloat(playwire_ortbExt.PubID, 'f', -1, 64) - } else { - siteCopy.Publisher = &openrtb2.Publisher{ID: strconv.FormatFloat(playwire_ortbExt.PubID, 'f', -1, 64)} - } - } - - validImps = append(validImps, imp) - } - } - - if len(validImps) == 0 { - return nil, errs - } - - request.Imp = validImps - - if request.Site != nil { - request.Site = &siteCopy - } - - reqJSON, err := json.Marshal(request) - if err != nil { - errs = append(errs, err) - return nil, errs - } - - headers := http.Header{} - headers.Add("Content-Type", "application/json;charset=utf-8") - headers.Add("Accept", "application/json") - return []*adapters.RequestData{{ - Method: "POST", - Uri: g.URI, - Body: reqJSON, - Headers: headers, - }}, errs -} - -// MakeBids unpacks the server's response into Bids. -func (g *PlaywireOrtbAdapter) 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", response.StatusCode), - }} - } - - if response.StatusCode != http.StatusOK { - return nil, []error{&errortypes.BadServerResponse{ - Message: fmt.Sprintf("Bad server response: HTTP status %d", 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: %d. ", err), - }} - } - - var errs []error - bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) - - for _, sb := range bidResp.SeatBid { - for i := range sb.Bid { - mediaType := getMediaTypeForImpID(sb.Bid[i].ImpID, internalRequest.Imp) - if mediaType == openrtb_ext.BidTypeVideo { - price := strconv.FormatFloat(sb.Bid[i].Price, 'f', -1, 64) - sb.Bid[i].AdM = strings.Replace(sb.Bid[i].AdM, "${AUCTION_PRICE}", price, -1) - } - - bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ - Bid: &sb.Bid[i], - BidType: mediaType, - }) - } - } - - return bidResponse, errs -} - -func preprocess(imp *openrtb2.Imp) (*openrtb_ext.ExtImpGumGum, error) { - var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { - err = &errortypes.BadInput{ - Message: err.Error(), - } - return nil, err - } - - var playwire_ortbExt openrtb_ext.ExtImpGumGum - if err := json.Unmarshal(bidderExt.Bidder, &playwire_ortbExt); err != nil { - err = &errortypes.BadInput{ - Message: err.Error(), - } - return nil, err - } - - if imp.Banner != nil && imp.Banner.W == nil && imp.Banner.H == nil && len(imp.Banner.Format) > 0 { - bannerCopy := *imp.Banner - format := bannerCopy.Format[0] - bannerCopy.W = &(format.W) - bannerCopy.H = &(format.H) - imp.Banner = &bannerCopy - } - - if imp.Video != nil { - err := validateVideoParams(imp.Video) - if err != nil { - return nil, err - } - - if playwire_ortbExt.IrisID != "" { - videoCopy := *imp.Video - videoExt := openrtb_ext.ExtImpGumGumVideo{IrisID: playwire_ortbExt.IrisID} - videoCopy.Ext, err = json.Marshal(&videoExt) - if err != nil { - return nil, err - } - imp.Video = &videoCopy - } - } - - return &playwire_ortbExt, nil -} - -func getMediaTypeForImpID(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { - for _, imp := range imps { - if imp.ID == impID && imp.Banner != nil { - return openrtb_ext.BidTypeBanner - } - } - return openrtb_ext.BidTypeVideo -} - -func validateVideoParams(video *openrtb2.Video) (err error) { - if video.W == 0 || video.H == 0 || video.MinDuration == 0 || video.MaxDuration == 0 || video.Placement == 0 || video.Linearity == 0 { - return &errortypes.BadInput{ - Message: "Invalid or missing video field(s)", - } - } - return nil -} - -// Builder builds a new instance of the PlaywireOrtb adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { - bidder := &PlaywireOrtbAdapter{ - URI: config.Endpoint, - } - return bidder, nil -} diff --git a/adapters/playwire_ortb/playwire_ortb_test.go b/adapters/playwire_ortb/playwire_ortb_test.go deleted file mode 100644 index e68c31cb4a5..00000000000 --- a/adapters/playwire_ortb/playwire_ortb_test.go +++ /dev/null @@ -1,17 +0,0 @@ -package playwire_ortb - -import ( - "github.com/prebid/prebid-server/adapters/adapterstest" - "testing" -) - -func TestJsonSamples(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderGumGum, config.Adapter{ - Endpoint: "https://g2.gumgum.com/providers/prbds2s/bid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) - - if buildErr != nil { - t.Fatalf("Builder returned unexpected error %v", buildErr) - } - - adapterstest.RunJSONBidderTest(t, "gumgumtest", bidder) -} diff --git a/adapters/playwire_ortb/playwire_ortbtest/exemplary/banner.json b/adapters/playwire_ortb/playwire_ortbtest/exemplary/banner.json deleted file mode 100644 index 2fbd3da22da..00000000000 --- a/adapters/playwire_ortb/playwire_ortbtest/exemplary/banner.json +++ /dev/null @@ -1,99 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 300 - } - ], - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - "zone": "dc9d6be1" - } - } - } - ] - }, - - "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": { - "bidder": { - "zone": "dc9d6be1" - } - } - }] - } - }, - "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" - } - ] - } - ] -} diff --git a/adapters/playwire_ortb/playwire_ortbtest/params/race/banner.json b/adapters/playwire_ortb/playwire_ortbtest/params/race/banner.json deleted file mode 100644 index 6e222304f36..00000000000 --- a/adapters/playwire_ortb/playwire_ortbtest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "zone": "dc9d6be1" -} diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 1a53dd9e438..5ceaae1cdb8 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -1,7 +1,6 @@ package exchange import ( - "github.com/prebid-server/adapters/playwire_ortb" "github.com/prebid/prebid-server/adapters" ttx "github.com/prebid/prebid-server/adapters/33across" "github.com/prebid/prebid-server/adapters/aax" @@ -230,7 +229,6 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderDmx: dmx.Builder, openrtb_ext.BidderEmxDigital: emx_digital.Builder, openrtb_ext.BidderEngageBDR: engagebdr.Builder, - openrtb_ext.BidderEngageBDROrtb: playwire_ortb.Builder, openrtb_ext.BidderEPlanning: eplanning.Builder, openrtb_ext.BidderEpom: epom.Builder, openrtb_ext.BidderEVolution: evolution.Builder, @@ -241,7 +239,6 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderGrid: grid.Builder, openrtb_ext.BidderGroupm: pubmatic.Builder, openrtb_ext.BidderGumGum: gumgum.Builder, - openrtb_ext.BidderGumGumOrtb: playwire_ortb.Builder, openrtb_ext.BidderHuaweiAds: huaweiads.Builder, openrtb_ext.BidderImpactify: impactify.Builder, openrtb_ext.BidderImprovedigital: improvedigital.Builder, @@ -281,12 +278,9 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderOutbrain: outbrain.Builder, openrtb_ext.BidderPangle: pangle.Builder, openrtb_ext.BidderPGAM: adtelligent.Builder, - openrtb_ext.BidderPlaywire: grid.Builder, - openrtb_ext.BidderPlaywireOrtb: playwire_ortb.Builder, openrtb_ext.BidderPubmatic: pubmatic.Builder, openrtb_ext.BidderPubnative: pubnative.Builder, openrtb_ext.BidderPulsepoint: pulsepoint.Builder, - openrtb_ext.BidderPulsepointOrtb: playwire_ortb.Builder, openrtb_ext.BidderQuantumdex: apacdex.Builder, openrtb_ext.BidderRevcontent: revcontent.Builder, openrtb_ext.BidderRhythmone: rhythmone.Builder, diff --git a/go.mod b/go.mod index 2566111bf2e..556c6659666 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,6 @@ go 1.19 // Magic comment that determines which Go version Heroku uses. // +heroku goVersion go1.19 -replace github.com/prebid-server/adapters/playwire_ortb => ./adapters/playwire_ortb/ - require ( github.com/DATA-DOG/go-sqlmock v1.5.0 github.com/IABTechLab/adscert v0.34.0 @@ -23,8 +21,6 @@ require ( github.com/julienschmidt/httprouter v1.3.0 github.com/lib/pq v1.10.4 github.com/mitchellh/copystructure v1.2.0 - github.com/mxmCherry/openrtb/v15 v15.0.1 - github.com/prebid-server/adapters/playwire_ortb v0.0.0-00010101000000-000000000000 github.com/prebid/go-gdpr v1.11.0 github.com/prebid/go-gpp v0.1.1 github.com/prebid/openrtb/v17 v17.1.0 diff --git a/go.sum b/go.sum index 56926b400ac..30f3017ba7f 100644 --- a/go.sum +++ b/go.sum @@ -382,8 +382,6 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 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.1 h1:RnMj/n8Sv0wqaiWQCbmUwzeO6T4Bk3d9RokKvla1CDc= -github.com/mxmCherry/openrtb/v15 v15.0.1/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= diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 2fde9c5b96e..c5de0576718 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -148,7 +148,6 @@ const ( BidderDmx BidderName = "dmx" BidderEmxDigital BidderName = "emx_digital" BidderEngageBDR BidderName = "engagebdr" - BidderEngageBDROrtb BidderName = "engagebdr_ortb" BidderEPlanning BidderName = "eplanning" BidderEpom BidderName = "epom" BidderEVolution BidderName = "e_volution" @@ -159,7 +158,6 @@ const ( BidderGrid BidderName = "grid" BidderGroupm BidderName = "groupm" BidderGumGum BidderName = "gumgum" - BidderGumGumOrtb BidderName = "gumgum_ortb" BidderHuaweiAds BidderName = "huaweiads" BidderImpactify BidderName = "impactify" BidderImprovedigital BidderName = "improvedigital" @@ -199,12 +197,9 @@ const ( BidderOutbrain BidderName = "outbrain" BidderPangle BidderName = "pangle" BidderPGAM BidderName = "pgam" - BidderPlaywire BidderName = "playwire" - BidderPlaywireOrtb BidderName = "playwire_ortb" BidderPubmatic BidderName = "pubmatic" BidderPubnative BidderName = "pubnative" BidderPulsepoint BidderName = "pulsepoint" - BidderPulsepointOrtb BidderName = "pulsepoint_ortb" BidderQuantumdex BidderName = "quantumdex" BidderRevcontent BidderName = "revcontent" BidderRhythmone BidderName = "rhythmone" @@ -323,7 +318,6 @@ func CoreBidderNames() []BidderName { BidderDmx, BidderEmxDigital, BidderEngageBDR, - BidderEngageBDROrtb, BidderEPlanning, BidderEpom, BidderEVolution, @@ -334,7 +328,6 @@ func CoreBidderNames() []BidderName { BidderGrid, BidderGroupm, BidderGumGum, - BidderGumGumOrtb, BidderHuaweiAds, BidderImpactify, BidderImprovedigital, @@ -374,12 +367,9 @@ func CoreBidderNames() []BidderName { BidderOutbrain, BidderPangle, BidderPGAM, - BidderPlaywire, - BidderPlaywireOrtb, BidderPubmatic, BidderPubnative, BidderPulsepoint, - BidderPulsepointOrtb, BidderQuantumdex, BidderRevcontent, BidderRhythmone, diff --git a/openrtb_ext/bidders_validate_test.go b/openrtb_ext/bidders_validate_test.go index 8a8f9936147..530f260c761 100644 --- a/openrtb_ext/bidders_validate_test.go +++ b/openrtb_ext/bidders_validate_test.go @@ -55,7 +55,7 @@ func TestBidderUniquenessGatekeeping(t *testing.T) { } } - currentThreshold := 11 + currentThreshold := 6 measuredThreshold := minUniquePrefixLength(bidders) assert.NotZero(t, measuredThreshold, "BidderMap contains duplicate bidder name values.") diff --git a/openrtb_ext/imp_playwire.go b/openrtb_ext/imp_playwire.go deleted file mode 100644 index 4c1e6b36268..00000000000 --- a/openrtb_ext/imp_playwire.go +++ /dev/null @@ -1,6 +0,0 @@ -package openrtb_ext - -// ExtImpPlaywire defines the contract for bidrequest.imp[i].ext.playwire -type ExtImpPlaywire struct { - Uid int `json:"uid"` -} diff --git a/static/bidder-info/engagebdr_ortb.yaml b/static/bidder-info/engagebdr_ortb.yaml deleted file mode 100644 index fa6e5bffb6d..00000000000 --- a/static/bidder-info/engagebdr_ortb.yaml +++ /dev/null @@ -1,16 +0,0 @@ -endpoint: "https://dsp.bnmla.com/bid?sspid=1000204" -maintainer: - email: "na@playwire.com" -capabilities: - app: - mediaTypes: - - banner - - video - - audio - - native - site: - mediaTypes: - - banner - - video - - audio - - native diff --git a/static/bidder-info/gumgum_ortb.yaml b/static/bidder-info/gumgum_ortb.yaml deleted file mode 100644 index 05b26f3f1b4..00000000000 --- a/static/bidder-info/gumgum_ortb.yaml +++ /dev/null @@ -1,16 +0,0 @@ -endpoint: "https://g2.gumgum.com/zones/8ylgv2wd/bid" -maintainer: - email: "na@playwire.com" -capabilities: - app: - mediaTypes: - - banner - - video - - audio - - native - site: - mediaTypes: - - banner - - video - - audio - - native diff --git a/static/bidder-info/playwire.yaml b/static/bidder-info/playwire.yaml deleted file mode 100644 index 2ea70768a56..00000000000 --- a/static/bidder-info/playwire.yaml +++ /dev/null @@ -1,18 +0,0 @@ -endpoint: "https://grid.bidswitch.net/sp_bid?sp=prebid" -maintainer: - email: "na@playwire.com" -capabilities: - app: - mediaTypes: - - banner - - video - - native - site: - mediaTypes: - - banner - - video - - native -userSync: - redirect: - url: "https://x.bidswitch.net/check_uuid/{{.RedirectURL}}?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}" - userMacro: "${BSW_UUID}" \ No newline at end of file diff --git a/static/bidder-info/playwire_ortb.yaml b/static/bidder-info/playwire_ortb.yaml deleted file mode 100644 index 8c25833a376..00000000000 --- a/static/bidder-info/playwire_ortb.yaml +++ /dev/null @@ -1,12 +0,0 @@ -endpoint: "https://grid.bidswitch.net/sp_bid?sp=prebid" -maintainer: - email: "na@playwire.com" -capabilities: - app: - mediaTypes: - - banner - - video - site: - mediaTypes: - - banner - - video diff --git a/static/bidder-info/pulsepoint_ortb.yaml b/static/bidder-info/pulsepoint_ortb.yaml deleted file mode 100644 index 3d28d0394b9..00000000000 --- a/static/bidder-info/pulsepoint_ortb.yaml +++ /dev/null @@ -1,16 +0,0 @@ -endpoint: "http://rts.lga.contextweb.com/rt-seller/bid/openrtb/playwire" -maintainer: - email: "na@playwire.com" -capabilities: - app: - mediaTypes: - - banner - - video - - audio - - native - site: - mediaTypes: - - banner - - video - - audio - - native diff --git a/static/bidder-params/engagebdr_ortb.json b/static/bidder-params/engagebdr_ortb.json deleted file mode 100644 index 8849dc474dc..00000000000 --- a/static/bidder-params/engagebdr_ortb.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Playwire ORTB Adapter Params", - "description": "A schema which validates params accepted by the Playwire ORTB adapter", - "type": "object", - "properties": { - "sspid": { - "type": "string", - "description": "SSPID parameter", - "pattern": "^[0-9]+$" - } - } -} diff --git a/static/bidder-params/gumgum_ortb.json b/static/bidder-params/gumgum_ortb.json deleted file mode 100644 index 8849dc474dc..00000000000 --- a/static/bidder-params/gumgum_ortb.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Playwire ORTB Adapter Params", - "description": "A schema which validates params accepted by the Playwire ORTB adapter", - "type": "object", - "properties": { - "sspid": { - "type": "string", - "description": "SSPID parameter", - "pattern": "^[0-9]+$" - } - } -} diff --git a/static/bidder-params/playwire.json b/static/bidder-params/playwire.json deleted file mode 100644 index 83fcbe0adbf..00000000000 --- a/static/bidder-params/playwire.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Playwire Adapter Params", - "description": "A schema which validates params accepted by the Playwire adapter", - "type": "object", - "properties": { - "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": [] -} \ No newline at end of file diff --git a/static/bidder-params/playwire_ortb.json b/static/bidder-params/playwire_ortb.json deleted file mode 100644 index dd4b9be09a7..00000000000 --- a/static/bidder-params/playwire_ortb.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Playwire Adapter Params", - "description": "A schema which validates params accepted by the Playwire adapter", - "type": "object", - "properties": { - "uid": { - "type": "integer", - "description": "An ID which identifies this placement of the impression" - } - }, - "required": [] -} diff --git a/static/bidder-params/pulsepoint_ortb.json b/static/bidder-params/pulsepoint_ortb.json deleted file mode 100644 index 8849dc474dc..00000000000 --- a/static/bidder-params/pulsepoint_ortb.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Playwire ORTB Adapter Params", - "description": "A schema which validates params accepted by the Playwire ORTB adapter", - "type": "object", - "properties": { - "sspid": { - "type": "string", - "description": "SSPID parameter", - "pattern": "^[0-9]+$" - } - } -} From b56339b89bba0dcb6efbed624b946b9c2cf27716 Mon Sep 17 00:00:00 2001 From: Derek Bruneau <derek.bruneau@gmail.com> Date: Fri, 11 Oct 2024 09:45:29 -0400 Subject: [PATCH 111/125] Enabling bidder keys (e.g. hb_pb_rubicon) in targeting object of response --- stored_requests/data/by_id/stored_requests/playwire.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stored_requests/data/by_id/stored_requests/playwire.json b/stored_requests/data/by_id/stored_requests/playwire.json index 03f5a4ed79c..3700ebc67ff 100644 --- a/stored_requests/data/by_id/stored_requests/playwire.json +++ b/stored_requests/data/by_id/stored_requests/playwire.json @@ -5,7 +5,7 @@ "prebid": { "targeting": { "includewinners": true, - "includebidderkeys": false, + "includebidderkeys": true, "mediatypepricegranularity": { "banner": { "ranges": [ From 5d5303f385e0e826cae7ce1fd64784ac50f272cd Mon Sep 17 00:00:00 2001 From: Derek Bruneau <derek.bruneau@gmail.com> Date: Fri, 25 Oct 2024 16:00:29 -0400 Subject: [PATCH 112/125] Support keys added by Prebid.js's PAAPI module --- endpoints/openrtb2/auction.go | 9 +++++++++ exchange/utils.go | 7 +++++++ exchange/utils_test.go | 30 ++++++++++++++++++++++++++++++ openrtb_ext/bidders.go | 21 +++++++++++++++++++++ openrtb_ext/bidders_test.go | 6 ++++++ openrtb_ext/request.go | 8 ++++++++ 6 files changed, 81 insertions(+) diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index aeb5c20e64e..36cca596022 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -1562,6 +1562,15 @@ func isPossibleBidder(bidder string) bool { return false case openrtb_ext.BidderReservedAE: return false + // HACK: Support the keys used by the PAAPI (Protected Audience API) module. + // Without the hack, PBS will reject any requests that contain them. This hack + // can be removed once the upstream repo supports them. For more info, see: + // https://docs.prebid.org/dev-docs/modules/paapi.html + // https://github.com/prebid/prebid-server/issues/3735 + case openrtb_ext.BidderReservedIGS: + return false + case openrtb_ext.BidderReservedPAAPI: + return false default: return true } diff --git a/exchange/utils.go b/exchange/utils.go index 0becca21f6c..283dc45e612 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -595,6 +595,13 @@ var allowedImpExtFields = map[string]interface{}{ openrtb_ext.GPIDKey: struct{}{}, openrtb_ext.SKAdNExtKey: struct{}{}, openrtb_ext.TIDKey: struct{}{}, + // HACK: Support the keys used by the PAAPI (Protected Audience API) module. + // Without the hack, PBS will reject any requests that contain them. This hack + // can be removed once the upstream repo supports them. For more info, see: + // https://docs.prebid.org/dev-docs/modules/paapi.html + // https://github.com/prebid/prebid-server/issues/3735 + openrtb_ext.IGSKey: struct{}{}, + openrtb_ext.PAAPIKey: struct{}{}, } var allowedImpExtPrebidFields = map[string]interface{}{ diff --git a/exchange/utils_test.go b/exchange/utils_test.go index ee6dad1ad96..82195ae9f5e 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -249,6 +249,8 @@ func TestCreateSanitizedImpExt(t *testing.T) { "skadn": json.RawMessage(`"anySKAdNetwork"`), "gpid": json.RawMessage(`"anyGPID"`), "tid": json.RawMessage(`"anyTID"`), + "igs": json.RawMessage(`"anyIGS"`), + "paapi": json.RawMessage(`"anyPAAPI"`), }, givenImpExtPrebid: map[string]json.RawMessage{ "bidder": json.RawMessage(`"anyBidder"`), @@ -259,6 +261,8 @@ func TestCreateSanitizedImpExt(t *testing.T) { "skadn": json.RawMessage(`"anySKAdNetwork"`), "gpid": json.RawMessage(`"anyGPID"`), "tid": json.RawMessage(`"anyTID"`), + "igs": json.RawMessage(`"anyIGS"`), + "paapi": json.RawMessage(`"anyPAAPI"`), }, expectedError: "", }, @@ -271,6 +275,8 @@ func TestCreateSanitizedImpExt(t *testing.T) { "skadn": json.RawMessage(`"anySKAdNetwork"`), "gpid": json.RawMessage(`"anyGPID"`), "tid": json.RawMessage(`"anyTID"`), + "igs": json.RawMessage(`"anyIGS"`), + "paapi": json.RawMessage(`"anyPAAPI"`), }, givenImpExtPrebid: map[string]json.RawMessage{ "bidder": json.RawMessage(`"anyBidder"`), @@ -282,6 +288,8 @@ func TestCreateSanitizedImpExt(t *testing.T) { "skadn": json.RawMessage(`"anySKAdNetwork"`), "gpid": json.RawMessage(`"anyGPID"`), "tid": json.RawMessage(`"anyTID"`), + "igs": json.RawMessage(`"anyIGS"`), + "paapi": json.RawMessage(`"anyPAAPI"`), }, expectedError: "", }, @@ -294,6 +302,8 @@ func TestCreateSanitizedImpExt(t *testing.T) { "skadn": json.RawMessage(`"anySKAdNetwork"`), "gpid": json.RawMessage(`"anyGPID"`), "tid": json.RawMessage(`"anyTID"`), + "igs": json.RawMessage(`"anyIGS"`), + "paapi": json.RawMessage(`"anyPAAPI"`), }, givenImpExtPrebid: map[string]json.RawMessage{ "bidder": json.RawMessage(`"anyBidder"`), @@ -307,6 +317,8 @@ func TestCreateSanitizedImpExt(t *testing.T) { "skadn": json.RawMessage(`"anySKAdNetwork"`), "gpid": json.RawMessage(`"anyGPID"`), "tid": json.RawMessage(`"anyTID"`), + "igs": json.RawMessage(`"anyIGS"`), + "paapi": json.RawMessage(`"anyPAAPI"`), }, expectedError: "", }, @@ -319,6 +331,8 @@ func TestCreateSanitizedImpExt(t *testing.T) { "skadn": json.RawMessage(`"anySKAdNetwork"`), "gpid": json.RawMessage(`"anyGPID"`), "tid": json.RawMessage(`"anyTID"`), + "igs": json.RawMessage(`"anyIGS"`), + "paapi": json.RawMessage(`"anyPAAPI"`), }, givenImpExtPrebid: map[string]json.RawMessage{}, expected: map[string]json.RawMessage{ @@ -327,6 +341,8 @@ func TestCreateSanitizedImpExt(t *testing.T) { "skadn": json.RawMessage(`"anySKAdNetwork"`), "gpid": json.RawMessage(`"anyGPID"`), "tid": json.RawMessage(`"anyTID"`), + "igs": json.RawMessage(`"anyIGS"`), + "paapi": json.RawMessage(`"anyPAAPI"`), }, expectedError: "", }, @@ -340,6 +356,8 @@ func TestCreateSanitizedImpExt(t *testing.T) { "skadn": json.RawMessage(`"anySKAdNetwork"`), "gpid": json.RawMessage(`"anyGPID"`), "tid": json.RawMessage(`"anyTID"`), + "igs": json.RawMessage(`"anyIGS"`), + "paapi": json.RawMessage(`"anyPAAPI"`), }, givenImpExtPrebid: map[string]json.RawMessage{ "bidder": json.RawMessage(`"anyBidder"`), @@ -350,6 +368,8 @@ func TestCreateSanitizedImpExt(t *testing.T) { "skadn": json.RawMessage(`"anySKAdNetwork"`), "gpid": json.RawMessage(`"anyGPID"`), "tid": json.RawMessage(`"anyTID"`), + "igs": json.RawMessage(`"anyIGS"`), + "paapi": json.RawMessage(`"anyPAAPI"`), }, expectedError: "", }, @@ -363,6 +383,8 @@ func TestCreateSanitizedImpExt(t *testing.T) { "skadn": json.RawMessage(`"anySKAdNetwork"`), "gpid": json.RawMessage(`"anyGPID"`), "tid": json.RawMessage(`"anyTID"`), + "igs": json.RawMessage(`"anyIGS"`), + "paapi": json.RawMessage(`"anyPAAPI"`), }, givenImpExtPrebid: map[string]json.RawMessage{ "bidder": json.RawMessage(`"anyBidder"`), @@ -374,6 +396,8 @@ func TestCreateSanitizedImpExt(t *testing.T) { "skadn": json.RawMessage(`"anySKAdNetwork"`), "gpid": json.RawMessage(`"anyGPID"`), "tid": json.RawMessage(`"anyTID"`), + "igs": json.RawMessage(`"anyIGS"`), + "paapi": json.RawMessage(`"anyPAAPI"`), }, expectedError: "", }, @@ -387,6 +411,8 @@ func TestCreateSanitizedImpExt(t *testing.T) { "skadn": json.RawMessage(`"anySKAdNetwork"`), "gpid": json.RawMessage(`"anyGPID"`), "tid": json.RawMessage(`"anyTID"`), + "igs": json.RawMessage(`"anyIGS"`), + "paapi": json.RawMessage(`"anyPAAPI"`), }, givenImpExtPrebid: map[string]json.RawMessage{ "bidder": json.RawMessage(`"anyBidder"`), @@ -400,6 +426,8 @@ func TestCreateSanitizedImpExt(t *testing.T) { "skadn": json.RawMessage(`"anySKAdNetwork"`), "gpid": json.RawMessage(`"anyGPID"`), "tid": json.RawMessage(`"anyTID"`), + "igs": json.RawMessage(`"anyIGS"`), + "paapi": json.RawMessage(`"anyPAAPI"`), }, expectedError: "", }, @@ -412,6 +440,8 @@ func TestCreateSanitizedImpExt(t *testing.T) { "skadn": json.RawMessage(`"anySKAdNetwork"`), "gpid": json.RawMessage(`"anyGPID"`), "tid": json.RawMessage(`"anyTID"`), + "igs": json.RawMessage(`"anyIGS"`), + "paapi": json.RawMessage(`"anyPAAPI"`), }, givenImpExtPrebid: map[string]json.RawMessage{ "options": json.RawMessage(`malformed`), // String value without quotes. diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index e12316f7e72..597ed0a88d2 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -264,6 +264,14 @@ const ( BidderReservedSKAdN BidderName = "skadn" // Reserved for Apple's SKAdNetwork OpenRTB extension. BidderReservedTID BidderName = "tid" // Reserved for Per-Impression Transactions IDs for Multi-Impression Bid Requests. BidderReservedAE BidderName = "ae" // Reserved for FLEDGE Auction Environment + + // HACK: Support the keys used by the PAAPI (Protected Audience API) module. + // Without the hack, PBS will reject any requests that contain them. This hack + // can be removed once the upstream repo supports them. For more info, see: + // https://docs.prebid.org/dev-docs/modules/paapi.html + // https://github.com/prebid/prebid-server/issues/3735 + BidderReservedIGS BidderName = "igs" + BidderReservedPAAPI BidderName = "paapi" ) // IsBidderNameReserved returns true if the specified name is a case insensitive match for a reserved bidder name. @@ -304,6 +312,19 @@ func IsBidderNameReserved(name string) bool { return true } + // HACK: Support the keys used by the PAAPI (Protected Audience API) module. + // Without the hack, PBS will reject any requests that contain them. This hack + // can be removed once the upstream repo supports them. For more info, see: + // https://docs.prebid.org/dev-docs/modules/paapi.html + // https://github.com/prebid/prebid-server/issues/3735 + if strings.EqualFold(name, string(BidderReservedIGS)) { + return true + } + + if strings.EqualFold(name, string(BidderReservedPAAPI)) { + return true + } + return false } diff --git a/openrtb_ext/bidders_test.go b/openrtb_ext/bidders_test.go index 2176c28e184..af74b848a0f 100644 --- a/openrtb_ext/bidders_test.go +++ b/openrtb_ext/bidders_test.go @@ -110,6 +110,12 @@ func TestIsBidderNameReserved(t *testing.T) { {"gpid", true}, {"GPID", true}, {"GPid", true}, + {"igs", true}, + {"IGS", true}, + {"Igs", true}, + {"paapi", true}, + {"PAAPI", true}, + {"PAApi", true}, {"prebid", true}, {"PREbid", true}, {"PREBID", true}, diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index f5418ba4d1f..e44ab206273 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -28,6 +28,14 @@ const TIDKey = "tid" // AuctionEnvironmentKey is the json key under imp[].ext for ExtImp.AuctionEnvironment const AuctionEnvironmentKey = string(BidderReservedAE) +// HACK: Support the keys used by the PAAPI (Protected Audience API) module. +// Without the hack, PBS will reject any requests that contain them. This hack +// can be removed once the upstream repo supports them. For more info, see: +// https://docs.prebid.org/dev-docs/modules/paapi.html +// https://github.com/prebid/prebid-server/issues/3735 +const IGSKey = "igs" +const PAAPIKey = "paapi" + // NativeExchangeSpecificLowerBound defines the lower threshold of exchange specific types for native ads. There is no upper bound. const NativeExchangeSpecificLowerBound = 500 From 4b92838e08946b2cc0ce50855f9793f11f5a3581 Mon Sep 17 00:00:00 2001 From: Derek Bruneau <derek.bruneau@gmail.com> Date: Wed, 30 Oct 2024 11:39:04 -0400 Subject: [PATCH 113/125] Replacing "hb_pb" with "app" in targeting KVs for mobile app requests --- .../events-vast-account-off-request-off.json | 2 +- .../events-vast-account-off-request-on.json | 2 +- .../events-vast-account-on-request-off.json | 2 +- exchange/exchangetest/targeting-mobile.json | 10 +++++----- exchange/targeting.go | 11 ++++++++++- openrtb_ext/bid.go | 7 +++++++ 6 files changed, 25 insertions(+), 9 deletions(-) diff --git a/exchange/exchangetest/events-vast-account-off-request-off.json b/exchange/exchangetest/events-vast-account-off-request-off.json index a94546d2aa0..e57fcaa527a 100644 --- a/exchange/exchangetest/events-vast-account-off-request-off.json +++ b/exchange/exchangetest/events-vast-account-off-request-off.json @@ -151,7 +151,7 @@ "hb_cache_host": "www.pbcserver.com", "hb_cache_path": "/pbcache/endpoint", "hb_env": "mobile-app", - "hb_pb": "0.70", + "app": "0.70", "hb_size": "200x250" } } diff --git a/exchange/exchangetest/events-vast-account-off-request-on.json b/exchange/exchangetest/events-vast-account-off-request-on.json index 275b8aef2ce..93269faa2fd 100644 --- a/exchange/exchangetest/events-vast-account-off-request-on.json +++ b/exchange/exchangetest/events-vast-account-off-request-on.json @@ -160,7 +160,7 @@ "hb_cache_host": "www.pbcserver.com", "hb_cache_path": "/pbcache/endpoint", "hb_env": "mobile-app", - "hb_pb": "0.70", + "app": "0.70", "hb_size": "200x250" } } diff --git a/exchange/exchangetest/events-vast-account-on-request-off.json b/exchange/exchangetest/events-vast-account-on-request-off.json index e271ecf5c8f..17b9a0895a1 100644 --- a/exchange/exchangetest/events-vast-account-on-request-off.json +++ b/exchange/exchangetest/events-vast-account-on-request-off.json @@ -153,7 +153,7 @@ "hb_cache_host": "www.pbcserver.com", "hb_cache_path": "/pbcache/endpoint", "hb_env": "mobile-app", - "hb_pb": "0.70", + "app": "0.70", "hb_size": "200x250" } } diff --git a/exchange/exchangetest/targeting-mobile.json b/exchange/exchangetest/targeting-mobile.json index 914a557bca9..aeed8fc2ceb 100644 --- a/exchange/exchangetest/targeting-mobile.json +++ b/exchange/exchangetest/targeting-mobile.json @@ -157,7 +157,7 @@ "hb_bidder_audienceNe": "audienceNetwork", "hb_cache_host_audien": "www.pbcserver.com", "hb_cache_path_audien": "/pbcache/endpoint", - "hb_pb_audienceNetwor": "0.50", + "app_audienceNetwork": "0.50", "hb_size_audienceNetw": "200x250", "hb_env_audienceNetwo": "mobile-app" } @@ -187,8 +187,8 @@ "hb_cache_path": "/pbcache/endpoint", "hb_cache_path_appnex": "/pbcache/endpoint", "hb_bidder_appnexus": "appnexus", - "hb_pb": "0.70", - "hb_pb_appnexus": "0.70", + "app": "0.70", + "app_appnexus": "0.70", "hb_size": "200x250", "hb_size_appnexus": "200x250", "hb_env": "mobile-app", @@ -229,8 +229,8 @@ "hb_cache_path": "/pbcache/endpoint", "hb_cache_path_appnex": "/pbcache/endpoint", "hb_bidder_appnexus": "appnexus", - "hb_pb": "0.60", - "hb_pb_appnexus": "0.60", + "app": "0.60", + "app_appnexus": "0.60", "hb_size": "300x500", "hb_size_appnexus": "300x500", "hb_env": "mobile-app", diff --git a/exchange/targeting.go b/exchange/targeting.go index dbbf10041c9..20b5520ab80 100644 --- a/exchange/targeting.go +++ b/exchange/targeting.go @@ -63,7 +63,16 @@ func (targData *targetData) setTargeting(auc *auction, isApp bool, categoryMappi targets := make(map[string]string, 10) if cpm, ok := auc.roundedPrices[topBid]; ok { - targData.addKeys(targets, openrtb_ext.HbpbConstantKey, cpm, targetingBidderCode, isOverallWinner, truncateTargetAttr) + // HACK: Just for mobile apps, use an alternate name for this key. We're + // doing this so that we can separately target app and web line items in + // GAM without updating our existing web line items. We can revert to + // using the original key in both cases when/if we update our line items, + // e.g. by looking at whether the hb_env KV equals "mobile-app". + if isApp { + targData.addKeys(targets, openrtb_ext.AppConstantKey, cpm, targetingBidderCode, isOverallWinner, truncateTargetAttr) + } else { + targData.addKeys(targets, openrtb_ext.HbpbConstantKey, cpm, targetingBidderCode, isOverallWinner, truncateTargetAttr) + } } targData.addKeys(targets, openrtb_ext.HbBidderConstantKey, string(targetingBidderCode), targetingBidderCode, isOverallWinner, truncateTargetAttr) if hbSize := makeHbSize(topBid.Bid); hbSize != "" { diff --git a/openrtb_ext/bid.go b/openrtb_ext/bid.go index 2e190389212..8c939d75dfe 100644 --- a/openrtb_ext/bid.go +++ b/openrtb_ext/bid.go @@ -132,6 +132,13 @@ type TargetingKey string const ( HbpbConstantKey TargetingKey = "hb_pb" + // HACK: Just for mobile apps, use an alternate name for the key above. We're + // doing this so that we can separately target app and web line items in GAM + // without updating our existing web line items. We can remove this hack + // when/if we update them to exclude apps, e.g. by also requiring the hb_env + // KV to not equal "mobile-app". + AppConstantKey TargetingKey = "app" + // HbEnvKey exists to support the Prebid Universal Creative. If it exists, the only legal value is mobile-app. // It will exist only if the incoming bidRequest defined request.app instead of request.site. HbEnvKey TargetingKey = "hb_env" From 5a36e9eeff757d473fc12d94b0640f794d16ae60 Mon Sep 17 00:00:00 2001 From: Derek Bruneau <derek.bruneau@gmail.com> Date: Mon, 18 Nov 2024 11:04:31 -0500 Subject: [PATCH 114/125] Revert "[CU-86b2pc510] Replacing "hb_pb" with "app" in targeting KVs for mobile app requests" --- .../events-vast-account-off-request-off.json | 2 +- .../events-vast-account-off-request-on.json | 2 +- .../events-vast-account-on-request-off.json | 2 +- exchange/exchangetest/targeting-mobile.json | 10 +++++----- exchange/targeting.go | 11 +---------- openrtb_ext/bid.go | 7 ------- 6 files changed, 9 insertions(+), 25 deletions(-) diff --git a/exchange/exchangetest/events-vast-account-off-request-off.json b/exchange/exchangetest/events-vast-account-off-request-off.json index e57fcaa527a..a94546d2aa0 100644 --- a/exchange/exchangetest/events-vast-account-off-request-off.json +++ b/exchange/exchangetest/events-vast-account-off-request-off.json @@ -151,7 +151,7 @@ "hb_cache_host": "www.pbcserver.com", "hb_cache_path": "/pbcache/endpoint", "hb_env": "mobile-app", - "app": "0.70", + "hb_pb": "0.70", "hb_size": "200x250" } } diff --git a/exchange/exchangetest/events-vast-account-off-request-on.json b/exchange/exchangetest/events-vast-account-off-request-on.json index 93269faa2fd..275b8aef2ce 100644 --- a/exchange/exchangetest/events-vast-account-off-request-on.json +++ b/exchange/exchangetest/events-vast-account-off-request-on.json @@ -160,7 +160,7 @@ "hb_cache_host": "www.pbcserver.com", "hb_cache_path": "/pbcache/endpoint", "hb_env": "mobile-app", - "app": "0.70", + "hb_pb": "0.70", "hb_size": "200x250" } } diff --git a/exchange/exchangetest/events-vast-account-on-request-off.json b/exchange/exchangetest/events-vast-account-on-request-off.json index 17b9a0895a1..e271ecf5c8f 100644 --- a/exchange/exchangetest/events-vast-account-on-request-off.json +++ b/exchange/exchangetest/events-vast-account-on-request-off.json @@ -153,7 +153,7 @@ "hb_cache_host": "www.pbcserver.com", "hb_cache_path": "/pbcache/endpoint", "hb_env": "mobile-app", - "app": "0.70", + "hb_pb": "0.70", "hb_size": "200x250" } } diff --git a/exchange/exchangetest/targeting-mobile.json b/exchange/exchangetest/targeting-mobile.json index aeed8fc2ceb..914a557bca9 100644 --- a/exchange/exchangetest/targeting-mobile.json +++ b/exchange/exchangetest/targeting-mobile.json @@ -157,7 +157,7 @@ "hb_bidder_audienceNe": "audienceNetwork", "hb_cache_host_audien": "www.pbcserver.com", "hb_cache_path_audien": "/pbcache/endpoint", - "app_audienceNetwork": "0.50", + "hb_pb_audienceNetwor": "0.50", "hb_size_audienceNetw": "200x250", "hb_env_audienceNetwo": "mobile-app" } @@ -187,8 +187,8 @@ "hb_cache_path": "/pbcache/endpoint", "hb_cache_path_appnex": "/pbcache/endpoint", "hb_bidder_appnexus": "appnexus", - "app": "0.70", - "app_appnexus": "0.70", + "hb_pb": "0.70", + "hb_pb_appnexus": "0.70", "hb_size": "200x250", "hb_size_appnexus": "200x250", "hb_env": "mobile-app", @@ -229,8 +229,8 @@ "hb_cache_path": "/pbcache/endpoint", "hb_cache_path_appnex": "/pbcache/endpoint", "hb_bidder_appnexus": "appnexus", - "app": "0.60", - "app_appnexus": "0.60", + "hb_pb": "0.60", + "hb_pb_appnexus": "0.60", "hb_size": "300x500", "hb_size_appnexus": "300x500", "hb_env": "mobile-app", diff --git a/exchange/targeting.go b/exchange/targeting.go index 20b5520ab80..dbbf10041c9 100644 --- a/exchange/targeting.go +++ b/exchange/targeting.go @@ -63,16 +63,7 @@ func (targData *targetData) setTargeting(auc *auction, isApp bool, categoryMappi targets := make(map[string]string, 10) if cpm, ok := auc.roundedPrices[topBid]; ok { - // HACK: Just for mobile apps, use an alternate name for this key. We're - // doing this so that we can separately target app and web line items in - // GAM without updating our existing web line items. We can revert to - // using the original key in both cases when/if we update our line items, - // e.g. by looking at whether the hb_env KV equals "mobile-app". - if isApp { - targData.addKeys(targets, openrtb_ext.AppConstantKey, cpm, targetingBidderCode, isOverallWinner, truncateTargetAttr) - } else { - targData.addKeys(targets, openrtb_ext.HbpbConstantKey, cpm, targetingBidderCode, isOverallWinner, truncateTargetAttr) - } + targData.addKeys(targets, openrtb_ext.HbpbConstantKey, cpm, targetingBidderCode, isOverallWinner, truncateTargetAttr) } targData.addKeys(targets, openrtb_ext.HbBidderConstantKey, string(targetingBidderCode), targetingBidderCode, isOverallWinner, truncateTargetAttr) if hbSize := makeHbSize(topBid.Bid); hbSize != "" { diff --git a/openrtb_ext/bid.go b/openrtb_ext/bid.go index 8c939d75dfe..2e190389212 100644 --- a/openrtb_ext/bid.go +++ b/openrtb_ext/bid.go @@ -132,13 +132,6 @@ type TargetingKey string const ( HbpbConstantKey TargetingKey = "hb_pb" - // HACK: Just for mobile apps, use an alternate name for the key above. We're - // doing this so that we can separately target app and web line items in GAM - // without updating our existing web line items. We can remove this hack - // when/if we update them to exclude apps, e.g. by also requiring the hb_env - // KV to not equal "mobile-app". - AppConstantKey TargetingKey = "app" - // HbEnvKey exists to support the Prebid Universal Creative. If it exists, the only legal value is mobile-app. // It will exist only if the incoming bidRequest defined request.app instead of request.site. HbEnvKey TargetingKey = "hb_env" From 5da886abed463444f712973f90f0bb5508bd1bb8 Mon Sep 17 00:00:00 2001 From: Derek Bruneau <derek.bruneau@gmail.com> Date: Thu, 21 Nov 2024 16:37:48 -0500 Subject: [PATCH 115/125] Configuring price granularity for mobile app requests to match GAM line items --- .../data/by_id/stored_requests/playwire.json | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/stored_requests/data/by_id/stored_requests/playwire.json b/stored_requests/data/by_id/stored_requests/playwire.json index 3700ebc67ff..8a710dae0a1 100644 --- a/stored_requests/data/by_id/stored_requests/playwire.json +++ b/stored_requests/data/by_id/stored_requests/playwire.json @@ -6,17 +6,26 @@ "targeting": { "includewinners": true, "includebidderkeys": true, - "mediatypepricegranularity": { - "banner": { - "ranges": [ - { - "max": 50, - "min": 0, - "increment": 0.01 - } - ], - "precision": 3 - } + "pricegranularity": { + "ranges": [ + { + "max": 3, + "increment": 0.01 + }, + { + "max": 8, + "increment": 0.05 + }, + { + "max": 15, + "increment": 0.25 + }, + { + "max": 30, + "increment": 1 + } + ], + "precision": 2 } }, "cache": { From 7e3a78fa54beba2dd1f0703417aff48b8cfb0151 Mon Sep 17 00:00:00 2001 From: Derek Bruneau <derek.bruneau@gmail.com> Date: Mon, 9 Dec 2024 15:39:14 -0500 Subject: [PATCH 116/125] Turning hard-coded stored request timeout into configurable value This borrows code from a later version of the upstream repo. --- config/config.go | 6 ++++++ config/config_test.go | 14 ++++++++++++++ endpoints/openrtb2/amp_auction.go | 2 +- endpoints/openrtb2/auction.go | 3 +-- endpoints/openrtb2/video_auction.go | 2 +- 5 files changed, 23 insertions(+), 4 deletions(-) diff --git a/config/config.go b/config/config.go index f503d0fcb59..f1bb87fa616 100644 --- a/config/config.go +++ b/config/config.go @@ -55,6 +55,8 @@ type Configuration struct { // Note that StoredVideo refers to stored video requests, and has nothing to do with caching video creatives. StoredVideo StoredRequests `mapstructure:"stored_video_req"` StoredResponses StoredRequests `mapstructure:"stored_responses"` + // StoredRequestsTimeout defines the number of milliseconds before a timeout occurs with stored requests fetch + StoredRequestsTimeout int `mapstructure:"stored_requests_timeout_ms"` MaxRequestSize int64 `mapstructure:"max_request_size"` Analytics Analytics `mapstructure:"analytics"` @@ -125,6 +127,9 @@ func (cfg *Configuration) validate(v *viper.Viper) []error { errs = cfg.AuctionTimeouts.validate(errs) errs = cfg.StoredRequests.validate(errs) errs = cfg.StoredRequestsAMP.validate(errs) + if cfg.StoredRequestsTimeout <= 0 { + errs = append(errs, fmt.Errorf("cfg.stored_requests_timeout_ms must be > 0. Got %d", cfg.StoredRequestsTimeout)) + } errs = cfg.Accounts.validate(errs) errs = cfg.CategoryMapping.validate(errs) errs = cfg.StoredVideo.validate(errs) @@ -894,6 +899,7 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { v.SetDefault("category_mapping.filesystem.enabled", true) v.SetDefault("category_mapping.filesystem.directorypath", "./static/category-mapping") v.SetDefault("category_mapping.http.endpoint", "") + v.SetDefault("stored_requests_timeout_ms", 50) v.SetDefault("stored_requests.filesystem.enabled", false) v.SetDefault("stored_requests.filesystem.directorypath", "./stored_requests/data/by_id") v.SetDefault("stored_requests.directorypath", "./stored_requests/data/by_id") diff --git a/config/config_test.go b/config/config_test.go index 057ed06ecc4..f7b75317766 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -162,6 +162,7 @@ func TestDefaults(t *testing.T) { cmpBools(t, "adapter_connections_metrics", true, cfg.Metrics.Disabled.AdapterConnectionMetrics) cmpBools(t, "adapter_gdpr_request_blocked", false, cfg.Metrics.Disabled.AdapterGDPRRequestBlocked) cmpStrings(t, "certificates_file", "", cfg.PemCertsFile) + cmpInts(t, "stored_requests_timeout_ms", 50, cfg.StoredRequestsTimeout) cmpBools(t, "stored_requests.filesystem.enabled", false, cfg.StoredRequests.Files.Enabled) cmpStrings(t, "stored_requests.filesystem.directorypath", "./stored_requests/data/by_id", cfg.StoredRequests.Files.Path) cmpBools(t, "auto_gen_source_tid", true, cfg.AutoGenSourceTID) @@ -386,6 +387,7 @@ external_url: http://prebid-server.prebid.org/ host: prebid-server.prebid.org port: 1234 admin_port: 5678 +stored_requests_timeout_ms: 75 enable_gzip: false compression: request: @@ -549,6 +551,7 @@ func TestFullConfig(t *testing.T) { cmpInts(t, "garbage_collector_threshold", 1, cfg.GarbageCollectorThreshold) cmpInts(t, "auction_timeouts_ms.default", 50, int(cfg.AuctionTimeouts.Default)) cmpInts(t, "auction_timeouts_ms.max", 123, int(cfg.AuctionTimeouts.Max)) + cmpInts(t, "stored_request_timeout_ms", 75, cfg.StoredRequestsTimeout) cmpStrings(t, "cache.scheme", "http", cfg.CacheURL.Scheme) cmpStrings(t, "cache.host", "prebidcache.net", cfg.CacheURL.Host) cmpStrings(t, "cache.query", "uuid=%PBS_CACHE_UUID%", cfg.CacheURL.Query) @@ -789,6 +792,7 @@ func TestValidateConfig(t *testing.T) { Purpose10: TCF2Purpose{EnforceAlgo: TCF2EnforceAlgoFull}, }, }, + StoredRequestsTimeout: 50, StoredRequests: StoredRequests{ Files: FileFetcherConfig{Enabled: true}, InMemoryCache: InMemoryCache{ @@ -2755,6 +2759,16 @@ func TestIsConfigInfoPresent(t *testing.T) { } } +func TestNegativeOrZeroStoredRequestsTimeout(t *testing.T) { + cfg, v := newDefaultConfig(t) + + cfg.StoredRequestsTimeout = -1 + assertOneError(t, cfg.validate(v), "cfg.stored_requests_timeout_ms must be > 0. Got -1") + + cfg.StoredRequestsTimeout = 0 + assertOneError(t, cfg.validate(v), "cfg.stored_requests_timeout_ms must be > 0. Got 0") +} + func TestNegativeRequestSize(t *testing.T) { cfg, v := newDefaultConfig(t) cfg.MaxRequestSize = -1 diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index 2879610d8e4..8059cdc7c75 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -509,7 +509,7 @@ func (deps *endpointDeps) loadRequestJSONForAmp(httpRequest *http.Request) (req return nil, nil, nil, nil, []error{err} } - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(storedRequestTimeoutMillis)*time.Millisecond) + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(deps.cfg.StoredRequestsTimeout)*time.Millisecond) defer cancel() storedRequests, _, errs := deps.storedReqFetcher.FetchRequests(ctx, []string{ampParams.StoredRequestID}, nil) diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 36cca596022..9b3010b72d8 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -57,7 +57,6 @@ import ( "github.com/prebid/prebid-server/version" ) -const storedRequestTimeoutMillis = 50 const ampChannel = "amp" const appChannel = "app" @@ -443,7 +442,7 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metric return } - timeout := parseTimeout(requestJson, time.Duration(storedRequestTimeoutMillis)*time.Millisecond) + timeout := parseTimeout(requestJson, time.Duration(deps.cfg.StoredRequestsTimeout)*time.Millisecond) ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index a2a519fa8df..a6dae32ab9b 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -495,7 +495,7 @@ func createImpressionTemplate(imp openrtb2.Imp, video *openrtb2.Video) openrtb2. } func (deps *endpointDeps) loadStoredImp(storedImpId string) (openrtb2.Imp, []error) { - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(storedRequestTimeoutMillis)*time.Millisecond) + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(deps.cfg.StoredRequestsTimeout)*time.Millisecond) defer cancel() impr := openrtb2.Imp{} From 5dd9fe977dd77899ffc62d3a552fe325ac9f0667 Mon Sep 17 00:00:00 2001 From: Derek Bruneau <derek.bruneau@gmail.com> Date: Tue, 17 Dec 2024 17:15:36 -0500 Subject: [PATCH 117/125] Percent-encoding unsafe brackets in stored request URLs --- stored_requests/backends/http_fetcher/fetcher.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/stored_requests/backends/http_fetcher/fetcher.go b/stored_requests/backends/http_fetcher/fetcher.go index df55fab33f3..5f4872ab520 100644 --- a/stored_requests/backends/http_fetcher/fetcher.go +++ b/stored_requests/backends/http_fetcher/fetcher.go @@ -222,12 +222,21 @@ func (fetcher *HttpFetcher) FetchCategories(ctx context.Context, primaryAdServer } func buildRequest(endpoint string, requestIDs []string, impIDs []string) (*http.Request, error) { + // Percent-encode the '[', ']', and '"' characters in the query string since + // they're unsafe in URLs. The Config File Server, for example, rejects + // unencoded double quotes, and CloudFront won't let you invalidate URLs that + // contain unencoded brackets. + // + // See the following open issue for the status of a possible upstream fix: + // + // https://github.com/prebid/prebid-server/issues/3595 + // if len(requestIDs) > 0 && len(impIDs) > 0 { - return http.NewRequest("GET", endpoint+"request-ids=[%22"+strings.Join(requestIDs, "%22,%22")+"%22]&imp-ids=[%22"+strings.Join(impIDs, "%22,%22")+"%22]", nil) + return http.NewRequest("GET", endpoint+"request-ids=%5B%22"+strings.Join(requestIDs, "%22,%22")+"%22%5D&imp-ids=%5B%22"+strings.Join(impIDs, "%22,%22")+"%22%5D", nil) } else if len(requestIDs) > 0 { - return http.NewRequest("GET", endpoint+"request-ids=[%22"+strings.Join(requestIDs, "%22,%22")+"%22]", nil) + return http.NewRequest("GET", endpoint+"request-ids=%5B%22"+strings.Join(requestIDs, "%22,%22")+"%22%5D", nil) } else { - return http.NewRequest("GET", endpoint+"imp-ids=[%22"+strings.Join(impIDs, "%22,%22")+"%22]", nil) + return http.NewRequest("GET", endpoint+"imp-ids=%5B%22"+strings.Join(impIDs, "%22,%22")+"%22%5D", nil) } } From e90219e5b4815f9c448c0f7c0b66bed05a27b332 Mon Sep 17 00:00:00 2001 From: Derek Bruneau <derek.bruneau@gmail.com> Date: Tue, 17 Dec 2024 17:44:39 -0500 Subject: [PATCH 118/125] Removing obsolete local stored imps and stored requests --- .../data/by_id/stored_imps/.gitignore | 3 ++ .../stored_imps/banner_300x250_android.json | 47 ------------------- .../by_id/stored_imps/banner_300x250_ios.json | 47 ------------------- .../stored_imps/banner_320x100_android.json | 47 ------------------- .../by_id/stored_imps/banner_320x100_ios.json | 47 ------------------- .../stored_imps/banner_320x50_android.json | 47 ------------------- .../by_id/stored_imps/banner_320x50_ios.json | 47 ------------------- .../stored_imps/banner_728x90_android.json | 47 ------------------- .../by_id/stored_imps/banner_728x90_ios.json | 47 ------------------- .../stored_imps/content_top_android.json | 40 ---------------- .../stored_imps/content_top_android_test.json | 40 ---------------- .../by_id/stored_imps/game_over_android.json | 44 ----------------- .../stored_imps/game_over_android_test.json | 44 ----------------- .../data/by_id/stored_imps/game_over_ios.json | 44 ----------------- .../by_id/stored_imps/game_over_ios_test.json | 44 ----------------- .../by_id/stored_imps/in_article_android.json | 44 ----------------- .../stored_imps/in_article_android_test.json | 44 ----------------- .../by_id/stored_imps/in_article_ios.json | 44 ----------------- .../stored_imps/in_article_ios_test.json | 44 ----------------- .../interstitial_1024x768_android.json | 39 --------------- .../interstitial_1024x768_ios.json | 39 --------------- .../interstitial_320x480_android.json | 39 --------------- .../stored_imps/interstitial_320x480_ios.json | 39 --------------- .../interstitial_480x320_android.json | 40 ---------------- .../stored_imps/interstitial_480x320_ios.json | 39 --------------- .../interstitial_480x640v_android.json | 40 ---------------- .../interstitial_480x640v_ios.json | 39 --------------- .../interstitial_640x480v_android.json | 39 --------------- .../interstitial_640x480v_ios.json | 39 --------------- .../interstitial_768x1024_android.json | 39 --------------- .../interstitial_768x1024_ios.json | 39 --------------- .../by_id/stored_imps/play_screen_ios.json | 40 ---------------- .../stored_imps/play_screen_ios_test.json | 40 ---------------- .../data/by_id/stored_requests/.gitignore | 3 ++ .../by_id/stored_requests/in_article_ios.json | 40 ---------------- .../data/by_id/stored_requests/playwire.json | 37 --------------- 36 files changed, 6 insertions(+), 1435 deletions(-) create mode 100644 stored_requests/data/by_id/stored_imps/.gitignore delete mode 100644 stored_requests/data/by_id/stored_imps/banner_300x250_android.json delete mode 100644 stored_requests/data/by_id/stored_imps/banner_300x250_ios.json delete mode 100644 stored_requests/data/by_id/stored_imps/banner_320x100_android.json delete mode 100644 stored_requests/data/by_id/stored_imps/banner_320x100_ios.json delete mode 100644 stored_requests/data/by_id/stored_imps/banner_320x50_android.json delete mode 100644 stored_requests/data/by_id/stored_imps/banner_320x50_ios.json delete mode 100644 stored_requests/data/by_id/stored_imps/banner_728x90_android.json delete mode 100644 stored_requests/data/by_id/stored_imps/banner_728x90_ios.json delete mode 100644 stored_requests/data/by_id/stored_imps/content_top_android.json delete mode 100644 stored_requests/data/by_id/stored_imps/content_top_android_test.json delete mode 100644 stored_requests/data/by_id/stored_imps/game_over_android.json delete mode 100644 stored_requests/data/by_id/stored_imps/game_over_android_test.json delete mode 100644 stored_requests/data/by_id/stored_imps/game_over_ios.json delete mode 100644 stored_requests/data/by_id/stored_imps/game_over_ios_test.json delete mode 100644 stored_requests/data/by_id/stored_imps/in_article_android.json delete mode 100644 stored_requests/data/by_id/stored_imps/in_article_android_test.json delete mode 100644 stored_requests/data/by_id/stored_imps/in_article_ios.json delete mode 100644 stored_requests/data/by_id/stored_imps/in_article_ios_test.json delete mode 100644 stored_requests/data/by_id/stored_imps/interstitial_1024x768_android.json delete mode 100644 stored_requests/data/by_id/stored_imps/interstitial_1024x768_ios.json delete mode 100644 stored_requests/data/by_id/stored_imps/interstitial_320x480_android.json delete mode 100644 stored_requests/data/by_id/stored_imps/interstitial_320x480_ios.json delete mode 100644 stored_requests/data/by_id/stored_imps/interstitial_480x320_android.json delete mode 100644 stored_requests/data/by_id/stored_imps/interstitial_480x320_ios.json delete mode 100644 stored_requests/data/by_id/stored_imps/interstitial_480x640v_android.json delete mode 100644 stored_requests/data/by_id/stored_imps/interstitial_480x640v_ios.json delete mode 100644 stored_requests/data/by_id/stored_imps/interstitial_640x480v_android.json delete mode 100644 stored_requests/data/by_id/stored_imps/interstitial_640x480v_ios.json delete mode 100644 stored_requests/data/by_id/stored_imps/interstitial_768x1024_android.json delete mode 100644 stored_requests/data/by_id/stored_imps/interstitial_768x1024_ios.json delete mode 100644 stored_requests/data/by_id/stored_imps/play_screen_ios.json delete mode 100644 stored_requests/data/by_id/stored_imps/play_screen_ios_test.json create mode 100644 stored_requests/data/by_id/stored_requests/.gitignore delete mode 100644 stored_requests/data/by_id/stored_requests/in_article_ios.json delete mode 100644 stored_requests/data/by_id/stored_requests/playwire.json diff --git a/stored_requests/data/by_id/stored_imps/.gitignore b/stored_requests/data/by_id/stored_imps/.gitignore new file mode 100644 index 00000000000..ca0de93fdd9 --- /dev/null +++ b/stored_requests/data/by_id/stored_imps/.gitignore @@ -0,0 +1,3 @@ +# Ignore everything in this directory, except for this file +* +!.gitignore \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/banner_300x250_android.json b/stored_requests/data/by_id/stored_imps/banner_300x250_android.json deleted file mode 100644 index 14720534145..00000000000 --- a/stored_requests/data/by_id/stored_imps/banner_300x250_android.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "id": "banner_300x250_android", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "pubmatic": { - "publisherId": "158326", - "adSlot": "2165611" - }, - "triplelift": { - "inventoryCode": "pwm_android_300x250_pb" - }, - "between": { - "host": "lbs-us-east1.ads", - "publisher_id": "44634" - }, - "rubicon": { - "accountId": 12556, - "siteId": 376280, - "zoneId": 2369430 - }, - "sonobi": { - "TagID": "1b22f1a1d1a47a68b67b" - }, - "rhythmone": { - "placementId": "247568", - "zone": "1r", - "path": "mvo" - }, - "openx": { - "unit": "544086382", - "delDomain": "playwire-d.openx.net" - }, - "appnexus": { - "placement_id": 24773200 - }, - "yieldmo": { - "placementId": "2986450036092510476" - } - } -} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/banner_300x250_ios.json b/stored_requests/data/by_id/stored_imps/banner_300x250_ios.json deleted file mode 100644 index b8d8ff53531..00000000000 --- a/stored_requests/data/by_id/stored_imps/banner_300x250_ios.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "id": "banner_300x250_ios", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "pubmatic": { - "publisherId": "158326", - "adSlot": "2165608" - }, - "triplelift": { - "inventoryCode": "pwm_ios_300x250_pb" - }, - "between": { - "host": "lbs-us-east1.ads", - "publisher_id": "44634" - }, - "rubicon": { - "accountId": 12556, - "siteId": 376280, - "zoneId": 2369428 - }, - "sonobi": { - "TagID": "bc7f500b477b40d44f67" - }, - "rhythmone": { - "placementId": "247567", - "zone": "1r", - "path": "mvo" - }, - "openx": { - "unit": "544086381", - "delDomain": "playwire-d.openx.net" - }, - "appnexus": { - "placement_id": 24773199 - }, - "yieldmo": { - "placementId": "2986450035966681355" - } - } -} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/banner_320x100_android.json b/stored_requests/data/by_id/stored_imps/banner_320x100_android.json deleted file mode 100644 index 0cedc937d0e..00000000000 --- a/stored_requests/data/by_id/stored_imps/banner_320x100_android.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "id": "banner_320x100_android", - "banner": { - "format": [ - { - "w": 320, - "h": 100 - } - ] - }, - "ext": { - "pubmatic": { - "publisherId": "158326", - "adSlot": "2165611" - }, - "triplelift": { - "inventoryCode": "pwm_android_hdx_pb" - }, - "between": { - "host": "lbs-us-east1.ads", - "publisher_id": "44634" - }, - "rubicon": { - "accountId": 12556, - "siteId": 376280, - "zoneId": 2369430 - }, - "sonobi": { - "TagID": "c94aa51bb23253f97ee8" - }, - "rhythmone": { - "placementId": "247568", - "zone": "1r", - "path": "mvo" - }, - "openx": { - "unit": "544086382", - "delDomain": "playwire-d.openx.net" - }, - "appnexus": { - "placement_id": 24773200 - }, - "yieldmo": { - "placementId": "2986450036092510476" - } - } -} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/banner_320x100_ios.json b/stored_requests/data/by_id/stored_imps/banner_320x100_ios.json deleted file mode 100644 index b8703a55ab3..00000000000 --- a/stored_requests/data/by_id/stored_imps/banner_320x100_ios.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "id": "banner_320x100_ios", - "banner": { - "format": [ - { - "w": 320, - "h": 100 - } - ] - }, - "ext": { - "pubmatic": { - "publisherId": "158326", - "adSlot": "2165608" - }, - "triplelift": { - "inventoryCode": "pwm_ios_320x50_300x250_pb" - }, - "between": { - "host": "lbs-us-east1.ads", - "publisher_id": "44634" - }, - "rubicon": { - "accountId": 12556, - "siteId": 376280, - "zoneId": 2369428 - }, - "sonobi": { - "TagID": "589fed9631ab52cbe0dc" - }, - "rhythmone": { - "placementId": "247567", - "zone": "1r", - "path": "mvo" - }, - "openx": { - "unit": "544086381", - "delDomain": "playwire-d.openx.net" - }, - "appnexus": { - "placement_id": 24773199 - }, - "yieldmo": { - "placementId": "2986450035966681355" - } - } -} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/banner_320x50_android.json b/stored_requests/data/by_id/stored_imps/banner_320x50_android.json deleted file mode 100644 index dd83d868d24..00000000000 --- a/stored_requests/data/by_id/stored_imps/banner_320x50_android.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "id": "banner_320x50_android", - "banner": { - "format": [ - { - "w": 320, - "h": 50 - } - ] - }, - "ext": { - "pubmatic": { - "publisherId": "158326", - "adSlot": "2165611" - }, - "triplelift": { - "inventoryCode": "pwm_android_320x50_300x250_pb" - }, - "between": { - "host": "lbs-us-east1.ads", - "publisher_id": "44634" - }, - "rubicon": { - "accountId": 12556, - "siteId": 376280, - "zoneId": 2369430 - }, - "sonobi": { - "TagID": "5200294cfc91869f267d" - }, - "rhythmone": { - "placementId": "247568", - "zone": "1r", - "path": "mvo" - }, - "openx": { - "unit": "544086382", - "delDomain": "playwire-d.openx.net" - }, - "appnexus": { - "placement_id": 24773200 - }, - "yieldmo": { - "placementId": "2986450036092510476" - } - } -} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/banner_320x50_ios.json b/stored_requests/data/by_id/stored_imps/banner_320x50_ios.json deleted file mode 100644 index 8d7ff85c220..00000000000 --- a/stored_requests/data/by_id/stored_imps/banner_320x50_ios.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "id": "banner_320x50_ios", - "banner": { - "format": [ - { - "w": 320, - "h": 50 - } - ] - }, - "ext": { - "pubmatic": { - "publisherId": "158326", - "adSlot": "2165608" - }, - "triplelift": { - "inventoryCode": "pwm_ios_320x50_300x250_pb" - }, - "between": { - "host": "lbs-us-east1.ads", - "publisher_id": "44634" - }, - "rubicon": { - "accountId": 12556, - "siteId": 376280, - "zoneId": 2369428 - }, - "sonobi": { - "TagID": "cc1338cc8e2c6e7864dd" - }, - "rhythmone": { - "placementId": "247567", - "zone": "1r", - "path": "mvo" - }, - "openx": { - "unit": "544086381", - "delDomain": "playwire-d.openx.net" - }, - "appnexus": { - "placement_id": 24773199 - }, - "yieldmo": { - "placementId": "2986450035966681355" - } - } -} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/banner_728x90_android.json b/stored_requests/data/by_id/stored_imps/banner_728x90_android.json deleted file mode 100644 index 516eb0f7557..00000000000 --- a/stored_requests/data/by_id/stored_imps/banner_728x90_android.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "id": "banner_728x90_android", - "banner": { - "format": [ - { - "w": 728, - "h": 90 - } - ] - }, - "ext": { - "pubmatic": { - "publisherId": "158326", - "adSlot": "2165611" - }, - "triplelift": { - "inventoryCode": "pwm_android_320x50_300x250_pb" - }, - "between": { - "host": "lbs-us-east1.ads", - "publisher_id": "44634" - }, - "rubicon": { - "accountId": 12556, - "siteId": 376280, - "zoneId": 2369430 - }, - "sonobi": { - "TagID": "5defb02096c7ca6be2ac" - }, - "rhythmone": { - "placementId": "247568", - "zone": "1r", - "path": "mvo" - }, - "openx": { - "unit": "544086382", - "delDomain": "playwire-d.openx.net" - }, - "appnexus": { - "placement_id": 24773200 - }, - "yieldmo": { - "placementId": "2986450036092510476" - } - } -} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/banner_728x90_ios.json b/stored_requests/data/by_id/stored_imps/banner_728x90_ios.json deleted file mode 100644 index fa98f44e0c7..00000000000 --- a/stored_requests/data/by_id/stored_imps/banner_728x90_ios.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "id": "banner_728x90_ios", - "banner": { - "format": [ - { - "w": 728, - "h": 90 - } - ] - }, - "ext": { - "pubmatic": { - "publisherId": "158326", - "adSlot": "2165608" - }, - "triplelift": { - "inventoryCode": "pwm_ios_320x50_300x250_pb" - }, - "between": { - "host": "lbs-us-east1.ads", - "publisher_id": "44634" - }, - "rubicon": { - "accountId": 12556, - "siteId": 376280, - "zoneId": 2369428 - }, - "sonobi": { - "TagID": "b79e15de7e04b90543d3" - }, - "rhythmone": { - "placementId": "247567", - "zone": "1r", - "path": "mvo" - }, - "openx": { - "unit": "544086381", - "delDomain": "playwire-d.openx.net" - }, - "appnexus": { - "placement_id": 24773199 - }, - "yieldmo": { - "placementId": "2986450035966681355" - } - } -} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/content_top_android.json b/stored_requests/data/by_id/stored_imps/content_top_android.json deleted file mode 100644 index 354f79d988f..00000000000 --- a/stored_requests/data/by_id/stored_imps/content_top_android.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "id": "content_top_android", - "banner": { - "format": [ - { - "w": 320, - "h": 50 - } - ] - }, - "ext": { - "appnexus": { - "placement_id": 19689655 - }, - "openx": { - "unit": "544086382", - "delDomain": "playwire-d.openx.net" - }, - "pubmatic": { - "publisherId": "158326", - "adSlot": "3029233" - }, - "rhythmone": { - "placementId": "213696", - "zone": "1r", - "path": "mvo" - }, - "rubicon": { - "accountId": 12556, - "siteId": 376280, - "zoneId": 2061954 - }, - "sonobi": { - "TagID": "5200294cfc91869f267d" - }, - "triplelift": { - "inventoryCode": "pwm_android_hdx_pb" - } - } -} diff --git a/stored_requests/data/by_id/stored_imps/content_top_android_test.json b/stored_requests/data/by_id/stored_imps/content_top_android_test.json deleted file mode 100644 index 891916bad7c..00000000000 --- a/stored_requests/data/by_id/stored_imps/content_top_android_test.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "id": "content_top_android", - "banner": { - "format": [ - { - "w": 320, - "h": 50 - } - ] - }, - "ext": { - "appnexus": { - "placement_id": 19689655 - }, - "openx": { - "unit": "544086382", - "delDomain": "playwire-d.openx.net" - }, - "pubmatic": { - "publisherId": "158326", - "adSlot": "3029233" - }, - "rhythmone": { - "placementId": "213696", - "zone": "1r", - "path": "mvo" - }, - "rubicon": { - "accountId": 12556, - "siteId": 376280, - "zoneId": 2061954 - }, - "sonobi": { - "TagID": "5200294cfc91869f267d" - }, - "triplelift": { - "inventoryCode": "playwire_app_hdx_android_prebid_TEST" - } - } -} diff --git a/stored_requests/data/by_id/stored_imps/game_over_android.json b/stored_requests/data/by_id/stored_imps/game_over_android.json deleted file mode 100644 index f9bacc08f0e..00000000000 --- a/stored_requests/data/by_id/stored_imps/game_over_android.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "id": "game_over_android", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 320, - "h": 50 - } - ] - }, - "ext": { - "appnexus": { - "placement_id": 19689655 - }, - "openx": { - "unit": "544086382", - "delDomain": "playwire-d.openx.net" - }, - "pubmatic": { - "publisherId": "158326", - "adSlot": "1957467" - }, - "rhythmone": { - "placementId": "213696", - "zone": "1r", - "path": "mvo" - }, - "rubicon": { - "accountId": 12556, - "siteId": 376280, - "zoneId": 2061954 - }, - "sonobi": { - "TagID": "e67f243a24590037a75c" - }, - "triplelift": { - "inventoryCode": "pwm_android_320x50_300x250_pb" - } - } -} diff --git a/stored_requests/data/by_id/stored_imps/game_over_android_test.json b/stored_requests/data/by_id/stored_imps/game_over_android_test.json deleted file mode 100644 index 45a63673913..00000000000 --- a/stored_requests/data/by_id/stored_imps/game_over_android_test.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "id": "game_over_android", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 320, - "h": 50 - } - ] - }, - "ext": { - "appnexus": { - "placement_id": 19689655 - }, - "openx": { - "unit": "544086382", - "delDomain": "playwire-d.openx.net" - }, - "pubmatic": { - "publisherId": "158326", - "adSlot": "1957467" - }, - "rhythmone": { - "placementId": "213696", - "zone": "1r", - "path": "mvo" - }, - "rubicon": { - "accountId": 12556, - "siteId": 376280, - "zoneId": 2061954 - }, - "sonobi": { - "TagID": "e67f243a24590037a75c" - }, - "triplelift": { - "inventoryCode": "playwire_app_hdx_android_prebid_TEST" - } - } -} diff --git a/stored_requests/data/by_id/stored_imps/game_over_ios.json b/stored_requests/data/by_id/stored_imps/game_over_ios.json deleted file mode 100644 index 8b9111dcefe..00000000000 --- a/stored_requests/data/by_id/stored_imps/game_over_ios.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "id": "game_over_ios", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 320, - "h": 50 - } - ] - }, - "ext": { - "appnexus": { - "placement_id": 19689655 - }, - "openx": { - "unit": "544086381", - "delDomain": "playwire-d.openx.net" - }, - "pubmatic": { - "publisherId": "158326", - "adSlot": "1957464" - }, - "rhythmone": { - "placementId": "213696", - "zone": "1r", - "path": "mvo" - }, - "rubicon": { - "accountId": 12556, - "siteId": 376280, - "zoneId": 2061954 - }, - "sonobi": { - "TagID": "12eb86143abb80d6a9c6" - }, - "triplelift": { - "inventoryCode": "pwm_ios_320x50_300x250_pb" - } - } -} diff --git a/stored_requests/data/by_id/stored_imps/game_over_ios_test.json b/stored_requests/data/by_id/stored_imps/game_over_ios_test.json deleted file mode 100644 index 78b3496a834..00000000000 --- a/stored_requests/data/by_id/stored_imps/game_over_ios_test.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "id": "game_over_ios", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 320, - "h": 50 - } - ] - }, - "ext": { - "appnexus": { - "placement_id": 19689655 - }, - "openx": { - "unit": "544086381", - "delDomain": "playwire-d.openx.net" - }, - "pubmatic": { - "publisherId": "158326", - "adSlot": "1957464" - }, - "rhythmone": { - "placementId": "213696", - "zone": "1r", - "path": "mvo" - }, - "rubicon": { - "accountId": 12556, - "siteId": 376280, - "zoneId": 2061954 - }, - "sonobi": { - "TagID": "12eb86143abb80d6a9c6" - }, - "triplelift": { - "inventoryCode": "playwire_app_hdx_ios_prebid_TEST" - } - } -} diff --git a/stored_requests/data/by_id/stored_imps/in_article_android.json b/stored_requests/data/by_id/stored_imps/in_article_android.json deleted file mode 100644 index 6977485d867..00000000000 --- a/stored_requests/data/by_id/stored_imps/in_article_android.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "id": "in_article_android", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 320, - "h": 50 - } - ] - }, - "ext": { - "appnexus": { - "placement_id": 19689655 - }, - "openx": { - "unit": "544086382", - "delDomain": "playwire-d.openx.net" - }, - "pubmatic": { - "publisherId": "158326", - "adSlot": "1957467" - }, - "rhythmone": { - "placementId": "213696", - "zone": "1r", - "path": "mvo" - }, - "rubicon": { - "accountId": 12556, - "siteId": 376280, - "zoneId": 2061954 - }, - "sonobi": { - "TagID": "e67f243a24590037a75c" - }, - "triplelift": { - "inventoryCode": "pwm_android_320x50_300x250_pb" - } - } -} diff --git a/stored_requests/data/by_id/stored_imps/in_article_android_test.json b/stored_requests/data/by_id/stored_imps/in_article_android_test.json deleted file mode 100644 index b4dd2a860c6..00000000000 --- a/stored_requests/data/by_id/stored_imps/in_article_android_test.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "id": "in_article_android", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 320, - "h": 50 - } - ] - }, - "ext": { - "appnexus": { - "placement_id": 19689655 - }, - "openx": { - "unit": "544086382", - "delDomain": "playwire-d.openx.net" - }, - "pubmatic": { - "publisherId": "158326", - "adSlot": "1957467" - }, - "rhythmone": { - "placementId": "213696", - "zone": "1r", - "path": "mvo" - }, - "rubicon": { - "accountId": 12556, - "siteId": 376280, - "zoneId": 2061954 - }, - "sonobi": { - "TagID": "e67f243a24590037a75c" - }, - "triplelift": { - "inventoryCode": "playwire_app_hdx_android_prebid_TEST" - } - } -} diff --git a/stored_requests/data/by_id/stored_imps/in_article_ios.json b/stored_requests/data/by_id/stored_imps/in_article_ios.json deleted file mode 100644 index cd488fc1302..00000000000 --- a/stored_requests/data/by_id/stored_imps/in_article_ios.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "id": "in_article_ios", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 320, - "h": 50 - } - ] - }, - "ext": { - "appnexus": { - "placement_id": 19689654 - }, - "openx": { - "unit": "544086381", - "delDomain": "playwire-d.openx.net" - }, - "pubmatic": { - "publisherId": "158326", - "adSlot": "1957464" - }, - "rhythmone": { - "placementId": "213696", - "zone": "1r", - "path": "mvo" - }, - "rubicon": { - "accountId": 12556, - "siteId": 376280, - "zoneId": 2061954 - }, - "sonobi": { - "TagID": "12eb86143abb80d6a9c6" - }, - "triplelift": { - "inventoryCode": "pwm_ios_320x50_300x250_pb" - } - } -} diff --git a/stored_requests/data/by_id/stored_imps/in_article_ios_test.json b/stored_requests/data/by_id/stored_imps/in_article_ios_test.json deleted file mode 100644 index 4545cca603e..00000000000 --- a/stored_requests/data/by_id/stored_imps/in_article_ios_test.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "id": "in_article_ios", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 320, - "h": 50 - } - ] - }, - "ext": { - "appnexus": { - "placement_id": 19689654 - }, - "openx": { - "unit": "544086381", - "delDomain": "playwire-d.openx.net" - }, - "pubmatic": { - "publisherId": "158326", - "adSlot": "1957464" - }, - "rhythmone": { - "placementId": "213696", - "zone": "1r", - "path": "mvo" - }, - "rubicon": { - "accountId": 12556, - "siteId": 376280, - "zoneId": 2061954 - }, - "sonobi": { - "TagID": "12eb86143abb80d6a9c6" - }, - "triplelift": { - "inventoryCode": "playwire_app_hdx_ios_prebid_TEST" - } - } -} diff --git a/stored_requests/data/by_id/stored_imps/interstitial_1024x768_android.json b/stored_requests/data/by_id/stored_imps/interstitial_1024x768_android.json deleted file mode 100644 index c537c83cda4..00000000000 --- a/stored_requests/data/by_id/stored_imps/interstitial_1024x768_android.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "id": "interstitial_1024x768_android", - "ext": { - "pubmatic": { - "publisherId": "158326", - "adSlot": "2165611" - }, - "triplelift": { - "inventoryCode": "Playwire_RON_Interstitial_Android" - }, - "between": { - "host": "lbs-us-east1.ads", - "publisher_id": "44634" - }, - "rubicon": { - "accountId": 12556, - "siteId": 376280, - "zoneId": 2369430 - }, - "sonobi": { - "TagID": "e75048f43948741bad48" - }, - "rhythmone": { - "placementId": "247568", - "zone": "1r", - "path": "mvo" - }, - "openx": { - "unit": "558236878", - "delDomain": "playwire-d.openx.net" - }, - "appnexus": { - "placement_id": 24773200 - }, - "yieldmo": { - "placementId": "3124076396956033378" - } - } -} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/interstitial_1024x768_ios.json b/stored_requests/data/by_id/stored_imps/interstitial_1024x768_ios.json deleted file mode 100644 index d2c2197d06b..00000000000 --- a/stored_requests/data/by_id/stored_imps/interstitial_1024x768_ios.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "id": "interstitial_1024x768_ios", - "ext": { - "pubmatic": { - "publisherId": "158326", - "adSlot": "2165608" - }, - "triplelift": { - "inventoryCode": "Playwire_RON_Interstitial_IOS" - }, - "between": { - "host": "lbs-us-east1.ads", - "publisher_id": "44634" - }, - "rubicon": { - "accountId": 12556, - "siteId": 376280, - "zoneId": 2369428 - }, - "sonobi": { - "TagID": "e94911505445cf11c143" - }, - "rhythmone": { - "placementId": "247567", - "zone": "1r", - "path": "mvo" - }, - "openx": { - "unit": "558236877", - "delDomain": "playwire-d.openx.net" - }, - "appnexus": { - "placement_id": 24773199 - }, - "yieldmo": { - "placementId": "3124076397123805539" - } - } -} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/interstitial_320x480_android.json b/stored_requests/data/by_id/stored_imps/interstitial_320x480_android.json deleted file mode 100644 index fce6ca03e5e..00000000000 --- a/stored_requests/data/by_id/stored_imps/interstitial_320x480_android.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "id": "interstitial_320x480_android", - "ext": { - "pubmatic": { - "publisherId": "158326", - "adSlot": "2165611" - }, - "triplelift": { - "inventoryCode": "Playwire_RON_Interstitial_Android" - }, - "between": { - "host": "lbs-us-east1.ads", - "publisher_id": "44634" - }, - "rubicon": { - "accountId": 12556, - "siteId": 376280, - "zoneId": 2369430 - }, - "sonobi": { - "TagID": "e75048f43948741bad48" - }, - "rhythmone": { - "placementId": "247568", - "zone": "1r", - "path": "mvo" - }, - "openx": { - "unit": "558236878", - "delDomain": "playwire-d.openx.net" - }, - "appnexus": { - "placement_id": 24773200 - }, - "yieldmo": { - "placementId": "3124076396956033378" - } - } -} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/interstitial_320x480_ios.json b/stored_requests/data/by_id/stored_imps/interstitial_320x480_ios.json deleted file mode 100644 index 4e2c80b58ba..00000000000 --- a/stored_requests/data/by_id/stored_imps/interstitial_320x480_ios.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "id": "interstitial_320x480_ios", - "ext": { - "pubmatic": { - "publisherId": "158326", - "adSlot": "2165608" - }, - "triplelift": { - "inventoryCode": "Playwire_RON_Interstitial_IOS" - }, - "between": { - "host": "lbs-us-east1.ads", - "publisher_id": "44634" - }, - "rubicon": { - "accountId": 12556, - "siteId": 376280, - "zoneId": 2369428 - }, - "sonobi": { - "TagID": "e94911505445cf11c143" - }, - "rhythmone": { - "placementId": "247567", - "zone": "1r", - "path": "mvo" - }, - "openx": { - "unit": "558236877", - "delDomain": "playwire-d.openx.net" - }, - "appnexus": { - "placement_id": 24773199 - }, - "yieldmo": { - "placementId": "3124076397123805539" - } - } -} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/interstitial_480x320_android.json b/stored_requests/data/by_id/stored_imps/interstitial_480x320_android.json deleted file mode 100644 index 95b38c83f0a..00000000000 --- a/stored_requests/data/by_id/stored_imps/interstitial_480x320_android.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "id": "interstitial_480x320_android", - "interstitial": {}, - "ext": { - "pubmatic": { - "publisherId": "158326", - "adSlot": "2165611" - }, - "triplelift": { - "inventoryCode": "Playwire_RON_Interstitial_Android" - }, - "between": { - "host": "lbs-us-east1.ads", - "publisher_id": "44634" - }, - "rubicon": { - "accountId": 12556, - "siteId": 376280, - "zoneId": 2369430 - }, - "sonobi": { - "TagID": "e75048f43948741bad48" - }, - "rhythmone": { - "placementId": "247568", - "zone": "1r", - "path": "mvo" - }, - "openx": { - "unit": "558236878", - "delDomain": "playwire-d.openx.net" - }, - "appnexus": { - "placement_id": 24773200 - }, - "yieldmo": { - "placementId": "3124076396956033378" - } - } -} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/interstitial_480x320_ios.json b/stored_requests/data/by_id/stored_imps/interstitial_480x320_ios.json deleted file mode 100644 index 08208df1fbe..00000000000 --- a/stored_requests/data/by_id/stored_imps/interstitial_480x320_ios.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "id": "interstitial_480x320_ios", - "ext": { - "pubmatic": { - "publisherId": "158326", - "adSlot": "2165608" - }, - "triplelift": { - "inventoryCode": "Playwire_RON_Interstitial_IOS" - }, - "between": { - "host": "lbs-us-east1.ads", - "publisher_id": "44634" - }, - "rubicon": { - "accountId": 12556, - "siteId": 376280, - "zoneId": 2369428 - }, - "sonobi": { - "TagID": "e94911505445cf11c143" - }, - "rhythmone": { - "placementId": "247567", - "zone": "1r", - "path": "mvo" - }, - "openx": { - "unit": "558236877", - "delDomain": "playwire-d.openx.net" - }, - "appnexus": { - "placement_id": 24773199 - }, - "yieldmo": { - "placementId": "3124076397123805539" - } - } -} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/interstitial_480x640v_android.json b/stored_requests/data/by_id/stored_imps/interstitial_480x640v_android.json deleted file mode 100644 index f644bc07bdb..00000000000 --- a/stored_requests/data/by_id/stored_imps/interstitial_480x640v_android.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "id": "interstitial_480x640v_android", - "interstitial": {}, - "ext": { - "pubmatic": { - "publisherId": "158326", - "adSlot": "2165611" - }, - "triplelift": { - "inventoryCode": "Playwire_RON_Interstitial_Android" - }, - "between": { - "host": "lbs-us-east1.ads", - "publisher_id": "44634" - }, - "rubicon": { - "accountId": 12556, - "siteId": 376280, - "zoneId": 2369430 - }, - "sonobi": { - "TagID": "e75048f43948741bad48" - }, - "rhythmone": { - "placementId": "247568", - "zone": "1r", - "path": "mvo" - }, - "openx": { - "unit": "558236878", - "delDomain": "playwire-d.openx.net" - }, - "appnexus": { - "placement_id": 24773200 - }, - "yieldmo": { - "placementId": "3124076396956033378" - } - } -} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/interstitial_480x640v_ios.json b/stored_requests/data/by_id/stored_imps/interstitial_480x640v_ios.json deleted file mode 100644 index d9c44258297..00000000000 --- a/stored_requests/data/by_id/stored_imps/interstitial_480x640v_ios.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "id": "interstitial_480x640v_ios", - "ext": { - "pubmatic": { - "publisherId": "158326", - "adSlot": "2165608" - }, - "triplelift": { - "inventoryCode": "Playwire_RON_Interstitial_IOS" - }, - "between": { - "host": "lbs-us-east1.ads", - "publisher_id": "44634" - }, - "rubicon": { - "accountId": 12556, - "siteId": 376280, - "zoneId": 2369428 - }, - "sonobi": { - "TagID": "e94911505445cf11c143" - }, - "rhythmone": { - "placementId": "247567", - "zone": "1r", - "path": "mvo" - }, - "openx": { - "unit": "558236877", - "delDomain": "playwire-d.openx.net" - }, - "appnexus": { - "placement_id": 24773199 - }, - "yieldmo": { - "placementId": "3124076397123805539" - } - } -} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/interstitial_640x480v_android.json b/stored_requests/data/by_id/stored_imps/interstitial_640x480v_android.json deleted file mode 100644 index 17cfeb67154..00000000000 --- a/stored_requests/data/by_id/stored_imps/interstitial_640x480v_android.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "id": "interstitial_640x480v_android", - "ext": { - "pubmatic": { - "publisherId": "158326", - "adSlot": "2165611" - }, - "triplelift": { - "inventoryCode": "Playwire_RON_Interstitial_Android" - }, - "between": { - "host": "lbs-us-east1.ads", - "publisher_id": "44634" - }, - "rubicon": { - "accountId": 12556, - "siteId": 376280, - "zoneId": 2369430 - }, - "sonobi": { - "TagID": "e75048f43948741bad48" - }, - "rhythmone": { - "placementId": "247568", - "zone": "1r", - "path": "mvo" - }, - "openx": { - "unit": "558236878", - "delDomain": "playwire-d.openx.net" - }, - "appnexus": { - "placement_id": 24773200 - }, - "yieldmo": { - "placementId": "3124076396956033378" - } - } -} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/interstitial_640x480v_ios.json b/stored_requests/data/by_id/stored_imps/interstitial_640x480v_ios.json deleted file mode 100644 index 00d54e76304..00000000000 --- a/stored_requests/data/by_id/stored_imps/interstitial_640x480v_ios.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "id": "interstitial_640x480v_ios", - "ext": { - "pubmatic": { - "publisherId": "158326", - "adSlot": "2165608" - }, - "triplelift": { - "inventoryCode": "Playwire_RON_Interstitial_IOS" - }, - "between": { - "host": "lbs-us-east1.ads", - "publisher_id": "44634" - }, - "rubicon": { - "accountId": 12556, - "siteId": 376280, - "zoneId": 2369428 - }, - "sonobi": { - "TagID": "e94911505445cf11c143" - }, - "rhythmone": { - "placementId": "247567", - "zone": "1r", - "path": "mvo" - }, - "openx": { - "unit": "558236877", - "delDomain": "playwire-d.openx.net" - }, - "appnexus": { - "placement_id": 24773199 - }, - "yieldmo": { - "placementId": "3124076397123805539" - } - } -} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/interstitial_768x1024_android.json b/stored_requests/data/by_id/stored_imps/interstitial_768x1024_android.json deleted file mode 100644 index 22366ee3d0e..00000000000 --- a/stored_requests/data/by_id/stored_imps/interstitial_768x1024_android.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "id": "interstitial_768x1024_android", - "ext": { - "pubmatic": { - "publisherId": "158326", - "adSlot": "2165611" - }, - "triplelift": { - "inventoryCode": "Playwire_RON_Interstitial_Android" - }, - "between": { - "host": "lbs-us-east1.ads", - "publisher_id": "44634" - }, - "rubicon": { - "accountId": 12556, - "siteId": 376280, - "zoneId": 2369430 - }, - "sonobi": { - "TagID": "e75048f43948741bad48" - }, - "rhythmone": { - "placementId": "247568", - "zone": "1r", - "path": "mvo" - }, - "openx": { - "unit": "558236878", - "delDomain": "playwire-d.openx.net" - }, - "appnexus": { - "placement_id": 24773200 - }, - "yieldmo": { - "placementId": "3124076396956033378" - } - } -} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/interstitial_768x1024_ios.json b/stored_requests/data/by_id/stored_imps/interstitial_768x1024_ios.json deleted file mode 100644 index 9a792dde409..00000000000 --- a/stored_requests/data/by_id/stored_imps/interstitial_768x1024_ios.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "id": "interstitial_768x1024_ios", - "ext": { - "pubmatic": { - "publisherId": "158326", - "adSlot": "2165608" - }, - "triplelift": { - "inventoryCode": "Playwire_RON_Interstitial_IOS" - }, - "between": { - "host": "lbs-us-east1.ads", - "publisher_id": "44634" - }, - "rubicon": { - "accountId": 12556, - "siteId": 376280, - "zoneId": 2369428 - }, - "sonobi": { - "TagID": "e94911505445cf11c143" - }, - "rhythmone": { - "placementId": "247567", - "zone": "1r", - "path": "mvo" - }, - "openx": { - "unit": "558236877", - "delDomain": "playwire-d.openx.net" - }, - "appnexus": { - "placement_id": 24773199 - }, - "yieldmo": { - "placementId": "3124076397123805539" - } - } -} \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_imps/play_screen_ios.json b/stored_requests/data/by_id/stored_imps/play_screen_ios.json deleted file mode 100644 index 60ae0da576f..00000000000 --- a/stored_requests/data/by_id/stored_imps/play_screen_ios.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "id": "play_screen_ios", - "banner": { - "format": [ - { - "w": 320, - "h": 50 - } - ] - }, - "ext": { - "appnexus": { - "placement_id": 19689655 - }, - "openx": { - "unit": "544086381", - "delDomain": "playwire-d.openx.net" - }, - "pubmatic": { - "publisherId": "158326", - "adSlot": "3029490" - }, - "rhythmone": { - "placementId": "213696", - "zone": "1r", - "path": "mvo" - }, - "rubicon": { - "accountId": 12556, - "siteId": 376280, - "zoneId": 2061954 - }, - "sonobi": { - "TagID": "cc1338cc8e2c6e7864dd" - }, - "triplelift": { - "inventoryCode": "pwm_ios_hdx_pb" - } - } -} diff --git a/stored_requests/data/by_id/stored_imps/play_screen_ios_test.json b/stored_requests/data/by_id/stored_imps/play_screen_ios_test.json deleted file mode 100644 index 9535c49733e..00000000000 --- a/stored_requests/data/by_id/stored_imps/play_screen_ios_test.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "id": "play_screen_ios", - "banner": { - "format": [ - { - "w": 320, - "h": 50 - } - ] - }, - "ext": { - "appnexus": { - "placement_id": 19689655 - }, - "openx": { - "unit": "544086381", - "delDomain": "playwire-d.openx.net" - }, - "pubmatic": { - "publisherId": "158326", - "adSlot": "3029490" - }, - "rhythmone": { - "placementId": "213696", - "zone": "1r", - "path": "mvo" - }, - "rubicon": { - "accountId": 12556, - "siteId": 376280, - "zoneId": 2061954 - }, - "sonobi": { - "TagID": "cc1338cc8e2c6e7864dd" - }, - "triplelift": { - "inventoryCode": "playwire_app_hdx_ios_prebid_TEST" - } - } -} diff --git a/stored_requests/data/by_id/stored_requests/.gitignore b/stored_requests/data/by_id/stored_requests/.gitignore new file mode 100644 index 00000000000..ca0de93fdd9 --- /dev/null +++ b/stored_requests/data/by_id/stored_requests/.gitignore @@ -0,0 +1,3 @@ +# Ignore everything in this directory, except for this file +* +!.gitignore \ No newline at end of file diff --git a/stored_requests/data/by_id/stored_requests/in_article_ios.json b/stored_requests/data/by_id/stored_requests/in_article_ios.json deleted file mode 100644 index da0fe99555c..00000000000 --- a/stored_requests/data/by_id/stored_requests/in_article_ios.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "id": "in_article_ios", - "banner": { - "format": [ - { - "w": 320, - "h": 50 - }, - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "appnexus": { - "placement_id": 19689654 - }, - "openx": { - "unit": "541169197", - "delDomain": "playwire-d.openx.net" - }, - "pubmatic": { - "publisherId": "158326" - }, - "rhythmone": { - "placementId": "213224", - "zone": "1r", - "path": "mvo" - }, - "rubicon": { - "accountId": 15526, - "siteId": 335554, - "zoneId": 1764488 - }, - "sonobi": { - "TagID": "12eb86143abb80d6a9c6" - } - } -} diff --git a/stored_requests/data/by_id/stored_requests/playwire.json b/stored_requests/data/by_id/stored_requests/playwire.json deleted file mode 100644 index 8a710dae0a1..00000000000 --- a/stored_requests/data/by_id/stored_requests/playwire.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "tmax": 2000, - "cur": ["USD"], - "ext": { - "prebid": { - "targeting": { - "includewinners": true, - "includebidderkeys": true, - "pricegranularity": { - "ranges": [ - { - "max": 3, - "increment": 0.01 - }, - { - "max": 8, - "increment": 0.05 - }, - { - "max": 15, - "increment": 0.25 - }, - { - "max": 30, - "increment": 1 - } - ], - "precision": 2 - } - }, - "cache": { - "bids": {} - }, - "aliases": { "oftmedia": "appnexus" } - } - } -} From 8362e08a0381b0b115fafb2c18a9d20bce056593 Mon Sep 17 00:00:00 2001 From: Derek Bruneau <derek.bruneau@gmail.com> Date: Mon, 17 Feb 2025 11:40:57 -0500 Subject: [PATCH 119/125] Adding note to Rubicon config file --- static/bidder-info/rubicon.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/static/bidder-info/rubicon.yaml b/static/bidder-info/rubicon.yaml index c3943058511..bd6dda1fc11 100644 --- a/static/bidder-info/rubicon.yaml +++ b/static/bidder-info/rubicon.yaml @@ -1,6 +1,10 @@ # Contact global-support@magnite.com to ask about enabling a connection to the Magnite (formerly Rubicon) exchange. # We have the following regional endpoint domains: exapi-us-east, exapi-us-west, exapi-apac, exapi-eu # Please deploy this config in each of your datacenters with the appropriate regional subdomain + +# NOTE: Values in this file can (and often should) be overridden by pbs.json, +# pbs.yaml, or environment variables. + endpoint: "https://REGION.rubiconproject.com/a/api/exchange" endpointCompression: GZIP geoscope: From 699a30d9eda631c36a0931cb3bc03ee7a849ae1a Mon Sep 17 00:00:00 2001 From: Derek Bruneau <derek.bruneau@gmail.com> Date: Mon, 17 Feb 2025 11:44:48 -0500 Subject: [PATCH 120/125] Updating version of Go for Heroku --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index c5b44daac96..6ab5c8512f3 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/prebid/prebid-server/v2 go 1.21 // Magic comment that determines which Go version Heroku uses. -// +heroku goVersion go1.19 +// +heroku goVersion go1.21 require ( github.com/DATA-DOG/go-sqlmock v1.5.0 From f82e0ec57f50c98bb0e6ff3ee2faf2a680adf07f Mon Sep 17 00:00:00 2001 From: Derek Bruneau <derek.bruneau@gmail.com> Date: Wed, 23 Apr 2025 10:39:33 -0400 Subject: [PATCH 121/125] Empty commit to upgrade to heroku-24 stack From cdbb051736b364c3cc6f6116b23c581b1723dc83 Mon Sep 17 00:00:00 2001 From: Thomas Creamer <tcreamer@MacBook-Pro.local> Date: Wed, 4 Jun 2025 15:33:35 -0400 Subject: [PATCH 122/125] remove paapi keys from bidders test file --- openrtb_ext/bidders_test.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/openrtb_ext/bidders_test.go b/openrtb_ext/bidders_test.go index ca85274184a..98404a205b1 100644 --- a/openrtb_ext/bidders_test.go +++ b/openrtb_ext/bidders_test.go @@ -111,12 +111,6 @@ func TestIsBidderNameReserved(t *testing.T) { {"gpid", true}, {"GPID", true}, {"GPid", true}, - {"igs", true}, - {"IGS", true}, - {"Igs", true}, - {"paapi", true}, - {"PAAPI", true}, - {"PAApi", true}, {"prebid", true}, {"PREbid", true}, {"PREBID", true}, From 78e8299a85135fb832647aa158bbf2573024fb46 Mon Sep 17 00:00:00 2001 From: Thomas Creamer <tcreamer@MacBook-Pro.local> Date: Mon, 9 Jun 2025 10:03:43 -0400 Subject: [PATCH 123/125] update magic comment version --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index f0442fc4c8e..7a5636fdb38 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.23.0 retract v3.0.0 // Forgot to update major version in import path and module name // Magic comment that determines which Go version Heroku uses. -// +heroku goVersion go1.21 +// +heroku goVersion go1.23 require ( github.com/51Degrees/device-detection-go/v4 v4.4.35 From 779635cc796e22befbb23aca4d05efc4db9438d9 Mon Sep 17 00:00:00 2001 From: Thomas Creamer <tcreamer@MacBook-Pro.local> Date: Mon, 9 Jun 2025 10:04:17 -0400 Subject: [PATCH 124/125] remove const for "HACK" --- openrtb_ext/request.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index ad6547c09ed..a05133fc377 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -29,14 +29,6 @@ const TIDKey = "tid" // AuctionEnvironmentKey is the json key under imp[].ext for ExtImp.AuctionEnvironment const AuctionEnvironmentKey = string(BidderReservedAE) -// HACK: Support the keys used by the PAAPI (Protected Audience API) module. -// Without the hack, PBS will reject any requests that contain them. This hack -// can be removed once the upstream repo supports them. For more info, see: -// https://docs.prebid.org/dev-docs/modules/paapi.html -// https://github.com/prebid/prebid-server/issues/3735 -const IGSKey = "igs" -const PAAPIKey = "paapi" - // NativeExchangeSpecificLowerBound defines the lower threshold of exchange specific types for native ads. There is no upper bound. const NativeExchangeSpecificLowerBound = 500 From b620aa2d9bd5676edcb90b655152e838160ef61f Mon Sep 17 00:00:00 2001 From: Scott Skow <169065991+sskow-pw@users.noreply.github.com> Date: Thu, 12 Jun 2025 15:18:22 -0600 Subject: [PATCH 125/125] Fix Heroku Deployments (#64) --- Aptfile | 1 + Procfile | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 Aptfile diff --git a/Aptfile b/Aptfile new file mode 100644 index 00000000000..ccade3c60c4 --- /dev/null +++ b/Aptfile @@ -0,0 +1 @@ +libatomic1 diff --git a/Procfile b/Procfile index 1944727d5e8..7a7dfbb28e9 100644 --- a/Procfile +++ b/Procfile @@ -1 +1 @@ -web: PBS_PORT=$PORT bin/prebid-server -stderrthreshold=INFO +web: PBS_PORT=$PORT bin/prebid-server