diff --git a/adapters/nativo/nativo.go b/adapters/nativo/nativo.go new file mode 100644 index 00000000000..6e595bbff83 --- /dev/null +++ b/adapters/nativo/nativo.go @@ -0,0 +1,96 @@ +package nativo + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" +) + +type adapter struct { + endpoint string +} + +// Builder builds a new instance of the Nativo 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, _ *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, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), + } + + return []*adapters.RequestData{requestData}, nil +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if adapters.IsResponseStatusCodeNoContent(response) { + return nil, nil + } + + if err := adapters.CheckResponseStatusCodeForErrors(response); err != nil { + return nil, []error{err} + } + + var bidResp openrtb2.BidResponse + + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponse() + + var errs []error + for _, seatBid := range bidResp.SeatBid { + for i := range seatBid.Bid { + bidType, err := getMediaTypeForImp(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 getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { + for _, imp := range imps { + if imp.ID == impID { + if imp.Native != nil { + return openrtb_ext.BidTypeNative, nil + } else if imp.Video != nil { + return openrtb_ext.BidTypeVideo, nil + } else if imp.Banner != nil { + return openrtb_ext.BidTypeBanner, nil + } + } + } + return "", fmt.Errorf("Unrecognized impression type in response from nativo: %s", impID) +} diff --git a/adapters/nativo/nativo_test.go b/adapters/nativo/nativo_test.go new file mode 100644 index 00000000000..87bce3f08d3 --- /dev/null +++ b/adapters/nativo/nativo_test.go @@ -0,0 +1,21 @@ +package nativo + +import ( + "testing" + + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" +) + +func TestBidderNativo(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderNativo, 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, "nativotest", bidder) +} diff --git a/adapters/nativo/nativotest/exemplary/banner-app.json b/adapters/nativo/nativotest/exemplary/banner-app.json new file mode 100644 index 00000000000..738d01bd9a4 --- /dev/null +++ b/adapters/nativo/nativotest/exemplary/banner-app.json @@ -0,0 +1,133 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "banner": { + "w": 320, + "h": 50 + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://foo.io/?src=prebid", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "tagid": "ogTAGID" + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + }, + "impIDs":["some-impression-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50 + } + ], + "seat": "nativo" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "nativo": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/nativo/nativotest/exemplary/banner-web.json b/adapters/nativo/nativotest/exemplary/banner-web.json new file mode 100644 index 00000000000..58af89b202c --- /dev/null +++ b/adapters/nativo/nativotest/exemplary/banner-web.json @@ -0,0 +1,121 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "banner": { + "w":320, + "h":50 + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://foo.io/?src=prebid", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "banner": { + "w":320, + "h":50 + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + }, + "impIDs":["some-impression-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50 + } + ], + "seat": "nativo" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "nativo": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids":[ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "crid": "20", + "adomain": [ + "awesome.com" + ], + "w": 320, + "h": 50 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/nativo/nativotest/exemplary/native-app.json b/adapters/nativo/nativotest/exemplary/native-app.json new file mode 100644 index 00000000000..5f038958b76 --- /dev/null +++ b/adapters/nativo/nativotest/exemplary/native-app.json @@ -0,0 +1,130 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "native": { + "ver":"1.1", + "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}" + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://foo.io/?src=prebid", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "native": { + "ver":"1.1", + "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}" + }, + "tagid": "ogTAGID" + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + }, + "impIDs":["some-impression-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20" + } + ], + "seat": "nativo" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "nativo": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids":[ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "crid": "20", + "adomain": [ + "awesome.com" + ] + }, + "type": "native" + } + ] + } + ] + } + \ No newline at end of file diff --git a/adapters/nativo/nativotest/exemplary/native-web.json b/adapters/nativo/nativotest/exemplary/native-web.json new file mode 100644 index 00000000000..598dc2abfc3 --- /dev/null +++ b/adapters/nativo/nativotest/exemplary/native-web.json @@ -0,0 +1,117 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ipv6": "2607:fb90:f27:4512:d800:cb23:a603:e245", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "native": { + "ver":"1.1", + "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}" + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://foo.io/?src=prebid", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ipv6": "2607:fb90:f27:4512:d800:cb23:a603:e245", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "native": { + "ver":"1.1", + "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}" + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + }, + "impIDs":["some-impression-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20" + } + ], + "seat": "nativo" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "nativo": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids":[ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "crid": "20", + "adomain": [ + "awesome.com" + ] + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/nativo/nativotest/exemplary/video-app.json b/adapters/nativo/nativotest/exemplary/video-app.json new file mode 100644 index 00000000000..596bf9bcaf0 --- /dev/null +++ b/adapters/nativo/nativotest/exemplary/video-app.json @@ -0,0 +1,143 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://foo.io/?src=prebid", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "tagid": "ogTAGID" + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + }, + "impIDs":["some-impression-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 1280, + "h": 720 + } + ], + "seat": "nativo" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "nativo": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids":[ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "crid": "20", + "adomain": [ + "awesome.com" + ], + "w": 1280, + "h": 720 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/nativo/nativotest/exemplary/video-web.json b/adapters/nativo/nativotest/exemplary/video-web.json new file mode 100644 index 00000000000..12e185ca158 --- /dev/null +++ b/adapters/nativo/nativotest/exemplary/video-web.json @@ -0,0 +1,141 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://foo.io/?src=prebid", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + }, + "impIDs":["some-impression-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 1280, + "h": 720, + "ext": { + "prebid": { + "type": "video" + } + } + } + ], + "seat": "nativo" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "nativo": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 1280, + "h": 720, + "ext": { + "prebid": { + "type": "video" + } + } + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/nativo/nativotest/supplemental/200-different-impID-response-from-nativo.json b/adapters/nativo/nativotest/supplemental/200-different-impID-response-from-nativo.json new file mode 100755 index 00000000000..320d7f5e1b0 --- /dev/null +++ b/adapters/nativo/nativotest/supplemental/200-different-impID-response-from-nativo.json @@ -0,0 +1,81 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "nativo-id", + "banner": { + "format": [ + { + "w": 300, + "h": 50 + } + ] + }, + "ext": { + "bidder": {} + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://foo.io/?src=prebid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "nativo-id", + "banner": { + "format": [ + { + "w": 300, + "h": 50 + } + ] + }, + "ext": { + "bidder": {} + } + } + ] + }, + "impIDs":["nativo-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "bid-id", + "impid": "nativo-id-2", + "price": 3.5, + "w": 300, + "h": 50 + } + ], + "seat": "nativo" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [] + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unrecognized impression type in response from nativo: nativo-id-2", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/nativo/nativotest/supplemental/204-response-from-nativo.json b/adapters/nativo/nativotest/supplemental/204-response-from-nativo.json new file mode 100755 index 00000000000..6f0f79ed6fd --- /dev/null +++ b/adapters/nativo/nativotest/supplemental/204-response-from-nativo.json @@ -0,0 +1,53 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "nativo-id", + "banner": { + "format": [ + { + "w": 300, + "h": 50 + } + ] + }, + "ext": { + "bidder": {} + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://foo.io/?src=prebid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "nativo-id", + "banner": { + "format": [ + { + "w": 300, + "h": 50 + } + ] + }, + "ext": { + "bidder": {} + } + } + ] + }, + "impIDs":["nativo-id"] + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/nativo/nativotest/supplemental/400-response-from-nativo.json b/adapters/nativo/nativotest/supplemental/400-response-from-nativo.json new file mode 100755 index 00000000000..f5ebb6024cb --- /dev/null +++ b/adapters/nativo/nativotest/supplemental/400-response-from-nativo.json @@ -0,0 +1,58 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "nativo-id", + "banner": { + "format": [ + { + "w": 300, + "h": 50 + } + ] + }, + "ext": { + "bidder": {} + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://foo.io/?src=prebid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "nativo-id", + "banner": { + "format": [ + { + "w": 300, + "h": 50 + } + ] + }, + "ext": { + "bidder": {} + } + } + ] + }, + "impIDs":["nativo-id"] + }, + "mockResponse": { + "status": 400, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/nativo/nativotest/supplemental/500-response-from-nativo.json b/adapters/nativo/nativotest/supplemental/500-response-from-nativo.json new file mode 100755 index 00000000000..cc58f18ecee --- /dev/null +++ b/adapters/nativo/nativotest/supplemental/500-response-from-nativo.json @@ -0,0 +1,58 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "nativo-id", + "banner": { + "format": [ + { + "w": 300, + "h": 50 + } + ] + }, + "ext": { + "bidder": {} + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://foo.io/?src=prebid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "nativo-id", + "banner": { + "format": [ + { + "w": 300, + "h": 50 + } + ] + }, + "ext": { + "bidder": {} + } + } + ] + }, + "impIDs":["nativo-id"] + }, + "mockResponse": { + "status": 500, + "body": {} + } + } + ], + "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/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index fa61c484f0d..e4faa7e9cb8 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -1132,12 +1132,17 @@ func validateRequestExt(req *openrtb_ext.RequestWrapper) []error { } func validateTargeting(t *openrtb_ext.ExtRequestTargeting) error { - if t != nil { - if t.PriceGranularity != nil { - if err := validatePriceGranularity(t.PriceGranularity); err != nil { - return err - } + if t == nil { + return nil + } + + if t.PriceGranularity != nil { + if err := validatePriceGranularity(t.PriceGranularity); err != nil { + return err } + } + + if t.MediaTypePriceGranularity != nil { if t.MediaTypePriceGranularity.Video != nil { if err := validatePriceGranularity(t.MediaTypePriceGranularity.Video); err != nil { return err @@ -1154,6 +1159,7 @@ func validateTargeting(t *openrtb_ext.ExtRequestTargeting) error { } } } + return nil } diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 2df4813d157..dfc4f441cfe 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -1987,7 +1987,7 @@ func TestValidateTargeting(t *testing.T) { expectedError: nil, }, { - name: "price granularity ranges out of order", + name: "pricegranularity-ranges-out-of-order", givenTargeting: &openrtb_ext.ExtRequestTargeting{ PriceGranularity: &openrtb_ext.PriceGranularity{ Precision: ptrutil.ToPtr(2), @@ -2000,9 +2000,16 @@ func TestValidateTargeting(t *testing.T) { expectedError: errors.New(`Price granularity error: range list must be ordered with increasing "max"`), }, { - name: "media type price granularity video correct", + name: "mediatypepricegranularity-nil", givenTargeting: &openrtb_ext.ExtRequestTargeting{ - MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ + MediaTypePriceGranularity: nil, + }, + expectedError: nil, + }, + { + name: "mediatypepricegranularity-video-ok", + givenTargeting: &openrtb_ext.ExtRequestTargeting{ + MediaTypePriceGranularity: &openrtb_ext.MediaTypePriceGranularity{ Video: &openrtb_ext.PriceGranularity{ Precision: ptrutil.ToPtr(2), Ranges: []openrtb_ext.GranularityRange{ @@ -2014,9 +2021,9 @@ func TestValidateTargeting(t *testing.T) { expectedError: nil, }, { - name: "media type price granularity banner correct", + name: "mediatypepricegranularity-banner-ok", givenTargeting: &openrtb_ext.ExtRequestTargeting{ - MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ + MediaTypePriceGranularity: &openrtb_ext.MediaTypePriceGranularity{ Banner: &openrtb_ext.PriceGranularity{ Precision: ptrutil.ToPtr(2), Ranges: []openrtb_ext.GranularityRange{ @@ -2028,9 +2035,9 @@ func TestValidateTargeting(t *testing.T) { expectedError: nil, }, { - name: "media type price granularity native correct", + name: "mediatypepricegranularity-native-ok", givenTargeting: &openrtb_ext.ExtRequestTargeting{ - MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ + MediaTypePriceGranularity: &openrtb_ext.MediaTypePriceGranularity{ Native: &openrtb_ext.PriceGranularity{ Precision: ptrutil.ToPtr(2), Ranges: []openrtb_ext.GranularityRange{ @@ -2042,9 +2049,9 @@ func TestValidateTargeting(t *testing.T) { expectedError: nil, }, { - name: "media type price granularity video and banner correct", + name: "mediatypepricegranularity-video+banner-ok", givenTargeting: &openrtb_ext.ExtRequestTargeting{ - MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ + MediaTypePriceGranularity: &openrtb_ext.MediaTypePriceGranularity{ Banner: &openrtb_ext.PriceGranularity{ Precision: ptrutil.ToPtr(2), Ranges: []openrtb_ext.GranularityRange{ @@ -2062,9 +2069,9 @@ func TestValidateTargeting(t *testing.T) { expectedError: nil, }, { - name: "media type price granularity video incorrect", + name: "mediatypepricegranularity-video-invalid", givenTargeting: &openrtb_ext.ExtRequestTargeting{ - MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ + MediaTypePriceGranularity: &openrtb_ext.MediaTypePriceGranularity{ Video: &openrtb_ext.PriceGranularity{ Precision: ptrutil.ToPtr(2), Ranges: []openrtb_ext.GranularityRange{ @@ -2076,9 +2083,9 @@ func TestValidateTargeting(t *testing.T) { expectedError: errors.New("Price granularity error: increment must be a nonzero positive number"), }, { - name: "media type price granularity banner incorrect", + name: "mediatypepricegranularity-banner-invalid", givenTargeting: &openrtb_ext.ExtRequestTargeting{ - MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ + MediaTypePriceGranularity: &openrtb_ext.MediaTypePriceGranularity{ Banner: &openrtb_ext.PriceGranularity{ Precision: ptrutil.ToPtr(2), Ranges: []openrtb_ext.GranularityRange{ @@ -2090,9 +2097,9 @@ func TestValidateTargeting(t *testing.T) { expectedError: errors.New("Price granularity error: range list must be ordered with increasing \"max\""), }, { - name: "media type price granularity native incorrect", + name: "mediatypepricegranularity-native-invalid", givenTargeting: &openrtb_ext.ExtRequestTargeting{ - MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ + MediaTypePriceGranularity: &openrtb_ext.MediaTypePriceGranularity{ Native: &openrtb_ext.PriceGranularity{ Precision: ptrutil.ToPtr(2), Ranges: []openrtb_ext.GranularityRange{ @@ -2104,9 +2111,9 @@ func TestValidateTargeting(t *testing.T) { expectedError: errors.New("Price granularity error: range list must be ordered with increasing \"max\""), }, { - name: "media type price granularity video correct and banner incorrect", + name: "mediatypepricegranularity-video-ok-banner-invalid", givenTargeting: &openrtb_ext.ExtRequestTargeting{ - MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ + MediaTypePriceGranularity: &openrtb_ext.MediaTypePriceGranularity{ Banner: &openrtb_ext.PriceGranularity{ Precision: ptrutil.ToPtr(2), Ranges: []openrtb_ext.GranularityRange{ @@ -2124,9 +2131,9 @@ func TestValidateTargeting(t *testing.T) { expectedError: errors.New("Price granularity error: range list must be ordered with increasing \"max\""), }, { - name: "media type price granularity native incorrect and banner correct", + name: "mediatypepricegranularity-native-invalid-banner-ok", givenTargeting: &openrtb_ext.ExtRequestTargeting{ - MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ + MediaTypePriceGranularity: &openrtb_ext.MediaTypePriceGranularity{ Native: &openrtb_ext.PriceGranularity{ Precision: ptrutil.ToPtr(2), Ranges: []openrtb_ext.GranularityRange{ @@ -2147,7 +2154,7 @@ func TestValidateTargeting(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - assert.Equal(t, tc.expectedError, validateTargeting(tc.givenTargeting), "Targeting") + assert.Equal(t, tc.expectedError, validateTargeting(tc.givenTargeting)) }) } } @@ -3970,6 +3977,7 @@ func TestParseRequestMergeBidderParams(t *testing.T) { expectedImpExt json.RawMessage expectedReqExt json.RawMessage expectedErrorCount int + expectedErrors []error }{ { name: "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext.prebid.bidder", @@ -3987,10 +3995,16 @@ func TestParseRequestMergeBidderParams(t *testing.T) { }, { name: "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext for backward compatibility", - givenRequestBody: validRequest(t, "req-ext-bidder-params-backward-compatible-merge.json"), - expectedImpExt: getObject(t, "req-ext-bidder-params-backward-compatible-merge.json", "expectedImpExt"), - expectedReqExt: getObject(t, "req-ext-bidder-params-backward-compatible-merge.json", "expectedReqExt"), - expectedErrorCount: 0, + givenRequestBody: validRequest(t, "req-ext-bidder-params-promotion.json"), + expectedImpExt: getObject(t, "req-ext-bidder-params-promotion.json", "expectedImpExt"), + expectedReqExt: getObject(t, "req-ext-bidder-params-promotion.json", "expectedReqExt"), + expectedErrorCount: 1, + expectedErrors: []error{ + &errortypes.Warning{ + WarningCode: 0, + Message: "request.imp[0].ext contains unknown bidder: 'arbitraryObject', ignoring", + }, + }, }, } for _, test := range tests { @@ -4047,6 +4061,8 @@ func TestParseRequestMergeBidderParams(t *testing.T) { assert.Equal(t, eReqE, reqE, "req.Ext should match") assert.Len(t, errL, test.expectedErrorCount, "error length should match") + + assert.Equal(t, errL, test.expectedErrors) }) } } @@ -4651,7 +4667,7 @@ func TestValidateStoredResp(t *testing.T) { storedBidResponses: stored_responses.ImpBidderStoredResp{"Some-Imp-ID": {"appnexus": json.RawMessage(`{"test":true}`), "rubicon": json.RawMessage(`{"test":true}`)}}, }, { - description: "One imp with 2 stored bid responses and 1 bidders in imp.ext and 1 in imp.ext.prebid.bidder, expect validate request to throw no errors", + description: "One imp with 1 stored bid response and 1 ignored bidder in imp.ext and 1 included bidder in imp.ext.prebid.bidder, expect validate request to throw no errors", givenRequestWrapper: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{ ID: "Some-ID", @@ -4678,7 +4694,7 @@ func TestValidateStoredResp(t *testing.T) { }, expectedErrorList: []error{}, hasStoredAuctionResponses: false, - storedBidResponses: stored_responses.ImpBidderStoredResp{"Some-Imp-ID": {"appnexus": json.RawMessage(`{"test":true}`), "telaria": json.RawMessage(`{"test":true}`)}}, + storedBidResponses: stored_responses.ImpBidderStoredResp{"Some-Imp-ID": {"telaria": json.RawMessage(`{"test":true}`)}}, }, { description: "One imp with 2 stored bid responses and 1 bidders in imp.ext and 1 in imp.ext.prebid.bidder that is not defined in stored bid responses, expect validate request to throw an error", 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 index 034d3235e15..c775d332ecd 100644 --- 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 @@ -105,8 +105,7 @@ ] }, "includewinners": true, - "includebidderkeys": true, - "mediatypepricegranularity": {} + "includebidderkeys": true } } } 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 index 30d8b9ca240..ab9657d51f5 100644 --- 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 @@ -97,8 +97,7 @@ ] }, "includewinners": true, - "includebidderkeys": true, - "mediatypepricegranularity": {} + "includebidderkeys": true } } } 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 index 8c232192a63..3d6c0bc0157 100644 --- 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 @@ -100,8 +100,7 @@ ] }, "includewinners": true, - "includebidderkeys": true, - "mediatypepricegranularity": {} + "includebidderkeys": true } } } 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 index 0249bbc3f96..c4f3da140a3 100644 --- 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 @@ -102,8 +102,7 @@ ] }, "includewinners": true, - "includebidderkeys": true, - "mediatypepricegranularity": {} + "includebidderkeys": true } } } 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 index 6f0f5780b43..2be461cdf6c 100644 --- 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 @@ -100,8 +100,7 @@ ] }, "includewinners": true, - "includebidderkeys": true, - "mediatypepricegranularity": {} + "includebidderkeys": true } } } diff --git a/endpoints/openrtb2/sample-requests/amp/valid-supplementary/ortb-2.5-to-2.6-upconvert.json b/endpoints/openrtb2/sample-requests/amp/valid-supplementary/ortb-2.5-to-2.6-upconvert.json index 766b7fc6ba9..1a2db473cdd 100644 --- a/endpoints/openrtb2/sample-requests/amp/valid-supplementary/ortb-2.5-to-2.6-upconvert.json +++ b/endpoints/openrtb2/sample-requests/amp/valid-supplementary/ortb-2.5-to-2.6-upconvert.json @@ -180,8 +180,7 @@ ] }, "includewinners": true, - "includebidderkeys": true, - "mediatypepricegranularity": {} + "includebidderkeys": true } } } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/unknown-bidder.json b/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-prebid-bidder-empty.json similarity index 80% rename from endpoints/openrtb2/sample-requests/invalid-whole/unknown-bidder.json rename to endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-prebid-bidder-empty.json index a907d4d9257..d87a6d33261 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/unknown-bidder.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-prebid-bidder-empty.json @@ -21,6 +21,9 @@ ] }, "ext": { + "prebid": { + "bidder": {} + }, "appnexus": { "placementId": 12883451 }, @@ -34,5 +37,5 @@ "tmax": 500 }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request" + "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-unknown-bidder.json b/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-unknown-bidder.json index 8a7b7932179..6b25ee8b037 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.prebid.bidder contains unknown bidder: noBidderShouldEverHaveThisName. Did you forget an alias in request.ext.prebid.aliases?\n" + "expectedErrorMessage": "Invalid request: request.imp[0].ext.prebid.bidder must contain at least one bidder" } diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/user-ext-prebid-buyeruids.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/user-ext-prebid-buyeruids.json new file mode 100644 index 00000000000..a6576c7b3f1 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/user-ext-prebid-buyeruids.json @@ -0,0 +1,170 @@ +{ + "description": "Bid request with user.ext.prebid.buyeruids object", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 1.00 + }, + { + "bidderName": "pubmatic", + "currency": "USD", + "price": 2.00 + } + ], + "bidderInfoOverrides": { + "appnexus": { + "openrtb": { + "version": "2.5" + } + }, + "pubmatic": { + "openrtb": { + "version": "2.6" + } + } + } + }, + "mockBidRequest": { + "id": "request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + }, + "pubmatic": { + "publisherId": "123" + } + } + } + ], + "user": { + "consent": "some-consent-string", + "ext": { + "prebid": { + "buyeruids": { + "appnexus": "appnexus-buyeruid", + "pubmatic": "pubmatic-buyeruid" + } + } + } + } + }, + "expectedMockBidderRequests": { + "appnexus": { + "id": "request-id", + "site": { + "page": "prebid.org", + "ext": { + "amp": 0 + } + }, + "at": 1, + "device": { + "ip": "192.0.2.1" + }, + "imp": [ + { + "id": "imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": 12883451 + } + }, + "secure": 1 + }], + "user": { + "buyeruid": "appnexus-buyeruid", + "ext": { + "consent": "some-consent-string" + } + } + }, + "pubmatic": { + "id": "request-id", + "site": { + "page": "prebid.org", + "ext": { + "amp": 0 + } + }, + "at": 1, + "device": { + "ip": "192.0.2.1" + }, + "imp": [ + { + "id": "imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "123" + } + }, + "secure": 1 + }], + "user": { + "buyeruid": "pubmatic-buyeruid", + "consent": "some-consent-string" + } + } + }, + "expectedBidResponse": { + "id": "request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "imp-id", + "price": 1.0 + } + ], + "seat": "appnexus" + }, + { + "bid": [ + { + "id": "pubmatic-bid", + "impid": "imp-id", + "price": 2.0 + } + ], + "seat": "pubmatic" + } + ], + "bidid": "test bid id", + "cur": "USD", + "nbr": 0 + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-merge.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-merge.json index 1e2f58cbfd2..9f0e78be352 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-merge.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-merge.json @@ -76,7 +76,6 @@ } ] }, - "mediatypepricegranularity": {}, "includewinners": true, "includebidderkeys": true }, 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-promotion.json similarity index 94% rename from endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-backward-compatible-merge.json rename to endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-promotion.json index 908e20e5b79..d1d20404911 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-promotion.json @@ -32,8 +32,8 @@ "profile": 1234 } }, - "prebid": { - "bidder": {} + "arbitraryObject": { + "arbitraryField": 1232 } } } @@ -74,7 +74,6 @@ } ] }, - "mediatypepricegranularity": {}, "includewinners": true, "includebidderkeys": true }, @@ -108,6 +107,9 @@ } } } + }, + "arbitraryObject": { + "arbitraryField": 1232 } }, "expectedReturnCode": 200 diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params.json index 311d4e11485..27ee9da486c 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params.json @@ -69,7 +69,6 @@ } ] }, - "mediatypepricegranularity": {}, "includewinners": true, "includebidderkeys": true }, diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index 79eaaab980c..93f7f6fcf33 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -156,7 +156,7 @@ func TestCreateBidExtensionTargeting(t *testing.T) { require.NotNil(t, ex.lastRequest, "The request never made it into the Exchange.") // assert targeting set to default - expectedRequestExt := `{"prebid":{"cache":{"vastxml":{}},"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":20,"increment":0.1}]},"mediatypepricegranularity":{},"includebidderkeys":true,"includewinners":true,"includebrandcategory":{"primaryadserver":1,"publisher":"","withcategory":true}}}}` + expectedRequestExt := `{"prebid":{"cache":{"vastxml":{}},"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":20,"increment":0.1}]},"includebidderkeys":true,"includewinners":true,"includebrandcategory":{"primaryadserver":1,"withcategory":true}}}}` assert.JSONEq(t, expectedRequestExt, string(ex.lastRequest.Ext)) } diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 9b5b4c8ab7b..6a95448d0ee 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -143,6 +143,7 @@ import ( "github.com/prebid/prebid-server/v3/adapters/mobfoxpb" "github.com/prebid/prebid-server/v3/adapters/mobilefuse" "github.com/prebid/prebid-server/v3/adapters/motorik" + "github.com/prebid/prebid-server/v3/adapters/nativo" "github.com/prebid/prebid-server/v3/adapters/nextmillennium" "github.com/prebid/prebid-server/v3/adapters/nobid" "github.com/prebid/prebid-server/v3/adapters/oms" @@ -376,6 +377,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderMobfoxpb: mobfoxpb.Builder, openrtb_ext.BidderMobileFuse: mobilefuse.Builder, openrtb_ext.BidderMotorik: motorik.Builder, + openrtb_ext.BidderNativo: nativo.Builder, openrtb_ext.BidderNextMillennium: nextmillennium.Builder, openrtb_ext.BidderNoBid: nobid.Builder, openrtb_ext.BidderOms: oms.Builder, diff --git a/exchange/utils.go b/exchange/utils.go index 8c8e63a0147..71f7c20f8c7 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -82,11 +82,12 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context, return } - explicitBuyerUIDs, err := extractBuyerUIDs(req.BidRequest.User) + explicitBuyerUIDs, err := extractAndCleanBuyerUIDs(req) if err != nil { errs = []error{err} return } + lowerCaseExplicitBuyerUIDs := make(map[string]string) for bidder, uid := range explicitBuyerUIDs { lowerKey := strings.ToLower(bidder) @@ -497,33 +498,30 @@ func buildRequestExtForBidder(bidder string, req *openrtb_ext.RequestWrapper, re } prebid := reqExt.GetPrebid() - // Resolve alternatebiddercode + // Resolve Alternate Bidder Codes var reqABC *openrtb_ext.ExtAlternateBidderCodes if prebid != nil && prebid.AlternateBidderCodes != nil { reqABC = prebid.AlternateBidderCodes } alternateBidderCodes := buildRequestExtAlternateBidderCodes(bidder, cfgABC, reqABC) - var prebidNew openrtb_ext.ExtRequestPrebid - if prebid == nil { - prebidNew = openrtb_ext.ExtRequestPrebid{ - BidderParams: reqExtBidderParams[bidder], - AlternateBidderCodes: alternateBidderCodes, - } - } else { - // Copy Allowed Fields - // Per: https://docs.prebid.org/prebid-server/endpoints/openrtb2/pbs-endpoint-auction.html#prebid-server-ortb2-extension-summary - prebidNew = openrtb_ext.ExtRequestPrebid{ - BidderParams: reqExtBidderParams[bidder], - AlternateBidderCodes: alternateBidderCodes, - Channel: prebid.Channel, - CurrencyConversions: prebid.CurrencyConversions, - Debug: prebid.Debug, - Integration: prebid.Integration, - MultiBid: buildRequestExtMultiBid(bidder, prebid.MultiBid, alternateBidderCodes), - Sdk: prebid.Sdk, - Server: prebid.Server, - } + // Build New/Filtered Prebid Ext + prebidNew := openrtb_ext.ExtRequestPrebid{ + BidderParams: reqExtBidderParams[bidder], + AlternateBidderCodes: alternateBidderCodes, + } + + // Copy Allowed Fields + // Per: https://docs.prebid.org/prebid-server/endpoints/openrtb2/pbs-endpoint-auction.html#prebid-server-ortb2-extension-summary + if prebid != nil { + prebidNew.Channel = prebid.Channel + prebidNew.CurrencyConversions = prebid.CurrencyConversions + prebidNew.Debug = prebid.Debug + prebidNew.Integration = prebid.Integration + prebidNew.MultiBid = buildRequestExtMultiBid(bidder, prebid.MultiBid, alternateBidderCodes) + prebidNew.Sdk = prebid.Sdk + prebidNew.Server = prebid.Server + prebidNew.Targeting = buildRequestExtTargeting(prebid.Targeting) } reqExt.SetPrebid(&prebidNew) @@ -585,6 +583,18 @@ func buildRequestExtMultiBid(adapter string, reqMultiBid []*openrtb_ext.ExtMulti return nil } +func buildRequestExtTargeting(t *openrtb_ext.ExtRequestTargeting) *openrtb_ext.ExtRequestTargeting { + if t == nil || t.IncludeBrandCategory == nil { + return nil + } + + // only include fields bidders can use to influence their response and which does + // not expose information about other bidders or restricted auction processing + return &openrtb_ext.ExtRequestTargeting{ + IncludeBrandCategory: t.IncludeBrandCategory, + } +} + func isBidderInExtAlternateBidderCodes(adapter, currentMultiBidBidder string, adapterABC *openrtb_ext.ExtAlternateBidderCodes) bool { if adapterABC != nil { if abc, ok := adapterABC.Bidders[adapter]; ok { @@ -598,39 +608,30 @@ func isBidderInExtAlternateBidderCodes(adapter, currentMultiBidBidder string, ad return false } -// extractBuyerUIDs parses the values from user.ext.prebid.buyeruids, and then deletes those values from the ext. +// extractAndCleanBuyerUIDs parses the values from user.ext.prebid.buyeruids, and then deletes those values from the ext. // This prevents a Bidder from using these values to figure out who else is involved in the Auction. -func extractBuyerUIDs(user *openrtb2.User) (map[string]string, error) { - if user == nil { - return nil, nil - } - if len(user.Ext) == 0 { +func extractAndCleanBuyerUIDs(req *openrtb_ext.RequestWrapper) (map[string]string, error) { + if req.User == nil { return nil, nil } - var userExt openrtb_ext.ExtUser - if err := jsonutil.Unmarshal(user.Ext, &userExt); err != nil { + userExt, err := req.GetUserExt() + if err != nil { return nil, err } - if userExt.Prebid == nil { + + prebid := userExt.GetPrebid() + if prebid == nil { return nil, nil } + buyerUIDs := prebid.BuyerUIDs + + prebid.BuyerUIDs = nil + userExt.SetPrebid(prebid) + // The API guarantees that user.ext.prebid.buyeruids exists and has at least one ID defined, // as long as user.ext.prebid exists. - buyerUIDs := userExt.Prebid.BuyerUIDs - userExt.Prebid = nil - - // Remarshal (instead of removing) if the ext has other known fields - if userExt.Consent != "" || len(userExt.Eids) > 0 { - if newUserExtBytes, err := jsonutil.Marshal(userExt); err != nil { - return nil, err - } else { - user.Ext = newUserExtBytes - } - } else { - user.Ext = nil - } return buyerUIDs, nil } @@ -717,20 +718,15 @@ func mergeImpFPD(imp *openrtb2.Imp, fpd json.RawMessage, index int) error { return nil } -var allowedImpExtFields = map[string]interface{}{ - openrtb_ext.AuctionEnvironmentKey: struct{}{}, - openrtb_ext.FirstPartyDataExtKey: struct{}{}, - openrtb_ext.FirstPartyDataContextExtKey: struct{}{}, - openrtb_ext.GPIDKey: struct{}{}, - openrtb_ext.SKAdNExtKey: struct{}{}, - openrtb_ext.TIDKey: struct{}{}, -} - var allowedImpExtPrebidFields = map[string]interface{}{ openrtb_ext.IsRewardedInventoryKey: struct{}{}, openrtb_ext.OptionsKey: struct{}{}, } +var deniedImpExtFields = map[string]interface{}{ + openrtb_ext.PrebidExtKey: struct{}{}, +} + func createSanitizedImpExt(impExt, impExtPrebid map[string]json.RawMessage) (map[string]json.RawMessage, error) { sanitizedImpExt := make(map[string]json.RawMessage, 6) sanitizedImpPrebidExt := make(map[string]json.RawMessage, 2) @@ -752,8 +748,8 @@ func createSanitizedImpExt(impExt, impExtPrebid map[string]json.RawMessage) (map } // copy reserved imp[].ext fields known to not be bidder names - for k := range allowedImpExtFields { - if v, exists := impExt[k]; exists { + for k, v := range impExt { + if _, exists := deniedImpExtFields[k]; !exists { sanitizedImpExt[k] = v } } @@ -762,8 +758,6 @@ func createSanitizedImpExt(impExt, impExtPrebid map[string]json.RawMessage) (map } // prepareUser changes req.User so that it's ready for the given bidder. -// This *will* mutate the request, but will *not* mutate any objects nested inside it. -// // In this function, "givenBidder" may or may not be an alias. "coreBidder" must *not* be an alias. // It returns true if a Cookie User Sync existed, and false otherwise. func prepareUser(req *openrtb_ext.RequestWrapper, givenBidder, syncerKey string, explicitBuyerUIDs map[string]string, usersyncs IdFetcher) bool { @@ -934,15 +928,15 @@ func getExtCacheInstructions(requestExtPrebid *openrtb_ext.ExtRequestPrebid) ext func getExtTargetData(requestExtPrebid *openrtb_ext.ExtRequestPrebid, cacheInstructions extCacheInstructions) *targetData { if requestExtPrebid != nil && requestExtPrebid.Targeting != nil { return &targetData{ - includeWinners: *requestExtPrebid.Targeting.IncludeWinners, - includeBidderKeys: *requestExtPrebid.Targeting.IncludeBidderKeys, + alwaysIncludeDeals: requestExtPrebid.Targeting.AlwaysIncludeDeals, + includeBidderKeys: ptrutil.ValueOrDefault(requestExtPrebid.Targeting.IncludeBidderKeys), includeCacheBids: cacheInstructions.cacheBids, includeCacheVast: cacheInstructions.cacheVAST, includeFormat: requestExtPrebid.Targeting.IncludeFormat, - priceGranularity: *requestExtPrebid.Targeting.PriceGranularity, - mediaTypePriceGranularity: requestExtPrebid.Targeting.MediaTypePriceGranularity, + includeWinners: ptrutil.ValueOrDefault(requestExtPrebid.Targeting.IncludeWinners), + mediaTypePriceGranularity: ptrutil.ValueOrDefault(requestExtPrebid.Targeting.MediaTypePriceGranularity), preferDeals: requestExtPrebid.Targeting.PreferDeals, - alwaysIncludeDeals: requestExtPrebid.Targeting.AlwaysIncludeDeals, + priceGranularity: ptrutil.ValueOrDefault(requestExtPrebid.Targeting.PriceGranularity), } } diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 67ad46dd725..64f0973ecf4 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -607,80 +607,83 @@ func TestCreateSanitizedImpExt(t *testing.T) { { description: "imp.ext", givenImpExt: map[string]json.RawMessage{ - "anyBidder": json.RawMessage(`"anyBidderValues"`), - "data": json.RawMessage(`"anyData"`), - "context": json.RawMessage(`"anyContext"`), - "skadn": json.RawMessage(`"anySKAdNetwork"`), - "gpid": json.RawMessage(`"anyGPID"`), - "tid": json.RawMessage(`"anyTID"`), + "arbitraryField": json.RawMessage(`"arbitraryValue"`), + "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{}, expected: map[string]json.RawMessage{ - "data": json.RawMessage(`"anyData"`), - "context": json.RawMessage(`"anyContext"`), - "skadn": json.RawMessage(`"anySKAdNetwork"`), - "gpid": json.RawMessage(`"anyGPID"`), - "tid": json.RawMessage(`"anyTID"`), + "arbitraryField": json.RawMessage(`"arbitraryValue"`), + "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 Only", 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"`), + "arbitraryField": json.RawMessage(`"arbitraryValue"`), + "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"`), }, expected: map[string]json.RawMessage{ - "data": json.RawMessage(`"anyData"`), - "context": json.RawMessage(`"anyContext"`), - "skadn": json.RawMessage(`"anySKAdNetwork"`), - "gpid": json.RawMessage(`"anyGPID"`), - "tid": json.RawMessage(`"anyTID"`), + "arbitraryField": json.RawMessage(`"arbitraryValue"`), + "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 Forbidden Value", 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"`), + "arbitraryField": json.RawMessage(`"arbitraryValue"`), + "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"`), "forbidden": json.RawMessage(`"anyValue"`), }, expected: map[string]json.RawMessage{ - "data": json.RawMessage(`"anyData"`), - "context": json.RawMessage(`"anyContext"`), - "skadn": json.RawMessage(`"anySKAdNetwork"`), - "gpid": json.RawMessage(`"anyGPID"`), - "tid": json.RawMessage(`"anyTID"`), + "arbitraryField": json.RawMessage(`"arbitraryValue"`), + "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"`), + "arbitraryField": json.RawMessage(`"arbitraryValue"`), + "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"`), @@ -688,12 +691,13 @@ func TestCreateSanitizedImpExt(t *testing.T) { "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"`), + "arbitraryField": json.RawMessage(`"arbitraryValue"`), + "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: "", }, @@ -731,7 +735,7 @@ func TestCleanOpenRTBRequests(t *testing.T) { consentedVendors: map[string]bool{"appnexus": true}, }, { - req: AuctionRequest{BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: newAdapterAliasBidRequest(t)}, UserSyncs: &emptyUsersync{}, TCF2Config: emptyTCF2Config}, + req: AuctionRequest{BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: newAdapterAliasBidRequest()}, UserSyncs: &emptyUsersync{}, TCF2Config: emptyTCF2Config}, bidReqAssertions: assertReq, hasError: false, applyCOPPA: false, @@ -798,17 +802,17 @@ func TestCleanOpenRTBRequestsWithFPD(t *testing.T) { }, { description: "Pass valid FPD data for bidders specified in request", - req: AuctionRequest{BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: newAdapterAliasBidRequest(t)}, UserSyncs: &emptyUsersync{}, FirstPartyData: fpd, TCF2Config: emptyTCF2Config}, + req: AuctionRequest{BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: newAdapterAliasBidRequest()}, UserSyncs: &emptyUsersync{}, FirstPartyData: fpd, TCF2Config: emptyTCF2Config}, fpdExpected: true, }, { description: "Bidders specified in request but there is no fpd data for this bidder", - req: AuctionRequest{BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: newAdapterAliasBidRequest(t)}, UserSyncs: &emptyUsersync{}, FirstPartyData: make(map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData), TCF2Config: emptyTCF2Config}, + req: AuctionRequest{BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: newAdapterAliasBidRequest()}, UserSyncs: &emptyUsersync{}, FirstPartyData: make(map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData), TCF2Config: emptyTCF2Config}, fpdExpected: false, }, { description: "No FPD data passed", - req: AuctionRequest{BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: newAdapterAliasBidRequest(t)}, UserSyncs: &emptyUsersync{}, FirstPartyData: nil, TCF2Config: emptyTCF2Config}, + req: AuctionRequest{BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: newAdapterAliasBidRequest()}, UserSyncs: &emptyUsersync{}, FirstPartyData: nil, TCF2Config: emptyTCF2Config}, fpdExpected: false, }, } @@ -1273,7 +1277,7 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) { } for _, test := range testCases { - req := newBidRequest(t) + req := newBidRequest() req.Ext = test.reqExt req.Regs = &openrtb2.Regs{ USPrivacy: test.ccpaConsent, @@ -1359,7 +1363,7 @@ func TestCleanOpenRTBRequestsCCPAErrors(t *testing.T) { } for _, test := range testCases { - req := newBidRequest(t) + req := newBidRequest() req.Ext = test.reqExt req.Regs = &openrtb2.Regs{USPrivacy: test.reqRegsPrivacy} @@ -1428,7 +1432,7 @@ func TestCleanOpenRTBRequestsCOPPA(t *testing.T) { } for _, test := range testCases { - req := newBidRequest(t) + req := newBidRequest() req.Regs = &openrtb2.Regs{COPPA: test.coppa} auctionReq := AuctionRequest{ @@ -1582,7 +1586,7 @@ func TestCleanOpenRTBRequestsSChain(t *testing.T) { } for _, test := range testCases { - req := newBidRequest(t) + req := newBidRequest() if test.inSChain != nil { req.Source.SChain = test.inSChain } @@ -1657,7 +1661,7 @@ func TestCleanOpenRTBRequestsBidderParams(t *testing.T) { } for _, test := range testCases { - req := newBidRequestWithBidderParams(t) + req := newBidRequestWithBidderParams() var extRequest *openrtb_ext.ExtRequest if test.inExt != nil { req.Ext = test.inExt @@ -1964,86 +1968,95 @@ func TestGetExtCacheInstructions(t *testing.T) { } func TestGetExtTargetData(t *testing.T) { - type inTest struct { - requestExtPrebid *openrtb_ext.ExtRequestPrebid - cacheInstructions extCacheInstructions - } - type outTest struct { - targetData *targetData - nilTargetData bool - } testCases := []struct { - desc string - in inTest - out outTest + name string + givenRequestExtPrebid *openrtb_ext.ExtRequestPrebid + givenCacheInstructions extCacheInstructions + expectTargetData *targetData }{ { - "nil requestExt, nil outTargetData", - inTest{ - requestExtPrebid: nil, - cacheInstructions: extCacheInstructions{ - cacheBids: true, - cacheVAST: true, - }, - }, - outTest{targetData: nil, nilTargetData: true}, + name: "nil", + givenRequestExtPrebid: nil, + givenCacheInstructions: extCacheInstructions{cacheBids: true, cacheVAST: true}, + expectTargetData: nil, }, { - "Valid requestExt, nil Targeting field, nil outTargetData", - inTest{ - requestExtPrebid: &openrtb_ext.ExtRequestPrebid{ - Targeting: nil, - }, - cacheInstructions: extCacheInstructions{ - cacheBids: true, - cacheVAST: true, - }, - }, - outTest{targetData: nil, nilTargetData: true}, + name: "nil-targeting", + givenRequestExtPrebid: &openrtb_ext.ExtRequestPrebid{Targeting: nil}, + givenCacheInstructions: extCacheInstructions{cacheBids: true, cacheVAST: true}, + expectTargetData: nil, }, { - "Valid targeting data in requestExt, valid outTargetData", - inTest{ - requestExtPrebid: &openrtb_ext.ExtRequestPrebid{ - Targeting: &openrtb_ext.ExtRequestTargeting{ - PriceGranularity: &openrtb_ext.PriceGranularity{ - Precision: ptrutil.ToPtr(2), - Ranges: []openrtb_ext.GranularityRange{{Min: 0.00, Max: 5.00, Increment: 1.00}}, - }, - IncludeWinners: ptrutil.ToPtr(true), - IncludeBidderKeys: ptrutil.ToPtr(true), + name: "populated-full", + givenRequestExtPrebid: &openrtb_ext.ExtRequestPrebid{ + Targeting: &openrtb_ext.ExtRequestTargeting{ + AlwaysIncludeDeals: true, + IncludeBidderKeys: ptrutil.ToPtr(true), + IncludeFormat: true, + IncludeWinners: ptrutil.ToPtr(true), + MediaTypePriceGranularity: &openrtb_ext.MediaTypePriceGranularity{}, + PreferDeals: true, + PriceGranularity: &openrtb_ext.PriceGranularity{ + Precision: ptrutil.ToPtr(2), + Ranges: []openrtb_ext.GranularityRange{{Min: 0.00, Max: 5.00, Increment: 1.00}}, }, }, - cacheInstructions: extCacheInstructions{ - cacheBids: true, - cacheVAST: true, + }, + givenCacheInstructions: extCacheInstructions{ + cacheBids: true, + cacheVAST: true, + }, + expectTargetData: &targetData{ + alwaysIncludeDeals: true, + includeBidderKeys: true, + includeCacheBids: true, + includeCacheVast: true, + includeFormat: true, + includeWinners: true, + mediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{}, + preferDeals: true, + priceGranularity: openrtb_ext.PriceGranularity{ + Precision: ptrutil.ToPtr(2), + Ranges: []openrtb_ext.GranularityRange{{Min: 0.00, Max: 5.00, Increment: 1.00}}, }, }, - outTest{ - targetData: &targetData{ - priceGranularity: openrtb_ext.PriceGranularity{ - Precision: ptrutil.ToPtr(2), - Ranges: []openrtb_ext.GranularityRange{{Min: 0.00, Max: 5.00, Increment: 1.00}}, - }, - includeWinners: true, - includeBidderKeys: true, - includeCacheBids: true, - includeCacheVast: true, + }, + { + name: "populated-pointers-nil", + givenRequestExtPrebid: &openrtb_ext.ExtRequestPrebid{ + Targeting: &openrtb_ext.ExtRequestTargeting{ + AlwaysIncludeDeals: true, + IncludeBidderKeys: nil, + IncludeFormat: true, + IncludeWinners: nil, + MediaTypePriceGranularity: nil, + PreferDeals: true, + PriceGranularity: nil, }, - nilTargetData: false, + }, + givenCacheInstructions: extCacheInstructions{ + cacheBids: true, + cacheVAST: true, + }, + expectTargetData: &targetData{ + alwaysIncludeDeals: true, + includeBidderKeys: false, + includeCacheBids: true, + includeCacheVast: true, + includeFormat: true, + includeWinners: false, + mediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{}, + preferDeals: true, + priceGranularity: openrtb_ext.PriceGranularity{}, }, }, } for _, test := range testCases { - actualTargetData := getExtTargetData(test.in.requestExtPrebid, test.in.cacheInstructions) - - if test.out.nilTargetData { - assert.Nil(t, actualTargetData, "%s. Targeting data should be nil. \n", test.desc) - } else { - assert.NotNil(t, actualTargetData, "%s. Targeting data should NOT be nil. \n", test.desc) - assert.Equal(t, *test.out.targetData, *actualTargetData, "%s. Unexpected targeting data value. \n", test.desc) - } + t.Run(test.name, func(t *testing.T) { + result := getExtTargetData(test.givenRequestExtPrebid, test.givenCacheInstructions) + assert.Equal(t, test.expectTargetData, result) + }) } } @@ -2249,7 +2262,7 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) { } for _, test := range testCases { - req := newBidRequest(t) + req := newBidRequest() req.Device.Lmt = test.lmt auctionReq := AuctionRequest{ @@ -2355,7 +2368,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { } for _, test := range testCases { - req := newBidRequest(t) + req := newBidRequest() req.User.Consent = test.gdprConsent privacyConfig := config.Privacy{} @@ -2446,7 +2459,7 @@ func TestCleanOpenRTBRequestsGDPRBlockBidRequest(t *testing.T) { } for _, test := range testCases { - req := newBidRequest(t) + req := newBidRequest() req.Regs = &openrtb2.Regs{ Ext: json.RawMessage(`{"gdpr":1}`), } @@ -2510,7 +2523,7 @@ func TestCleanOpenRTBRequestsGDPRBlockBidRequest(t *testing.T) { func TestCleanOpenRTBRequestsWithOpenRTBDowngrade(t *testing.T) { emptyTCF2Config := gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}) - bidReq := newBidRequest(t) + bidReq := newBidRequest() bidReq.Regs = &openrtb2.Regs{} bidReq.Regs.GPP = "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1NYN" bidReq.Regs.GPPSID = []int8{6} @@ -2713,6 +2726,11 @@ func TestBuildRequestExtForBidder(t *testing.T) { 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"]}}},"multibid":[{"bidder":"foo3","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidders":["pubmatic","groupm"],"maxbids":4}]}}`), 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"]}}}}}`), }, + { + name: "targeting", + requestExt: json.RawMessage(`{"prebid":{"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":20,"increment":0.1}]},"mediatypepricegranularity":{},"includebidderkeys":true,"includewinners":true,"includebrandcategory":{"primaryadserver":1,"publisher":"anyPublisher","withcategory":true}}}}`), + expectedJson: json.RawMessage(`{"prebid":{"targeting":{"includebrandcategory":{"primaryadserver":1,"publisher":"anyPublisher","withcategory":true}}}}`), + }, } for _, test := range testCases { @@ -2772,8 +2790,46 @@ func TestBuildRequestExtForBidder_RequestExtMalformed(t *testing.T) { assert.EqualError(t, err, "expect { or n, but found m") } +func TestBuildRequestExtTargeting(t *testing.T) { + t.Run("nil", func(t *testing.T) { + result := buildRequestExtTargeting(nil) + assert.Nil(t, result) + }) + + t.Run("brandcategory-nil", func(t *testing.T) { + given := &openrtb_ext.ExtRequestTargeting{} + + result := buildRequestExtTargeting(given) + assert.Nil(t, result) + }) + + t.Run("brandcategory-populated", func(t *testing.T) { + brandCatgory := &openrtb_ext.ExtIncludeBrandCategory{ + PrimaryAdServer: 1, + Publisher: "anyPublisher", + WithCategory: true, + TranslateCategories: ptrutil.ToPtr(true), + } + + given := &openrtb_ext.ExtRequestTargeting{ + PriceGranularity: &openrtb_ext.PriceGranularity{}, + IncludeBrandCategory: brandCatgory, + IncludeWinners: ptrutil.ToPtr(true), + } + + expected := &openrtb_ext.ExtRequestTargeting{ + PriceGranularity: nil, + IncludeBrandCategory: brandCatgory, + IncludeWinners: nil, + } + + result := buildRequestExtTargeting(given) + assert.Equal(t, expected, result) + }) +} + // newAdapterAliasBidRequest builds a BidRequest with aliases -func newAdapterAliasBidRequest(t *testing.T) *openrtb2.BidRequest { +func newAdapterAliasBidRequest() *openrtb2.BidRequest { dnt := int8(1) return &openrtb2.BidRequest{ Site: &openrtb2.Site{ @@ -2819,7 +2875,7 @@ func newAdapterAliasBidRequest(t *testing.T) *openrtb2.BidRequest { } } -func newBidRequest(t *testing.T) *openrtb2.BidRequest { +func newBidRequest() *openrtb2.BidRequest { return &openrtb2.BidRequest{ Site: &openrtb2.Site{ Page: "www.some.domain.com", @@ -2873,7 +2929,7 @@ func newBidRequest(t *testing.T) *openrtb2.BidRequest { } } -func newBidRequestWithBidderParams(t *testing.T) *openrtb2.BidRequest { +func newBidRequestWithBidderParams() *openrtb2.BidRequest { return &openrtb2.BidRequest{ Site: &openrtb2.Site{ Page: "www.some.domain.com", @@ -3386,7 +3442,7 @@ func TestCleanOpenRTBRequestsBidAdjustment(t *testing.T) { }, } for _, test := range testCases { - req := newBidRequest(t) + req := newBidRequest() accountConfig := config.Account{ GDPR: config.AccountGDPR{ Enabled: &falseValue, @@ -3423,6 +3479,166 @@ func TestCleanOpenRTBRequestsBidAdjustment(t *testing.T) { } } +func TestCleanOpenRTBRequestsBuyerUID(t *testing.T) { + tcf2Consent := "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA" + + buyerUIDAppnexus := `{"appnexus": "a"}` + buyerUIDAppnexusMixedCase := `{"aPpNeXuS": "a"}` + buyerUIDBoth := `{"appnexus": "a", "pubmatic": "b"}` + + bidderParamsAppnexus := `{"appnexus": {"placementId": 1}}` + bidderParamsBoth := `{"appnexus": {"placementId": 1}, "pubmatic": {"publisherId": "abc"}}` + + tests := []struct { + name string + bidderParams string + user openrtb2.User + expectedUsers map[string]openrtb2.User + }{ + { + name: "one-bidder-with-prebid-buyeruid", + bidderParams: bidderParamsAppnexus, + user: openrtb2.User{ + ID: "some-id", + Ext: json.RawMessage(`{"data": 1, "test": 2, "prebid": {"buyeruids": ` + buyerUIDAppnexus + `}}`), + Consent: tcf2Consent, + }, + expectedUsers: map[string]openrtb2.User{ + "appnexus": { + ID: "some-id", + BuyerUID: "a", + Ext: json.RawMessage(`{"consent":"` + tcf2Consent + `","data":1,"test":2}`), + }, + }, + }, + { + name: "one-bidder-with-prebid-buyeruid-mixed-case", + bidderParams: bidderParamsAppnexus, + user: openrtb2.User{ + ID: "some-id", + Ext: json.RawMessage(`{"data": 1, "test": 2, "prebid": {"buyeruids": ` + buyerUIDAppnexusMixedCase + `}}`), + Consent: tcf2Consent, + }, + expectedUsers: map[string]openrtb2.User{ + "appnexus": { + ID: "some-id", + BuyerUID: "a", + Ext: json.RawMessage(`{"consent":"` + tcf2Consent + `","data":1,"test":2}`), + }, + }, + }, + { + name: "one-bidder-with-buyeruid-already-set", + bidderParams: bidderParamsAppnexus, + user: openrtb2.User{ + ID: "some-id", + BuyerUID: "already-set-buyeruid", + Ext: json.RawMessage(`{"data": 1, "test": 2, "prebid": {"buyeruids": ` + buyerUIDAppnexus + `}}`), + Consent: tcf2Consent, + }, + expectedUsers: map[string]openrtb2.User{ + "appnexus": { + ID: "some-id", + BuyerUID: "already-set-buyeruid", + Ext: json.RawMessage(`{"consent":"` + tcf2Consent + `","data":1,"test":2}`), + }, + }, + }, + { + name: "two-bidder-with-prebid-buyeruids", + bidderParams: bidderParamsBoth, + user: openrtb2.User{ + ID: "some-id", + Ext: json.RawMessage(`{"data": 1, "test": 2, "prebid": {"buyeruids": ` + buyerUIDBoth + `}}`), + Consent: tcf2Consent, + }, + expectedUsers: map[string]openrtb2.User{ + "appnexus": { + ID: "some-id", + BuyerUID: "a", + Ext: json.RawMessage(`{"consent":"` + tcf2Consent + `","data":1,"test":2}`), + }, + "pubmatic": { + ID: "some-id", + BuyerUID: "b", + Ext: json.RawMessage(`{"consent":"` + tcf2Consent + `","data":1,"test":2}`), + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + + req := &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ + ID: "some-publisher-id", + }, + }, + Imp: []openrtb2.Imp{{ + ID: "some-imp-id", + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{ + W: 300, + H: 250, + }}, + }, + Ext: json.RawMessage(`{"prebid":{"tid":"123", "bidder":` + string(test.bidderParams) + `}}`), + }}, + User: &test.user, + } + + auctionReq := AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: req}, + UserSyncs: &emptyUsersync{}, + Account: config.Account{ + GDPR: config.AccountGDPR{ + Enabled: ptrutil.ToPtr(false), + }, + }, + TCF2Config: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + } + gdprPermissionsBuilder := fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + passGeo: false, + passID: false, + activitiesError: nil, + }, + }.Builder + + reqSplitter := &requestSplitter{ + bidderToSyncerKey: map[string]string{}, + me: &metrics.MetricsEngineMock{}, + gdprPermsBuilder: gdprPermissionsBuilder, + hostSChainNode: nil, + bidderInfo: config.BidderInfos{ + "appnexus": config.BidderInfo{ + OpenRTB: &config.OpenRTBInfo{ + Version: "2.5", + }, + }, + "pubmatic": config.BidderInfo{ + OpenRTB: &config.OpenRTBInfo{ + Version: "2.5", + }, + }, + }, + } + + results, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, false, nil) + + assert.Empty(t, errs) + for _, v := range results { + require.NotNil(t, v.BidRequest, "bidrequest") + require.NotNil(t, v.BidRequest.User, "bidrequest.user") + assert.Equal(t, test.expectedUsers[string(v.BidderName)], *v.BidRequest.User) + } + }) + } +} + func TestApplyFPD(t *testing.T) { testCases := []struct { description string @@ -3857,7 +4073,7 @@ func TestCleanOpenRTBRequestsFilterBidderRequestExt(t *testing.T) { } for _, test := range testCases { - req := newBidRequestWithBidderParams(t) + req := newBidRequestWithBidderParams() req.Ext = nil var extRequest *openrtb_ext.ExtRequest if test.inExt != nil { @@ -4813,7 +5029,7 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) { }{ { name: "fetch_bids_request_with_one_bidder_allowed", - req: newBidRequest(t), + req: newBidRequest(), privacyConfig: getFetchBidsActivityConfig("appnexus", true), ortbVersion: "2.6", expectedReqNumber: 1, @@ -4823,7 +5039,7 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) { }, { name: "fetch_bids_request_with_one_bidder_not_allowed", - req: newBidRequest(t), + req: newBidRequest(), privacyConfig: getFetchBidsActivityConfig("appnexus", false), expectedReqNumber: 0, expectedUser: expectedUserDefault, @@ -4832,7 +5048,7 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) { }, { name: "transmit_ufpd_allowed", - req: newBidRequest(t), + req: newBidRequest(), privacyConfig: getTransmitUFPDActivityConfig("appnexus", true), ortbVersion: "2.6", expectedReqNumber: 1, @@ -4844,7 +5060,7 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) { //remove user.eids, user.ext.data.*, user.data.*, user.{id, buyeruid, yob, gender} //and device-specific IDs name: "transmit_ufpd_deny", - req: newBidRequest(t), + req: newBidRequest(), privacyConfig: getTransmitUFPDActivityConfig("appnexus", false), expectedReqNumber: 1, expectedUser: openrtb2.User{ @@ -4874,7 +5090,7 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) { }, { name: "transmit_precise_geo_allowed", - req: newBidRequest(t), + req: newBidRequest(), privacyConfig: getTransmitPreciseGeoActivityConfig("appnexus", true), ortbVersion: "2.6", expectedReqNumber: 1, @@ -4886,7 +5102,7 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) { //round user's geographic location by rounding off IP address and lat/lng data. //this applies to both device.geo and user.geo name: "transmit_precise_geo_deny", - req: newBidRequest(t), + req: newBidRequest(), privacyConfig: getTransmitPreciseGeoActivityConfig("appnexus", false), ortbVersion: "2.6", expectedReqNumber: 1, @@ -4919,7 +5135,7 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) { }, { name: "transmit_tid_allowed", - req: newBidRequest(t), + req: newBidRequest(), privacyConfig: getTransmitTIDActivityConfig("appnexus", true), ortbVersion: "2.6", expectedReqNumber: 1, @@ -4930,7 +5146,7 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) { { //remove source.tid and imp.ext.tid name: "transmit_tid_deny", - req: newBidRequest(t), + req: newBidRequest(), privacyConfig: getTransmitTIDActivityConfig("appnexus", false), ortbVersion: "2.6", expectedReqNumber: 1, @@ -5437,3 +5653,109 @@ func TestRemoveImpsWithStoredResponses(t *testing.T) { assert.Equal(t, testCase.expectedImps, request.Imp, "incorrect Impressions for testCase %s", testCase.description) } } + +func TestExtractAndCleanBuyerUIDs(t *testing.T) { + tests := []struct { + name string + user *openrtb2.User + expectedBuyerUIDs map[string]string + expectedUser *openrtb2.User + expectError bool + }{ + { + name: "user_is_nil", + user: nil, + expectedBuyerUIDs: nil, + expectedUser: nil, + expectError: false, + }, + { + name: "user.ext_is_nil", + user: &openrtb2.User{ + Ext: nil, + }, + expectedBuyerUIDs: nil, + expectedUser: &openrtb2.User{ + Ext: nil, + }, + expectError: false, + }, + { + name: "user.ext_malformed", + user: &openrtb2.User{ + Ext: json.RawMessage(`{"prebid":}`), + }, + expectedBuyerUIDs: nil, + expectedUser: &openrtb2.User{ + Ext: json.RawMessage(`{"prebid":}`), + }, + expectError: true, + }, + { + name: "user.ext.prebid_is_nil", + user: &openrtb2.User{ + Ext: json.RawMessage(`{"prebid":null}`), + }, + expectedBuyerUIDs: nil, + expectedUser: &openrtb2.User{ + Ext: nil, + }, + expectError: false, + }, + { + name: "user.ext.prebid.buyeruids_is_nil", + user: &openrtb2.User{ + Ext: json.RawMessage(`{"prebid":{"buyeruids": null}}`), + }, + expectedBuyerUIDs: nil, + expectedUser: &openrtb2.User{ + Ext: nil, + }, + expectError: false, + }, + { + name: "user.ext.prebid.buyeruids_has_one", + user: &openrtb2.User{ + Ext: json.RawMessage(`{"prebid":{"buyeruids": {"appnexus":"a"}}}`), + }, + expectedBuyerUIDs: map[string]string{"appnexus": "a"}, + expectedUser: &openrtb2.User{ + Ext: nil, + }, + expectError: false, + }, + { + name: "user.ext.prebid.buyeruids_has_many", + user: &openrtb2.User{ + Ext: json.RawMessage(`{"prebid":{"buyeruids": {"appnexus":"a", "pubmatic":"b"}}}`), + }, + expectedBuyerUIDs: map[string]string{"appnexus": "a", "pubmatic": "b"}, + expectedUser: &openrtb2.User{ + Ext: nil, + }, + expectError: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + req := openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + User: test.user, + }, + } + + result, err := extractAndCleanBuyerUIDs(&req) + if test.expectError { + assert.NotNil(t, err) + } else { + assert.Nil(t, err) + } + + assert.NoError(t, req.RebuildRequest()) + + assert.Equal(t, req.User, test.expectedUser) + assert.Equal(t, test.expectedBuyerUIDs, result) + }) + } +} diff --git a/floors/fetcher.go b/floors/fetcher.go index 071ee200353..9fa6ad4f380 100644 --- a/floors/fetcher.go +++ b/floors/fetcher.go @@ -309,8 +309,8 @@ func validateRules(config config.AccountFloorFetch, priceFloors *openrtb_ext.Pri return errors.New("skip rate should be greater than or equal to 0 and less than 100") } - if priceFloors.Data.FetchRate != nil && (*priceFloors.Data.FetchRate < dataRateMin || *priceFloors.Data.FetchRate > dataRateMax) { - return errors.New("FetchRate should be greater than or equal to 0 and less than or equal to 100") + if priceFloors.Data.UseFetchDataRate != nil && (*priceFloors.Data.UseFetchDataRate < dataRateMin || *priceFloors.Data.UseFetchDataRate > dataRateMax) { + return errors.New("usefetchdatarate should be greater than or equal to 0 and less than or equal to 100") } for _, modelGroup := range priceFloors.Data.ModelGroups { diff --git a/floors/fetcher_test.go b/floors/fetcher_test.go index 91ce496f32f..83876dc48b7 100644 --- a/floors/fetcher_test.go +++ b/floors/fetcher_test.go @@ -398,7 +398,7 @@ func TestValidatePriceFloorRules(t *testing.T) { wantErr: true, }, { - name: "Invalid FetchRate", + name: "Invalid UseFetchDataRate", args: args{ configs: config.AccountFloorFetch{ Enabled: true, @@ -417,7 +417,7 @@ func TestValidatePriceFloorRules(t *testing.T) { "*|*|www.website.com": 15.01, }, }}, - FetchRate: ptrutil.ToPtr(-11), + UseFetchDataRate: ptrutil.ToPtr(-11), }, }, }, diff --git a/floors/floors.go b/floors/floors.go index 9aa70aedbde..5bcd2e5e62c 100644 --- a/floors/floors.go +++ b/floors/floors.go @@ -166,7 +166,7 @@ func resolveFloors(account config.Account, bidRequestWrapper *openrtb_ext.Reques fetchResult, fetchStatus = priceFloorFetcher.Fetch(account.PriceFloors) } - if fetchResult != nil && fetchStatus == openrtb_ext.FetchSuccess && useFetchedData(fetchResult.Data.FetchRate) { + if fetchResult != nil && fetchStatus == openrtb_ext.FetchSuccess && useFetchedData(fetchResult.Data.UseFetchDataRate) { mergedFloor := mergeFloors(reqFloor, fetchResult, conversions) floorRules, errList = createFloorsFrom(mergedFloor, account, fetchStatus, openrtb_ext.FetchLocation) } else if reqFloor != nil { diff --git a/floors/floors_test.go b/floors/floors_test.go index 438a49f9281..8d73a5de0ae 100644 --- a/floors/floors_test.go +++ b/floors/floors_test.go @@ -791,7 +791,7 @@ func (m *MockFetchDataRate0) Fetch(configs config.AccountPriceFloors) (*openrtb_ }, }, }, - FetchRate: ptrutil.ToPtr(0), + UseFetchDataRate: ptrutil.ToPtr(0), }, } return &priceFloors, openrtb_ext.FetchSuccess @@ -831,7 +831,7 @@ func (m *MockFetchDataRate100) Fetch(configs config.AccountPriceFloors) (*openrt }, }, }, - FetchRate: ptrutil.ToPtr(100), + UseFetchDataRate: ptrutil.ToPtr(100), }, } return &priceFloors, openrtb_ext.FetchSuccess @@ -979,7 +979,7 @@ func TestResolveFloorsWithUseDataRate(t *testing.T) { }, }, }, - FetchRate: ptrutil.ToPtr(100), + UseFetchDataRate: ptrutil.ToPtr(100), }, }, }, diff --git a/hooks/hookexecution/executor.go b/hooks/hookexecution/executor.go index 70725de79c5..a1ff81f9714 100644 --- a/hooks/hookexecution/executor.go +++ b/hooks/hookexecution/executor.go @@ -229,10 +229,10 @@ func (e *hookExecutor) ExecuteRawBidderResponseStage(response *adapters.BidderRe stageName := hooks.StageRawBidderResponse.String() executionCtx := e.newContext(stageName) - payload := hookstage.RawBidderResponsePayload{Bids: response.Bids, Bidder: bidder} + payload := hookstage.RawBidderResponsePayload{BidderResponse: response, Bidder: bidder} outcome, payload, contexts, reject := executeStage(executionCtx, plan, payload, handler, e.metricEngine) - response.Bids = payload.Bids + response = payload.BidderResponse outcome.Entity = entity(bidder) outcome.Stage = stageName diff --git a/hooks/hookexecution/mocks_test.go b/hooks/hookexecution/mocks_test.go index 57205707b98..1a415adba84 100644 --- a/hooks/hookexecution/mocks_test.go +++ b/hooks/hookexecution/mocks_test.go @@ -151,7 +151,7 @@ func (e mockTimeoutHook) HandleRawBidderResponseHook(_ context.Context, _ hookst time.Sleep(20 * time.Millisecond) c := hookstage.ChangeSet[hookstage.RawBidderResponsePayload]{} c.AddMutation(func(payload hookstage.RawBidderResponsePayload) (hookstage.RawBidderResponsePayload, error) { - payload.Bids[0].BidMeta = &openrtb_ext.ExtBidPrebidMeta{AdapterCode: "new-code"} + payload.BidderResponse.Bids[0].BidMeta = &openrtb_ext.ExtBidPrebidMeta{AdapterCode: "new-code"} return payload, nil }, hookstage.MutationUpdate, "bidderResponse", "bidMeta.AdapterCode") @@ -351,7 +351,7 @@ func (e mockUpdateBidderResponseHook) HandleRawBidderResponseHook(_ context.Cont c := hookstage.ChangeSet[hookstage.RawBidderResponsePayload]{} c.AddMutation( func(payload hookstage.RawBidderResponsePayload) (hookstage.RawBidderResponsePayload, error) { - payload.Bids[0].DealPriority = 10 + payload.BidderResponse.Bids[0].DealPriority = 10 return payload, nil }, hookstage.MutationUpdate, "bidderResponse", "bid.deal-priority", ) diff --git a/hooks/hookstage/rawbidderresponse.go b/hooks/hookstage/rawbidderresponse.go index 7f518211569..52d1d8a1ba1 100644 --- a/hooks/hookstage/rawbidderresponse.go +++ b/hooks/hookstage/rawbidderresponse.go @@ -21,10 +21,9 @@ type RawBidderResponse interface { ) (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. +// RawBidderResponsePayload consists of a bidder response returned by a particular bidder. +// Hooks are allowed to modify bidder response using mutations. type RawBidderResponsePayload struct { - Bids []*adapters.TypedBid - Bidder string + BidderResponse *adapters.BidderResponse + Bidder string } diff --git a/hooks/hookstage/rawbidderresponse_mutations.go b/hooks/hookstage/rawbidderresponse_mutations.go index 931d76f6a2e..027598c6518 100644 --- a/hooks/hookstage/rawbidderresponse_mutations.go +++ b/hooks/hookstage/rawbidderresponse_mutations.go @@ -29,11 +29,12 @@ type ChangeSetBids[T any] struct { changeSetRawBidderResponse ChangeSetRawBidderResponse[T] } -func (c ChangeSetBids[T]) Update(bids []*adapters.TypedBid) { +// UpdateBids updates the list of bids present in bidder-response using mutations. +func (c ChangeSetBids[T]) UpdateBids(bids []*adapters.TypedBid) { c.changeSetRawBidderResponse.changeSet.AddMutation(func(p T) (T, error) { bidderPayload, err := c.changeSetRawBidderResponse.castPayload(p) if err == nil { - bidderPayload.Bids = bids + bidderPayload.BidderResponse.Bids = bids } if payload, ok := any(bidderPayload).(T); ok { return payload, nil diff --git a/modules/prebid/ortb2blocking/hook_raw_bidder_response.go b/modules/prebid/ortb2blocking/hook_raw_bidder_response.go index f9b028b3c40..a6ddbdd58c3 100644 --- a/modules/prebid/ortb2blocking/hook_raw_bidder_response.go +++ b/modules/prebid/ortb2blocking/hook_raw_bidder_response.go @@ -34,7 +34,7 @@ func handleRawBidderResponseHook( // allowedBids will store all bids that have passed the attribute check allowedBids := make([]*adapters.TypedBid, 0) - for _, bid := range payload.Bids { + for _, bid := range payload.BidderResponse.Bids { failedChecksData := make(map[string]interface{}) bidMediaTypes := mediaTypesFromBid(bid) @@ -77,8 +77,8 @@ func handleRawBidderResponseHook( } changeSet := hookstage.ChangeSet[hookstage.RawBidderResponsePayload]{} - if len(payload.Bids) != len(allowedBids) { - changeSet.RawBidderResponse().Bids().Update(allowedBids) + if len(payload.BidderResponse.Bids) != len(allowedBids) { + changeSet.RawBidderResponse().Bids().UpdateBids(allowedBids) result.ChangeSet = changeSet } diff --git a/modules/prebid/ortb2blocking/module_test.go b/modules/prebid/ortb2blocking/module_test.go index d07ea6bd4d0..17008b2dad6 100644 --- a/modules/prebid/ortb2blocking/module_test.go +++ b/modules/prebid/ortb2blocking/module_test.go @@ -614,11 +614,13 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }{ { description: "Payload not changed when empty account config and empty module contexts are provided. Analytic tags have successful records", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ADomain: []string{"foo"}, ImpID: impID1}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ADomain: []string{"foo"}, ImpID: impID1}, + }, + }}, + }, expectedBids: []*adapters.TypedBid{ { Bid: &openrtb2.Bid{ADomain: []string{"foo"}, ImpID: impID1}, @@ -643,9 +645,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Catch error if wrong data has been passed from previous hook. Payload not changed", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ADomain: []string{"foo"}, ImpID: impID1}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ADomain: []string{"foo"}, ImpID: impID1}, + }, }, }}, expectedBids: []*adapters.TypedBid{ @@ -658,14 +662,16 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid blocked by badv attribute check. Payload updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, + }, + }}, + }, config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": true}}}`), expectedBids: []*adapters.TypedBid{ { @@ -701,14 +707,16 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid not blocked because blocking conditions for current bidder do not exist. Payload not updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, + }, + }}, + }, config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": true}}}`), expectedBids: []*adapters.TypedBid{ { @@ -742,14 +750,16 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid not blocked because enforce blocking is disabled by account config. Payload not updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, + }, + }}, + }, config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": false}}}`), expectedBids: []*adapters.TypedBid{ { @@ -783,14 +793,16 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid not blocked because enforce blocking overridden for given bidder. Payload not updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, + }, + }}, + }, config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": true, "action_overrides": {"enforce_blocks": [{"conditions": {"bidders": ["appnexus"]}, "override": false}]}}}}`), expectedBids: []*adapters.TypedBid{ { @@ -824,14 +836,16 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid blocked by badv attribute check (block unknown attributes). Payload updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, + }, + }}, + }, config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": true, "block_unknown_adomain": true}}}`), expectedBids: []*adapters.TypedBid{ { @@ -867,14 +881,16 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid not blocked because block unknown overridden for given bidder. Payload not updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", ImpID: impID2}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", ImpID: impID2}, + }, + }}, + }, config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": true, "block_unknown_adomain": true, "action_overrides": {"block_unknown_adomain": [{"conditions": {"bidders": ["appnexus"]}, "override": false}]}}}}`), expectedBids: []*adapters.TypedBid{ { @@ -908,14 +924,16 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid not blocked due to deal exception. Payload not updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1, DealID: "acceptDealID"}, - }, - { - Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1, DealID: "acceptDealID"}, + }, + { + Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, + }, + }}, + }, config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": true, "action_overrides": {"allowed_adomain_for_deals": [{"conditions": {"deal_ids": ["acceptDealID"]}, "override": ["forbidden_domain"]}]}}}}`), expectedBids: []*adapters.TypedBid{ { @@ -949,11 +967,13 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if there is an issue processing enforce blocks overrides for badv attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}}, + }, + }}, + }, config: json.RawMessage(`{"attributes": {"badv": {"enforce_blocks": true, "action_overrides": {"enforce_blocks": [{"conditions": {}}]}}}}`), expectedBids: []*adapters.TypedBid{ { @@ -974,11 +994,13 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if there is an issue processing block unknown domains overrides for badv attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}}, + }, + }}, + }, config: json.RawMessage(`{"attributes": {"badv": {"enforce_blocks": true, "action_overrides": {"block_unknown_adomain": [{"conditions": {}}]}}}}`), expectedBids: []*adapters.TypedBid{ { @@ -999,11 +1021,13 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if deal_ids not defined in config override conditions for badv attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, DealID: "acceptDealID"}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, DealID: "acceptDealID"}, + }, + }}, + }, config: json.RawMessage(`{"attributes": {"badv": {"enforce_blocks": true, "action_overrides": {"allowed_adomain_for_deals": [{"conditions": {}}]}}}}`), expectedBids: []*adapters.TypedBid{ { @@ -1024,14 +1048,16 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid blocked by bcat attribute check. Payload updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", Cat: []string{"moto"}, ImpID: impID2}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", Cat: []string{"moto"}, ImpID: impID2}, + }, + }}, + }, config: json.RawMessage(`{"attributes":{"bcat":{"enforce_blocks": true}}}`), expectedBids: []*adapters.TypedBid{ { @@ -1067,11 +1093,13 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if there is an issue processing enforce blocks overrides for bcat attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1}, + }, + }}, + }, config: json.RawMessage(`{"attributes": {"bcat": {"enforce_blocks": true, "action_overrides": {"enforce_blocks": [{"conditions": {}}]}}}}`), expectedBids: []*adapters.TypedBid{ { @@ -1092,11 +1120,13 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if there is an issue processing block unknown domains overrides for bcat attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1}, + }, + }}, + }, config: json.RawMessage(`{"attributes": {"bcat": {"enforce_blocks": true, "action_overrides": {"block_unknown_adv_cat": [{"conditions": {}}]}}}}`), expectedBids: []*adapters.TypedBid{ { @@ -1117,11 +1147,13 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if deal_ids not defined in config override conditions for bcat attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1, DealID: "acceptDealID"}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1, DealID: "acceptDealID"}, + }, + }}, + }, config: json.RawMessage(`{"attributes": {"bcat": {"enforce_blocks": true, "action_overrides": {"allowed_adv_cat_for_deals": [{"conditions": {}}]}}}}`), expectedBids: []*adapters.TypedBid{ { @@ -1142,14 +1174,16 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid blocked by cattax attribute check. Payload updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", CatTax: 1, ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", CatTax: 2, ImpID: impID2}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", CatTax: 1, ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", CatTax: 2, ImpID: impID2}, + }, + }}, + }, config: json.RawMessage(`{"attributes":{"bcat":{"enforce_blocks": true}}}`), expectedBids: []*adapters.TypedBid{ { @@ -1185,14 +1219,16 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid blocked by cattax attribute check (the default value used if no blocking attribute passed). Payload updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", CatTax: 1, ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", CatTax: 2, ImpID: impID2}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", CatTax: 1, ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", CatTax: 2, ImpID: impID2}, + }, + }}, + }, config: json.RawMessage(`{"attributes":{"bcat":{"enforce_blocks": true}}}`), expectedBids: []*adapters.TypedBid{ { @@ -1227,14 +1263,16 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid blocked by bapp attribute check. Payload updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Bundle: "forbidden_bundle", ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", Bundle: "allowed_bundle", ImpID: impID2}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Bundle: "forbidden_bundle", ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", Bundle: "allowed_bundle", ImpID: impID2}, + }, + }}, + }, config: json.RawMessage(`{"attributes":{"bapp":{"enforce_blocks": true}}}`), expectedBids: []*adapters.TypedBid{ { @@ -1270,11 +1308,13 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if there is an issue processing enforce blocks overrides for bapp attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Bundle: "forbidden_bundle", ImpID: impID1}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Bundle: "forbidden_bundle", ImpID: impID1}, + }, + }}, + }, config: json.RawMessage(`{"attributes": {"bapp": {"enforce_blocks": true, "action_overrides": {"enforce_blocks": [{"conditions": {}}]}}}}`), expectedBids: []*adapters.TypedBid{ { @@ -1295,11 +1335,13 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if deal_ids not defined in config override conditions for bapp attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Bundle: "forbidden_bundle", ImpID: impID1, DealID: "acceptDealID"}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Bundle: "forbidden_bundle", ImpID: impID1, DealID: "acceptDealID"}, + }, + }}, + }, config: json.RawMessage(`{"attributes": {"bapp": {"enforce_blocks": true, "action_overrides": {"allowed_app_for_deals": [{"conditions": {}}]}}}}`), expectedBids: []*adapters.TypedBid{ { @@ -1320,14 +1362,16 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid blocked by battr attribute check. Payload updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Attr: []adcom1.CreativeAttribute{1}, ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", Attr: []adcom1.CreativeAttribute{2}, ImpID: impID2}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Attr: []adcom1.CreativeAttribute{1}, ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", Attr: []adcom1.CreativeAttribute{2}, ImpID: impID2}, + }, + }}, + }, config: json.RawMessage(`{"attributes":{"battr":{"enforce_blocks": true}}}`), expectedBids: []*adapters.TypedBid{ { @@ -1363,11 +1407,13 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if there is an issue processing enforce blocks overrides for battr attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Attr: []adcom1.CreativeAttribute{1}, ImpID: impID1}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Attr: []adcom1.CreativeAttribute{1}, ImpID: impID1}, + }, + }}, + }, config: json.RawMessage(`{"attributes": {"battr": {"enforce_blocks": true, "action_overrides": {"enforce_blocks": [{"conditions": {}}]}}}}`), expectedBids: []*adapters.TypedBid{ { @@ -1388,11 +1434,13 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if deal_ids not defined in config override conditions for battr attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Attr: []adcom1.CreativeAttribute{1}, ImpID: impID1, DealID: "acceptDealID"}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Attr: []adcom1.CreativeAttribute{1}, ImpID: impID1, DealID: "acceptDealID"}, + }, + }}, + }, config: json.RawMessage(`{"attributes": {"battr": {"enforce_blocks": true, "action_overrides": {"allowed_banner_attr_for_deals": [{"conditions": {}}]}}}}`), expectedBids: []*adapters.TypedBid{ { @@ -1413,30 +1461,32 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid blocked by multiple attribute check. Payload updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ - ID: "1", - ADomain: []string{"forbidden_domain"}, - Cat: []string{"fishing"}, - CatTax: 1, - Bundle: "forbidden_bundle", - Attr: []adcom1.CreativeAttribute{1}, - ImpID: impID1, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "1", + ADomain: []string{"forbidden_domain"}, + Cat: []string{"fishing"}, + CatTax: 1, + Bundle: "forbidden_bundle", + Attr: []adcom1.CreativeAttribute{1}, + ImpID: impID1, + }, }, - }, - { - Bid: &openrtb2.Bid{ - ID: "2", - ADomain: []string{"allowed_domain"}, - Cat: []string{"moto"}, - CatTax: 2, - Bundle: "allowed_bundle", - Attr: []adcom1.CreativeAttribute{2}, - ImpID: impID2, + { + Bid: &openrtb2.Bid{ + ID: "2", + ADomain: []string{"allowed_domain"}, + Cat: []string{"moto"}, + CatTax: 2, + Bundle: "allowed_bundle", + Attr: []adcom1.CreativeAttribute{2}, + ImpID: impID2, + }, }, - }, - }}, + }}, + }, config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": true}, "bcat":{"enforce_blocks": true}, "cattax":{"enforce_blocks": true}, "bapp":{"enforce_blocks": true}, "battr":{"enforce_blocks": true}}}`), expectedBids: []*adapters.TypedBid{ { @@ -1514,7 +1564,7 @@ func TestHandleRawBidderResponseHook(t *testing.T) { assert.NoError(t, err) test.payload = newPayload } - assert.Equal(t, test.expectedBids, test.payload.Bids, "Invalid Bids returned after executing RawBidderResponse hook.") + assert.Equal(t, test.expectedBids, test.payload.BidderResponse.Bids, "Invalid Bids returned after executing RawBidderResponse hook.") // reset ChangeSet not to break hookResult assertion, we validated ChangeSet separately hookResult.ChangeSet = hookstage.ChangeSet[hookstage.RawBidderResponsePayload]{} diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 70994548745..846a78540ca 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -161,6 +161,7 @@ var coreBidderNames []BidderName = []BidderName{ BidderMobfoxpb, BidderMobileFuse, BidderMotorik, + BidderNativo, BidderNextMillennium, BidderNoBid, BidderOms, @@ -276,7 +277,8 @@ const ( 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. - BidderReservedAE BidderName = "ae" // Reserved for FLEDGE Auction Environment + BidderReservedAE BidderName = "ae" // Reserved for PAAPI Auction Environment. + BidderReservedIGS BidderName = "igs" // Reserved for PAAPI Interest Group Seller object. ) // IsBidderNameReserved returns true if the specified name is a case insensitive match for a reserved bidder name. @@ -317,10 +319,14 @@ func IsBidderNameReserved(name string) bool { return true } + if strings.EqualFold(name, string(BidderReservedIGS)) { + return true + } + return false } -// IsPotentialBidder returns true if the name is not reserved witbin the imp[].ext context +// IsPotentialBidder returns true if the name is not reserved within the imp[].ext context func IsPotentialBidder(name string) bool { switch BidderName(name) { case BidderReservedContext: @@ -337,6 +343,8 @@ func IsPotentialBidder(name string) bool { return false case BidderReservedAE: return false + case BidderReservedIGS: + return false default: return true } @@ -491,6 +499,7 @@ const ( BidderMobfoxpb BidderName = "mobfoxpb" BidderMobileFuse BidderName = "mobilefuse" BidderMotorik BidderName = "motorik" + BidderNativo BidderName = "nativo" BidderNextMillennium BidderName = "nextmillennium" BidderNoBid BidderName = "nobid" BidderOms BidderName = "oms" diff --git a/openrtb_ext/convert_up.go b/openrtb_ext/convert_up.go index 2cfb07fb071..cfc9259b206 100644 --- a/openrtb_ext/convert_up.go +++ b/openrtb_ext/convert_up.go @@ -168,7 +168,7 @@ func moveRewardedFromPrebidExtTo26(i *ImpWrapper) { // read and clear prebid ext impExt, _ := i.GetImpExt() rwddPrebidExt := (*int8)(nil) - if p := impExt.GetPrebid(); p != nil { + if p := impExt.GetPrebid(); p != nil && p.IsRewardedInventory != nil { rwddPrebidExt = p.IsRewardedInventory p.IsRewardedInventory = nil impExt.SetPrebid(p) diff --git a/openrtb_ext/convert_up_test.go b/openrtb_ext/convert_up_test.go index f83b85be897..2b5c2303897 100644 --- a/openrtb_ext/convert_up_test.go +++ b/openrtb_ext/convert_up_test.go @@ -417,12 +417,12 @@ func TestMoveRewardedFromPrebidExtTo26(t *testing.T) { { description: "Not Present - Null Prebid Ext", givenImp: openrtb2.Imp{Ext: json.RawMessage(`{"prebid":null}`)}, - expectedImp: openrtb2.Imp{}, // empty prebid object pruned by RebuildImp + expectedImp: openrtb2.Imp{Ext: json.RawMessage(`{"prebid":null}`)}, }, { description: "Not Present - Empty Prebid Ext", givenImp: openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{}}`)}, - expectedImp: openrtb2.Imp{}, // empty prebid object pruned by RebuildImp + expectedImp: openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{}}`)}, }, { description: "Prebid Ext Migrated To 2.6", diff --git a/openrtb_ext/floors.go b/openrtb_ext/floors.go index e663229a797..1faa6292320 100644 --- a/openrtb_ext/floors.go +++ b/openrtb_ext/floors.go @@ -89,7 +89,7 @@ type PriceFloorData struct { ModelTimestamp int `json:"modeltimestamp,omitempty"` ModelGroups []PriceFloorModelGroup `json:"modelgroups,omitempty"` FloorProvider string `json:"floorprovider,omitempty"` - FetchRate *int `json:"fetchrate,omitempty"` + UseFetchDataRate *int `json:"usefetchdatarate,omitempty"` } type PriceFloorModelGroup struct { diff --git a/openrtb_ext/imp.go b/openrtb_ext/imp.go index e02cfadc593..85be3124548 100644 --- a/openrtb_ext/imp.go +++ b/openrtb_ext/imp.go @@ -43,6 +43,8 @@ type ExtImpPrebid struct { Options *Options `json:"options,omitempty"` + AdUnitCode string `json:"adunitcode,omitempty"` + Passthrough json.RawMessage `json:"passthrough,omitempty"` Floors *ExtImpPrebidFloors `json:"floors,omitempty"` diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index eb01098c097..46cdb1a674a 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -191,21 +191,21 @@ type Adjustment struct { // ExtRequestTargeting defines the contract for bidrequest.ext.prebid.targeting type ExtRequestTargeting struct { - PriceGranularity *PriceGranularity `json:"pricegranularity,omitempty"` - MediaTypePriceGranularity MediaTypePriceGranularity `json:"mediatypepricegranularity,omitempty"` - IncludeWinners *bool `json:"includewinners,omitempty"` - IncludeBidderKeys *bool `json:"includebidderkeys,omitempty"` - IncludeBrandCategory *ExtIncludeBrandCategory `json:"includebrandcategory,omitempty"` - IncludeFormat bool `json:"includeformat,omitempty"` - DurationRangeSec []int `json:"durationrangesec,omitempty"` - PreferDeals bool `json:"preferdeals,omitempty"` - AppendBidderNames bool `json:"appendbiddernames,omitempty"` - AlwaysIncludeDeals bool `json:"alwaysincludedeals,omitempty"` + PriceGranularity *PriceGranularity `json:"pricegranularity,omitempty"` + MediaTypePriceGranularity *MediaTypePriceGranularity `json:"mediatypepricegranularity,omitempty"` + IncludeWinners *bool `json:"includewinners,omitempty"` + IncludeBidderKeys *bool `json:"includebidderkeys,omitempty"` + IncludeBrandCategory *ExtIncludeBrandCategory `json:"includebrandcategory,omitempty"` + IncludeFormat bool `json:"includeformat,omitempty"` + DurationRangeSec []int `json:"durationrangesec,omitempty"` + PreferDeals bool `json:"preferdeals,omitempty"` + AppendBidderNames bool `json:"appendbiddernames,omitempty"` + AlwaysIncludeDeals bool `json:"alwaysincludedeals,omitempty"` } type ExtIncludeBrandCategory struct { - PrimaryAdServer int `json:"primaryadserver"` - Publisher string `json:"publisher"` + PrimaryAdServer int `json:"primaryadserver,omitempty"` + Publisher string `json:"publisher,omitempty"` WithCategory bool `json:"withcategory"` TranslateCategories *bool `json:"translatecategories,omitempty"` } diff --git a/openrtb_ext/request_wrapper_test.go b/openrtb_ext/request_wrapper_test.go index 650a1efbe93..73851238d00 100644 --- a/openrtb_ext/request_wrapper_test.go +++ b/openrtb_ext/request_wrapper_test.go @@ -189,8 +189,10 @@ func TestUserExt(t *testing.T) { func TestRebuildImp(t *testing.T) { var ( - prebid = &ExtImpPrebid{IsRewardedInventory: openrtb2.Int8Ptr(1)} - prebidJson = json.RawMessage(`{"prebid":{"is_rewarded_inventory":1}}`) + prebid = &ExtImpPrebid{IsRewardedInventory: openrtb2.Int8Ptr(1)} + prebidJson = json.RawMessage(`{"prebid":{"is_rewarded_inventory":1}}`) + prebidWithAdunitCode = &ExtImpPrebid{AdUnitCode: "adunitcode"} + prebidWithAdunitCodeJson = json.RawMessage(`{"prebid":{"adunitcode":"adunitcode"}}`) ) testCases := []struct { @@ -220,6 +222,13 @@ func TestRebuildImp(t *testing.T) { expectedRequest: openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "2", Ext: prebidJson}}}, expectedAccessed: true, }, + { + description: "One - Accessed - Dirty - AdUnitCode", + request: openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "1"}}}, + requestImpWrapper: []*ImpWrapper{{Imp: &openrtb2.Imp{ID: "1"}, impExt: &ImpExt{prebid: prebidWithAdunitCode, prebidDirty: true}}}, + expectedRequest: openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "1", Ext: prebidWithAdunitCodeJson}}}, + expectedAccessed: true, + }, { description: "One - Accessed - Error", request: openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "1"}}}, diff --git a/ortb/default.go b/ortb/default.go index 4a25051e78a..ed2523b147a 100644 --- a/ortb/default.go +++ b/ortb/default.go @@ -55,22 +55,24 @@ func setDefaultsTargeting(targeting *openrtb_ext.ExtRequestTargeting) bool { // Default price granularity can be overwritten for video, banner or native bid type // only in case targeting.MediaTypePriceGranularity.Video|Banner|Native != nil. - if targeting.MediaTypePriceGranularity.Video != nil { - if newVideoPG, updated := setDefaultsPriceGranularity(targeting.MediaTypePriceGranularity.Video); updated { - modified = true - targeting.MediaTypePriceGranularity.Video = newVideoPG + if targeting.MediaTypePriceGranularity != nil { + if targeting.MediaTypePriceGranularity.Video != nil { + if newVideoPG, updated := setDefaultsPriceGranularity(targeting.MediaTypePriceGranularity.Video); updated { + modified = true + targeting.MediaTypePriceGranularity.Video = newVideoPG + } } - } - if targeting.MediaTypePriceGranularity.Banner != nil { - if newBannerPG, updated := setDefaultsPriceGranularity(targeting.MediaTypePriceGranularity.Banner); updated { - modified = true - targeting.MediaTypePriceGranularity.Banner = newBannerPG + if targeting.MediaTypePriceGranularity.Banner != nil { + if newBannerPG, updated := setDefaultsPriceGranularity(targeting.MediaTypePriceGranularity.Banner); updated { + modified = true + targeting.MediaTypePriceGranularity.Banner = newBannerPG + } } - } - if targeting.MediaTypePriceGranularity.Native != nil { - if newNativePG, updated := setDefaultsPriceGranularity(targeting.MediaTypePriceGranularity.Native); updated { - modified = true - targeting.MediaTypePriceGranularity.Native = newNativePG + if targeting.MediaTypePriceGranularity.Native != nil { + if newNativePG, updated := setDefaultsPriceGranularity(targeting.MediaTypePriceGranularity.Native); updated { + modified = true + targeting.MediaTypePriceGranularity.Native = newNativePG + } } } diff --git a/ortb/default_test.go b/ortb/default_test.go index a52fc508cb7..fd77ba4c547 100644 --- a/ortb/default_test.go +++ b/ortb/default_test.go @@ -38,7 +38,7 @@ func TestSetDefaults(t *testing.T) { { name: "targeting", // tests integration with setDefaultsTargeting givenRequest: openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"targeting":{}}}`)}, - expectedRequest: openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":20,"increment":0.1}]},"mediatypepricegranularity":{},"includewinners":true,"includebidderkeys":true}}}`)}, + expectedRequest: openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":20,"increment":0.1}]},"includewinners":true,"includebidderkeys":true}}}`)}, }, { name: "imp", // tests integration with setDefaultsImp @@ -162,7 +162,7 @@ func TestSetDefaultsTargeting(t *testing.T) { Precision: ptrutil.ToPtr(4), Ranges: nil, }, - MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ + MediaTypePriceGranularity: &openrtb_ext.MediaTypePriceGranularity{ Video: &openrtb_ext.PriceGranularity{ Precision: ptrutil.ToPtr(4), Ranges: nil, @@ -179,7 +179,7 @@ func TestSetDefaultsTargeting(t *testing.T) { }, expectedTargeting: &openrtb_ext.ExtRequestTargeting{ PriceGranularity: &defaultGranularity, - MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ + MediaTypePriceGranularity: &openrtb_ext.MediaTypePriceGranularity{ Video: &defaultGranularity, Banner: &defaultGranularity, Native: &defaultGranularity, @@ -189,6 +189,23 @@ func TestSetDefaultsTargeting(t *testing.T) { }, expectedModified: true, }, + { + name: "populated-ranges-nil-mediatypepricegranularity-nil", + givenTargeting: &openrtb_ext.ExtRequestTargeting{ + PriceGranularity: &openrtb_ext.PriceGranularity{ + Precision: ptrutil.ToPtr(4), + Ranges: nil, + }, + MediaTypePriceGranularity: nil, + }, + expectedTargeting: &openrtb_ext.ExtRequestTargeting{ + PriceGranularity: &defaultGranularity, + MediaTypePriceGranularity: nil, + IncludeWinners: ptrutil.ToPtr(DefaultTargetingIncludeWinners), + IncludeBidderKeys: ptrutil.ToPtr(DefaultTargetingIncludeBidderKeys), + }, + expectedModified: true, + }, { name: "populated-ranges-empty", givenTargeting: &openrtb_ext.ExtRequestTargeting{ @@ -211,7 +228,7 @@ func TestSetDefaultsTargeting(t *testing.T) { Precision: ptrutil.ToPtr(4), Ranges: []openrtb_ext.GranularityRange{}, }, - MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ + MediaTypePriceGranularity: &openrtb_ext.MediaTypePriceGranularity{ Video: &openrtb_ext.PriceGranularity{ Precision: ptrutil.ToPtr(4), Ranges: []openrtb_ext.GranularityRange{}, @@ -228,7 +245,7 @@ func TestSetDefaultsTargeting(t *testing.T) { }, expectedTargeting: &openrtb_ext.ExtRequestTargeting{ PriceGranularity: &defaultGranularity, - MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ + MediaTypePriceGranularity: &openrtb_ext.MediaTypePriceGranularity{ Video: &defaultGranularity, Banner: &defaultGranularity, Native: &defaultGranularity, @@ -265,7 +282,7 @@ func TestSetDefaultsTargeting(t *testing.T) { Precision: ptrutil.ToPtr(4), Ranges: []openrtb_ext.GranularityRange{{Min: 0, Max: 10, Increment: 1}}, }, - MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ + MediaTypePriceGranularity: &openrtb_ext.MediaTypePriceGranularity{ Video: &openrtb_ext.PriceGranularity{ Precision: ptrutil.ToPtr(4), Ranges: []openrtb_ext.GranularityRange{{Min: 0, Max: 10, Increment: 1}}, @@ -287,7 +304,7 @@ func TestSetDefaultsTargeting(t *testing.T) { Precision: ptrutil.ToPtr(4), Ranges: []openrtb_ext.GranularityRange{{Min: 0, Max: 10, Increment: 1}}, }, - MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ + MediaTypePriceGranularity: &openrtb_ext.MediaTypePriceGranularity{ Video: &openrtb_ext.PriceGranularity{ Precision: ptrutil.ToPtr(4), Ranges: []openrtb_ext.GranularityRange{{Min: 0, Max: 10, Increment: 1}}}, diff --git a/ortb/request_validator.go b/ortb/request_validator.go index c7cc0a4ee9b..e959caa9345 100644 --- a/ortb/request_validator.go +++ b/ortb/request_validator.go @@ -88,22 +88,27 @@ func (srv *standardRequestValidator) validateImpExt(imp *openrtb_ext.ImpWrapper, prebid := impExt.GetOrCreatePrebid() prebidModified := false + bidderPromote := false + if prebid.Bidder == nil { prebid.Bidder = make(map[string]json.RawMessage) + bidderPromote = true } 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 openrtb_ext.IsPotentialBidder(k) { - if _, exists := prebid.Bidder[k]; !exists { - prebid.Bidder[k] = v - prebidModified = true + if bidderPromote { + for k, v := range ext { + if openrtb_ext.IsPotentialBidder(k) { + if _, exists := prebid.Bidder[k]; !exists { + prebid.Bidder[k] = v + prebidModified = true + } + delete(ext, k) + extModified = true } - delete(ext, k) - extModified = true } } @@ -117,7 +122,7 @@ func (srv *standardRequestValidator) validateImpExt(imp *openrtb_ext.ImpWrapper, errL := []error{} - for bidder, ext := range prebid.Bidder { + for bidder, val := range prebid.Bidder { coreBidder, _ := openrtb_ext.NormalizeBidderName(bidder) if tmp, isAlias := aliases[bidder]; isAlias { coreBidder = openrtb_ext.BidderName(tmp) @@ -125,7 +130,7 @@ func (srv *standardRequestValidator) validateImpExt(imp *openrtb_ext.ImpWrapper, if coreBidderNormalized, isValid := srv.bidderMap[coreBidder.String()]; isValid { if !cfg.SkipBidderParams { - if err := srv.paramsValidator.Validate(coreBidderNormalized, ext); err != nil { + if err := srv.paramsValidator.Validate(coreBidderNormalized, val); err != nil { return []error{fmt.Errorf("request.imp[%d].ext.prebid.bidder.%s failed validation.\n%v", impIndex, bidder, err)} } } @@ -134,6 +139,11 @@ func (srv *standardRequestValidator) validateImpExt(imp *openrtb_ext.ImpWrapper, errL = append(errL, &errortypes.BidderTemporarilyDisabled{Message: msg}) delete(prebid.Bidder, bidder) prebidModified = true + } else if bidderPromote { + errL = append(errL, &errortypes.Warning{Message: fmt.Sprintf("request.imp[%d].ext contains unknown bidder: '%s', ignoring", impIndex, bidder)}) + ext[bidder] = val + delete(prebid.Bidder, bidder) + prebidModified = true } else { 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)} } diff --git a/ortb/request_validator_test.go b/ortb/request_validator_test.go index abceda31efb..c2b6ea3c7bc 100644 --- a/ortb/request_validator_test.go +++ b/ortb/request_validator_test.go @@ -38,11 +38,18 @@ func TestValidateImpExt(t *testing.T) { { "Unknown bidder tests", []testCase{ + { + description: "Unknown Bidder + Empty Prebid Bidder", + impExt: json.RawMessage(`{"prebid":{"bidder":{}}, "unknownbidder":{"placement_id":555}}`), + expectedImpExt: `{"prebid":{"bidder":{}}, "unknownbidder":{"placement_id":555}}`, + expectedErrs: []error{errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder")}, + }, { description: "Unknown Bidder only", impExt: json.RawMessage(`{"unknownbidder":{"placement_id":555}}`), expectedImpExt: `{"unknownbidder":{"placement_id":555}}`, - expectedErrs: []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, + expectedErrs: []error{&errortypes.Warning{Message: ("request.imp[0].ext contains unknown bidder: 'unknownbidder', ignoring")}, + errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder")}, }, { description: "Unknown Prebid Ext Bidder only", @@ -60,19 +67,26 @@ func TestValidateImpExt(t *testing.T) { 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.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, + expectedErrs: []error{&errortypes.Warning{Message: ("request.imp[0].ext contains unknown bidder: 'unknownbidder', ignoring")}, + errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder"), + }, }, { 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.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, + expectedErrs: []error{&errortypes.Warning{Message: ("request.imp[0].ext contains unknown bidder: 'unknownbidder', ignoring")}, + &errortypes.BidderTemporarilyDisabled{Message: ("The bidder 'disabledbidder' has been disabled.")}, + errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder"), + }, }, { 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.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, + expectedErrs: []error{&errortypes.BidderTemporarilyDisabled{Message: ("The bidder 'disabledbidder' has been disabled.")}, + errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder"), + }, }, }, }, @@ -140,6 +154,15 @@ func TestValidateImpExt(t *testing.T) { expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}}}`, expectedErrs: []error{}, }, + { + // Since prebid.bidder object is present - bidders expected to be within it + // So even though appnexus is a valid bidder - it is ignored and considered to be an arbitrary field + // If there was no prebid.bidder then appnexus would have been considered a bidder. + description: "Valid bidder root ext + Empty Prebid Bidder", + impExt: json.RawMessage(`{"prebid":{"bidder":{}}, "appnexus":{"placement_id":555}}`), + expectedImpExt: `{"prebid":{"bidder":{}}, "appnexus":{"placement_id":555}}`, + expectedErrs: []error{errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder")}, + }, { description: "Valid bidder in prebid field", impExt: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555}}}}`), @@ -161,8 +184,8 @@ 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.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, + expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}}, "unknownbidder":{"placement_id":555}}`, + expectedErrs: []error{&errortypes.Warning{Message: ("request.imp[0].ext contains unknown bidder: 'unknownbidder', ignoring")}}, }, { description: "Valid Bidder + Disabled Bidder", @@ -179,15 +202,23 @@ func TestValidateImpExt(t *testing.T) { { 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.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, + expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}}, "unknownbidder":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`, + expectedErrs: []error{&errortypes.Warning{Message: ("request.imp[0].ext contains unknown bidder: 'unknownbidder', ignoring")}, + &errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}, + }, }, { - description: "Valid Prebid Ext Bidder + Disabled Bidder Ext", + description: "Valid Prebid Ext Bidder + Disabled Prebid Bidder Ext", impExt: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"}}}}`), expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id": 555}}}}`, expectedErrs: []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}}, }, + { + description: "Valid Prebid Ext Bidder + Arbitrary Key Ext", + impExt: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id": 555}}},"arbitraryKey":{"placement_id":555}}`), + expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id": 555}}},"arbitraryKey":{"placement_id":555}}`, + expectedErrs: []error{}, + }, { 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"}}}`), @@ -195,7 +226,7 @@ func TestValidateImpExt(t *testing.T) { 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", + description: "Valid Prebid Ext Bidder + Disabled Prebid Ext Bidder + Unknown Prebid 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: `{"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?")}, @@ -252,7 +283,7 @@ func TestValidateImpExt(t *testing.T) { } else { assert.Empty(t, imp.Ext, "imp.ext expected to be empty but was: %s. Test: %s. %s\n", string(imp.Ext), group.description, test.description) } - assert.Equal(t, test.expectedErrs, errs, "errs slice does not match expected. Test: %s. %s\n", group.description, test.description) + assert.ElementsMatch(t, test.expectedErrs, errs, "errs slice does not match expected. Test: %s. %s\n", group.description, test.description) }) } } diff --git a/static/bidder-info/algorix.yaml b/static/bidder-info/algorix.yaml index 9e6c3738f68..dc5f80a5ebd 100644 --- a/static/bidder-info/algorix.yaml +++ b/static/bidder-info/algorix.yaml @@ -1,6 +1,7 @@ endpoint: "https://{{.Host}}.svr-algorix.com/rtb/sa?sid={{.SourceId}}&token={{.AccountID}}" maintainer: email: "prebid@algorix.co" +gvlVendorID: 1176 capabilities: app: mediaTypes: diff --git a/static/bidder-info/copper6ssp.yaml b/static/bidder-info/copper6ssp.yaml index 92dc5cfb7e8..d1b3186dc2d 100644 --- a/static/bidder-info/copper6ssp.yaml +++ b/static/bidder-info/copper6ssp.yaml @@ -1,4 +1,5 @@ endpoint: "https://endpoint.copper6.com/" +gvlVendorID: 1356 maintainer: email: "info@copper6.com" capabilities: diff --git a/static/bidder-info/driftpixel.yaml b/static/bidder-info/driftpixel.yaml index e197cdbeabb..b97451a0d1f 100644 --- a/static/bidder-info/driftpixel.yaml +++ b/static/bidder-info/driftpixel.yaml @@ -13,7 +13,6 @@ capabilities: - video - native userSync: - # DriftPixel 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 + redirect: + url: "https://sync.driftpixel.live/psync?t=s&e=0&gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb={{.RedirectURL}}" + userMacro: "%USER_ID%" diff --git a/static/bidder-info/krushmedia.yaml b/static/bidder-info/krushmedia.yaml index 75d29b5ebe7..aa28b473ce5 100644 --- a/static/bidder-info/krushmedia.yaml +++ b/static/bidder-info/krushmedia.yaml @@ -14,5 +14,8 @@ capabilities: - native userSync: redirect: - url: "https://cs.krushmedia.com/4e4abdd5ecc661643458a730b1aa927d.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}" + url: "https://cs.krushmedia.com/pbserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redir={{.RedirectURL}}" + userMacro: "[UID]" + iframe: + url: "https://cs.krushmedia.com/pbserverIframe?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&pbserverUrl={{.RedirectURL}}" userMacro: "[UID]" diff --git a/static/bidder-info/nativo.yaml b/static/bidder-info/nativo.yaml new file mode 100644 index 00000000000..308fc6388db --- /dev/null +++ b/static/bidder-info/nativo.yaml @@ -0,0 +1,22 @@ +endpoint: "https://exchange.postrelease.com/esi?ntv_epid=7" +maintainer: + email: "prebiddev@nativo.com" +gvlVendorID: 263 +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native +userSync: + redirect: + url: https://jadserve.postrelease.com/suid/101787?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&ntv_gpp_consent={{.GPP}}&ntv_r={{.RedirectURL}} + userMacro: NTV_USER_ID +openrtb: + version: 2.6 + gpp-supported: true \ No newline at end of file diff --git a/static/bidder-info/pgamssp.yaml b/static/bidder-info/pgamssp.yaml index 4cb4ad4e1ed..75cec680c13 100644 --- a/static/bidder-info/pgamssp.yaml +++ b/static/bidder-info/pgamssp.yaml @@ -1,4 +1,5 @@ endpoint: "http://us-east.pgammedia.com/pserver" +gvlVendorID: 1353 maintainer: email: "info@pgammedia.com" capabilities: diff --git a/static/bidder-info/pubmatic.yaml b/static/bidder-info/pubmatic.yaml index 0a8283b4f98..6cb4cc8a5fe 100644 --- a/static/bidder-info/pubmatic.yaml +++ b/static/bidder-info/pubmatic.yaml @@ -22,4 +22,5 @@ userSync: url: "https://image8.pubmatic.com/AdServer/ImgSync?p=159706&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&pu={{.RedirectURL}}" userMacro: "#PMUID" openrtb: + version: 2.6 gpp-supported: true diff --git a/static/bidder-info/sharethrough.yaml b/static/bidder-info/sharethrough.yaml index 9ee8cb918f5..e16129030d6 100644 --- a/static/bidder-info/sharethrough.yaml +++ b/static/bidder-info/sharethrough.yaml @@ -2,6 +2,8 @@ endpoint: "https://btlr.sharethrough.com/universal/v1?supply_id=FGMrCMMc" maintainer: email: pubgrowth.engineering@sharethrough.com gvlVendorID: 80 +openrtb: + version: 2.6 capabilities: app: mediaTypes: diff --git a/static/bidder-info/unruly.yaml b/static/bidder-info/unruly.yaml index 62f03dec84d..1f00d816d73 100644 --- a/static/bidder-info/unruly.yaml +++ b/static/bidder-info/unruly.yaml @@ -1,5 +1,4 @@ endpoint: "https://targeting.unrulymedia.com/unruly_prebid_server" -endpointCompression: gzip geoscope: - global maintainer: diff --git a/static/bidder-params/nativo.json b/static/bidder-params/nativo.json new file mode 100644 index 00000000000..0a8139a0838 --- /dev/null +++ b/static/bidder-params/nativo.json @@ -0,0 +1,7 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Nativo Adapter Params", + "description": "A schema which validates params accepted by the Nativo adapter", + "type": "object", + "properties": {} +} \ No newline at end of file diff --git a/stored_requests/events/database/database.go b/stored_requests/events/database/database.go index f11da259db1..965922b2707 100644 --- a/stored_requests/events/database/database.go +++ b/stored_requests/events/database/database.go @@ -78,7 +78,7 @@ func (e *DatabaseEventProducer) Invalidations() <-chan events.Invalidation { } func (e *DatabaseEventProducer) fetchAll() (fetchErr error) { - timeout := e.cfg.CacheInitTimeout * time.Millisecond + timeout := e.cfg.CacheInitTimeout ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() @@ -115,7 +115,7 @@ func (e *DatabaseEventProducer) fetchAll() (fetchErr error) { } func (e *DatabaseEventProducer) fetchDelta() (fetchErr error) { - timeout := e.cfg.CacheUpdateTimeout * time.Millisecond + timeout := e.cfg.CacheUpdateTimeout ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel()