diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index bbb76d3675c..9a7ae4300ca 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -14,6 +14,7 @@ }, "containerEnv": { "GOPRIVATE": "${localEnv:GOPRIVATE}", + "PBS_GDPR_DEFAULT_VALUE": "0" }, "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ], diff --git a/adapters/33across/33across.go b/adapters/33across/33across.go index fb329a76f21..b6c16b20fbd 100644 --- a/adapters/33across/33across.go +++ b/adapters/33across/33across.go @@ -54,6 +54,7 @@ type bidTtxExt struct { func (a *TtxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error var adapterRequests []*adapters.RequestData + var groupedImps = make(map[string][]openrtb2.Imp) // Construct request extension common to all imps // NOTE: not blocking adapter requests on errors @@ -64,27 +65,39 @@ func (a *TtxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapter } request.Ext = reqExt - // Break up multi-imp request into multiple external requests since we don't - // support SRA in our exchange server + // We only support SRA for requests containing same prod and + // zoneID, therefore group all imps accordingly and create a http + // request for each such group for i := 0; i < len(request.Imp); i++ { - if adapterReq, err := a.makeRequest(*request, request.Imp[i]); err == nil { - adapterRequests = append(adapterRequests, adapterReq) + if impCopy, err := makeImps(request.Imp[i]); err == nil { + var impExt Ext + + // Skip over imps whose extensions cannot be read since + // we cannot glean Prod or ZoneID which are required to + // group together. However let's not block request creation. + if err := json.Unmarshal(impCopy.Ext, &impExt); err == nil { + impKey := impExt.Ttx.Prod + impExt.Ttx.Zoneid + groupedImps[impKey] = append(groupedImps[impKey], impCopy) + } else { + errs = append(errs, err) + } } else { errs = append(errs, err) } } + for _, impList := range groupedImps { + if adapterReq, err := a.makeRequest(*request, impList); err == nil { + adapterRequests = append(adapterRequests, adapterReq) + } else { + errs = append(errs, err) + } + } return adapterRequests, errs } -func (a *TtxAdapter) makeRequest(request openrtb2.BidRequest, imp openrtb2.Imp) (*adapters.RequestData, error) { - impCopy, err := makeImps(imp) - - if err != nil { - return nil, err - } - - request.Imp = []openrtb2.Imp{*impCopy} +func (a *TtxAdapter) makeRequest(request openrtb2.BidRequest, impList []openrtb2.Imp) (*adapters.RequestData, error) { + request.Imp = impList // Last Step reqJSON, err := json.Marshal(request) @@ -103,23 +116,23 @@ func (a *TtxAdapter) makeRequest(request openrtb2.BidRequest, imp openrtb2.Imp) }, nil } -func makeImps(imp openrtb2.Imp) (*openrtb2.Imp, error) { +func makeImps(imp openrtb2.Imp) (openrtb2.Imp, error) { if imp.Banner == nil && imp.Video == nil { - return nil, &errortypes.BadInput{ + return openrtb2.Imp{}, &errortypes.BadInput{ Message: fmt.Sprintf("Imp ID %s must have at least one of [Banner, Video] defined", imp.ID), } } var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { - return nil, &errortypes.BadInput{ + return openrtb2.Imp{}, &errortypes.BadInput{ Message: err.Error(), } } var ttxExt openrtb_ext.ExtImp33across if err := json.Unmarshal(bidderExt.Bidder, &ttxExt); err != nil { - return nil, &errortypes.BadInput{ + return openrtb2.Imp{}, &errortypes.BadInput{ Message: err.Error(), } } @@ -135,7 +148,7 @@ func makeImps(imp openrtb2.Imp) (*openrtb2.Imp, error) { impExtJSON, err := json.Marshal(impExt) if err != nil { - return nil, &errortypes.BadInput{ + return openrtb2.Imp{}, &errortypes.BadInput{ Message: err.Error(), } } @@ -149,13 +162,13 @@ func makeImps(imp openrtb2.Imp) (*openrtb2.Imp, error) { imp.Video = videoCopy if err != nil { - return nil, &errortypes.BadInput{ + return openrtb2.Imp{}, &errortypes.BadInput{ Message: err.Error(), } } } - return &imp, nil + return imp, nil } func makeReqExt(request *openrtb2.BidRequest) ([]byte, error) { diff --git a/adapters/33across/33acrosstest/exemplary/multi-imp-banner.json b/adapters/33across/33acrosstest/exemplary/multi-imp-banner.json index b6d19f55683..db03a001526 100644 --- a/adapters/33across/33acrosstest/exemplary/multi-imp-banner.json +++ b/adapters/33across/33acrosstest/exemplary/multi-imp-banner.json @@ -25,6 +25,30 @@ "productId": "inview" } } + }, + { + "id": "test-imp-id3", + "banner": { + "format": [{"w": 728, "h": 90}] + }, + "ext": { + "bidder": { + "siteId": "fake-site-id1", + "productId": "inview" + } + } + }, + { + "id": "test-imp-id4", + "banner": { + "format": [{"w": 728, "h": 90}] + }, + "ext": { + "bidder": { + "siteId": "fake-site-id", + "productId": "siab" + } + } } ], "site": {} @@ -58,6 +82,18 @@ "zoneid": "fake-site-id" } } + }, + { + "id":"test-imp-id2", + "banner": { + "format": [{"w": 728, "h": 90}] + }, + "ext": { + "ttx": { + "prod": "inview", + "zoneid": "fake-site-id" + } + } } ], "site": {} @@ -70,7 +106,8 @@ "seatbid": [ { "seat": "ttx", - "bid": [{ + "bid": [ + { "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", "impid": "test-imp-id1", "price": 0.500000, @@ -83,7 +120,22 @@ "mediaType": "banner" } } - }] + }, + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id2", + "price": 0.600000, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 90, + "w": 728, + "ext": { + "ttx": { + "mediaType": "banner" + } + } + } + ] } ], "cur": "USD" @@ -107,13 +159,74 @@ "id": "test-request-id", "imp": [ { - "id":"test-imp-id2", + "id":"test-imp-id3", "banner": { "format": [{"w": 728, "h": 90}] }, "ext": { "ttx": { "prod": "inview", + "zoneid": "fake-site-id1" + } + } + } + ], + "site": {} + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "ttx", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id3", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 90, + "w": 728, + "ext": { + "ttx": { + "mediaType": "banner" + } + } + } + ] + } + ], + "cur": "USD" + } + } + }, + { + "expectedRequest": { + "uri": "http://ssc.33across.com", + "body": { + "ext": { + "ttx": { + "caller": [ + { + "name": "Prebid-Server", + "version": "n/a" + } + ] + } + }, + "id": "test-request-id", + "imp": [ + { + "id":"test-imp-id4", + "banner": { + "format": [{"w": 728, "h": 90}] + }, + "ext": { + "ttx": { + "prod": "siab", "zoneid": "fake-site-id" } } @@ -129,10 +242,11 @@ "seatbid": [ { "seat": "ttx", - "bid": [{ + "bid": [ + { "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", - "impid": "test-imp-id2", - "price": 0.600000, + "impid": "test-imp-id4", + "price": 0.500000, "adm": "some-test-ad", "crid": "crid_10", "h": 90, @@ -142,7 +256,8 @@ "mediaType": "banner" } } - }] + } + ] } ], "cur": "USD" @@ -171,6 +286,23 @@ } }, "type": "banner" + }, + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id2", + "price": 0.6, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 728, + "h": 90, + "ext": { + "ttx": { + "mediaType": "banner" + } + } + }, + "type": "banner" } ] }, @@ -180,8 +312,30 @@ { "bid": { "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", - "impid": "test-imp-id2", - "price": 0.6, + "impid": "test-imp-id3", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 728, + "h": 90, + "ext": { + "ttx": { + "mediaType": "banner" + } + } + }, + "type": "banner" + } + ] + }, + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id4", + "price": 0.5, "adm": "some-test-ad", "crid": "crid_10", "w": 728, diff --git a/adapters/adapterstest/test_json.go b/adapters/adapterstest/test_json.go index 1ec2fc08d86..6d2533df82a 100644 --- a/adapters/adapterstest/test_json.go +++ b/adapters/adapterstest/test_json.go @@ -10,6 +10,8 @@ import ( "github.com/mitchellh/copystructure" "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/currency" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" "github.com/yudai/gojsondiff" "github.com/yudai/gojsondiff/formatter" @@ -103,7 +105,40 @@ func loadFile(filename string) (*testSpec, error) { // // More assertions will almost certainly be added in the future, as bugs come up. func runSpec(t *testing.T, filename string, spec *testSpec, bidder adapters.Bidder, isAmpTest, isVideoTest bool) { - reqInfo := adapters.ExtraRequestInfo{} + reqInfo := getTestExtraRequestInfo(t, filename, spec, isAmpTest, isVideoTest) + requests := testMakeRequestsImpl(t, filename, spec, bidder, reqInfo) + + testMakeBidsImpl(t, filename, spec, bidder, requests) +} + +// getTestExtraRequestInfo builds the ExtraRequestInfo object that will be passed to testMakeRequestsImpl +func getTestExtraRequestInfo(t *testing.T, filename string, spec *testSpec, isAmpTest, isVideoTest bool) *adapters.ExtraRequestInfo { + t.Helper() + + var reqInfo adapters.ExtraRequestInfo + + // If test request.ext defines its own currency rates, add currency conversion to reqInfo + reqWrapper := &openrtb_ext.RequestWrapper{} + reqWrapper.BidRequest = &spec.BidRequest + + reqExt, err := reqWrapper.GetRequestExt() + assert.NoError(t, err, "Could not unmarshall test request ext. %s", filename) + + reqPrebid := reqExt.GetPrebid() + if reqPrebid != nil && reqPrebid.CurrencyConversions != nil && len(reqPrebid.CurrencyConversions.ConversionRates) > 0 { + err = currency.ValidateCustomRates(reqPrebid.CurrencyConversions) + assert.NoError(t, err, "Error validating currency rates in the test request: %s", filename) + + // Get currency rates conversions from the test request.ext + conversions := currency.NewRates(reqPrebid.CurrencyConversions.ConversionRates) + + // Create return adapters.ExtraRequestInfo object + reqInfo = adapters.NewExtraRequestInfo(conversions) + } else { + reqInfo = adapters.ExtraRequestInfo{} + } + + // Set PbsEntryPoint if either isAmpTest or isVideoTest is true if isAmpTest { // simulates AMP entry point reqInfo.PbsEntryPoint = "amp" @@ -111,9 +146,7 @@ func runSpec(t *testing.T, filename string, spec *testSpec, bidder adapters.Bidd reqInfo.PbsEntryPoint = "video" } - requests := testMakeRequestsImpl(t, filename, spec, bidder, &reqInfo) - - testMakeBidsImpl(t, filename, spec, bidder, requests) + return &reqInfo } type testSpec struct { @@ -190,8 +223,15 @@ func assertMakeRequestsOutput(t *testing.T, filename string, actual []*adapters. if len(expected) != len(actual) { t.Fatalf("%s: MakeRequests had wrong request count. Expected %d, got %d", filename, len(expected), len(actual)) } - for i := 0; i < len(actual); i++ { - diffHttpRequests(t, fmt.Sprintf("%s: httpRequest[%d]", filename, i), actual[i], &(expected[i].Request)) + + for i := 0; i < len(expected); i++ { + var err error + for j := 0; j < len(actual); j++ { + if err = diffHttpRequests(fmt.Sprintf("%s: httpRequest[%d]", filename, i), actual[j], &(expected[i].Request)); err == nil { + break + } + } + assert.NoError(t, err, fmt.Sprintf("%s Expected RequestData was not returned by adapters' MakeRequests() implementation: httpRequest[%d]", filename, i)) } } @@ -242,19 +282,30 @@ func assertMakeBidsOutput(t *testing.T, filename string, bidderResponse *adapter // diffHttpRequests compares the actual HTTP request data to the expected one. // It assumes that the request bodies are JSON -func diffHttpRequests(t *testing.T, description string, actual *adapters.RequestData, expected *httpRequest) { +func diffHttpRequests(description string, actual *adapters.RequestData, expected *httpRequest) error { + if actual == nil { - t.Errorf("Bidders cannot return nil HTTP calls. %s was nil.", description) - return + return fmt.Errorf("Bidders cannot return nil HTTP calls. %s was nil.", description) + } + + if expected.Uri != actual.Uri { + return fmt.Errorf(`%s.uri "%s" does not match expected "%s."`, description, actual.Uri, expected.Uri) } - diffStrings(t, fmt.Sprintf("%s.uri", description), actual.Uri, expected.Uri) if expected.Headers != nil { - actualHeader, _ := json.Marshal(actual.Headers) - expectedHeader, _ := json.Marshal(expected.Headers) - diffJson(t, description, actualHeader, expectedHeader) + actualHeader, err := json.Marshal(actual.Headers) + if err != nil { + return fmt.Errorf(`%s actual.Headers could not be marshalled. Error: %s"`, description, err.Error()) + } + expectedHeader, err := json.Marshal(expected.Headers) + if err != nil { + return fmt.Errorf(`%s expected.Headers could not be marshalled. Error: %s"`, description, err.Error()) + } + if err := diffJson(description, actualHeader, expectedHeader); err != nil { + return err + } } - diffJson(t, description, actual.Body, expected.Body) + return diffJson(description, actual.Body, expected.Body) } func diffBids(t *testing.T, description string, actual *adapters.TypedBid, expected *expectedBid) { @@ -263,60 +314,54 @@ func diffBids(t *testing.T, description string, actual *adapters.TypedBid, expec return } - diffStrings(t, fmt.Sprintf("%s.type", description), string(actual.BidType), string(expected.Type)) - diffOrtbBids(t, fmt.Sprintf("%s.bid", description), actual.Bid, expected.Bid) + assert.Equal(t, string(expected.Type), string(actual.BidType), fmt.Sprintf(`%s.type "%s" does not match expected "%s."`, description, string(actual.BidType), string(expected.Type))) + assert.NoError(t, diffOrtbBids(fmt.Sprintf("%s.bid", description), actual.Bid, expected.Bid)) } // diffOrtbBids compares the actual Bid made by the adapter to the expectation from the JSON file. -func diffOrtbBids(t *testing.T, description string, actual *openrtb2.Bid, expected json.RawMessage) { +func diffOrtbBids(description string, actual *openrtb2.Bid, expected json.RawMessage) error { if actual == nil { - t.Errorf("Bidders cannot return nil Bids. %s was nil.", description) - return + return fmt.Errorf("Bidders cannot return nil Bids. %s was nil.", description) } actualJson, err := json.Marshal(actual) if err != nil { - t.Fatalf("%s failed to marshal actual Bid into JSON. %v", description, err) + return fmt.Errorf("%s failed to marshal actual Bid into JSON. %v", description, err) } - diffJson(t, description, actualJson, expected) -} - -func diffStrings(t *testing.T, description string, actual string, expected string) { - if actual != expected { - t.Errorf(`%s "%s" does not match expected "%s."`, description, actual, expected) - } + return diffJson(description, actualJson, expected) } // diffJson compares two JSON byte arrays for structural equality. It will produce an error if either // byte array is not actually JSON. -func diffJson(t *testing.T, description string, actual []byte, expected []byte) { +func diffJson(description string, actual []byte, expected []byte) error { if len(actual) == 0 && len(expected) == 0 { - return + return nil } if len(actual) == 0 || len(expected) == 0 { - t.Fatalf("%s json diff failed. Expected %d bytes in body, but got %d.", description, len(expected), len(actual)) + return fmt.Errorf("%s json diff failed. Expected %d bytes in body, but got %d.", description, len(expected), len(actual)) } diff, err := gojsondiff.New().Compare(actual, expected) if err != nil { - t.Fatalf("%s json diff failed. %v", description, err) + return fmt.Errorf("%s json diff failed. %v", description, err) } if diff.Modified() { var left interface{} if err := json.Unmarshal(actual, &left); err != nil { - t.Fatalf("%s json did not match, but unmarshalling failed. %v", description, err) + return fmt.Errorf("%s json did not match, but unmarshalling failed. %v", description, err) } printer := formatter.NewAsciiFormatter(left, formatter.AsciiFormatterConfig{ ShowArrayIndex: true, }) output, err := printer.Format(diff) if err != nil { - t.Errorf("%s did not match, but diff formatting failed. %v", description, err) + return fmt.Errorf("%s did not match, but diff formatting failed. %v", description, err) } else { - t.Errorf("%s json did not match expected.\n\n%s", description, output) + return fmt.Errorf("%s json did not match expected.\n\n%s", description, output) } } + return nil } // testMakeRequestsImpl asserts the resulting values of the bidder's `MakeRequests()` implementation diff --git a/adapters/adf/adf.go b/adapters/adf/adf.go index 8014bd5bb56..bc54f78f187 100644 --- a/adapters/adf/adf.go +++ b/adapters/adf/adf.go @@ -16,6 +16,11 @@ type adapter struct { endpoint string } +type adfRequestExt struct { + openrtb_ext.ExtRequest + PriceType string `json:"pt"` +} + // Builder builds a new instance of the Adf adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { bidder := &adapter{ @@ -27,6 +32,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errors []error var validImps = make([]openrtb2.Imp, 0, len(request.Imp)) + priceType := "" for _, imp := range request.Imp { var bidderExt adapters.ExtImpBidder @@ -47,6 +53,30 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte imp.TagID = adfImpExt.MasterTagID.String() validImps = append(validImps, imp) + + // If imps specify priceType they should all be the same. If they differ, only the first one will be used + if adfImpExt.PriceType != "" && priceType == "" { + priceType = adfImpExt.PriceType + } + } + + if priceType != "" { + requestExt := adfRequestExt{} + var err error + + if len(request.Ext) > 0 { + if err = json.Unmarshal(request.Ext, &requestExt); err != nil { + errors = append(errors, err) + } + } + + if err == nil { + requestExt.PriceType = priceType + + if request.Ext, err = json.Marshal(&requestExt); err != nil { + errors = append(errors, err) + } + } } request.Imp = validImps diff --git a/adapters/adf/adftest/exemplary/single-banner-pricetype-gross-extend-ext.json b/adapters/adf/adftest/exemplary/single-banner-pricetype-gross-extend-ext.json new file mode 100644 index 00000000000..0cd03c75bb5 --- /dev/null +++ b/adapters/adf/adftest/exemplary/single-banner-pricetype-gross-extend-ext.json @@ -0,0 +1,122 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "ext":{ + "prebid":{ + "aliases":{ + "adfalias": "adf" + } + } + }, + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "mid": "828782", + "priceType": "gross" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + } + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://adx.adform.net/adx/openrtb", + "body": { + "id": "test-request-id", + "ext": { + "prebid":{ + "aliases":{ + "adfalias": "adf" + } + }, + "pt": "gross" + }, + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "mid": "828782", + "priceType": "gross" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "tagid": "828782" + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "bid": [{ + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{banner html}", + "adomain": [ "test.com" ], + "crid": "test-creative-id", + "ext": { + "prebid": { + "type": "banner" + } + } + }] + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{banner html}", + "crid": "test-creative-id", + "adomain": [ "test.com" ], + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + }] +} diff --git a/adapters/adf/adftest/exemplary/single-banner-pricetype-gross.json b/adapters/adf/adftest/exemplary/single-banner-pricetype-gross.json new file mode 100644 index 00000000000..2fb7cf24c5a --- /dev/null +++ b/adapters/adf/adftest/exemplary/single-banner-pricetype-gross.json @@ -0,0 +1,112 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "mid": "828782", + "priceType": "gross" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + } + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://adx.adform.net/adx/openrtb", + "body": { + "id": "test-request-id", + "ext": { + "prebid": { + }, + "pt": "gross" + }, + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "mid": "828782", + "priceType": "gross" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "tagid": "828782" + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "bid": [{ + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{banner html}", + "adomain": [ "test.com" ], + "crid": "test-creative-id", + "ext": { + "prebid": { + "type": "banner" + } + } + }] + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{banner html}", + "crid": "test-creative-id", + "adomain": [ "test.com" ], + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + }] +} diff --git a/adapters/adf/adftest/exemplary/single-banner-pricetype-net.json b/adapters/adf/adftest/exemplary/single-banner-pricetype-net.json new file mode 100644 index 00000000000..cb4b5f8a1f4 --- /dev/null +++ b/adapters/adf/adftest/exemplary/single-banner-pricetype-net.json @@ -0,0 +1,112 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "mid": "828782", + "priceType": "net" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + } + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://adx.adform.net/adx/openrtb", + "body": { + "id": "test-request-id", + "ext": { + "prebid": { + }, + "pt": "net" + }, + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "mid": "828782", + "priceType": "net" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "tagid": "828782" + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "bid": [{ + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{banner html}", + "adomain": [ "test.com" ], + "crid": "test-creative-id", + "ext": { + "prebid": { + "type": "banner" + } + } + }] + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{banner html}", + "crid": "test-creative-id", + "adomain": [ "test.com" ], + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + }] +} diff --git a/adapters/adf/adftest/exemplary/two-banners-different-pricetypes-extend-ext.json b/adapters/adf/adftest/exemplary/two-banners-different-pricetypes-extend-ext.json new file mode 100644 index 00000000000..59e380d5f2e --- /dev/null +++ b/adapters/adf/adftest/exemplary/two-banners-different-pricetypes-extend-ext.json @@ -0,0 +1,151 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "ext":{ + "prebid":{ + "aliases":{ + "adfalias": "adf" + } + } + }, + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "mid": "828782", + "priceType": "gross" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + } + },{ + "id": "test-imp-id2", + "ext": { + "bidder": { + "mid": "828783", + "priceType": "net" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + } + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://adx.adform.net/adx/openrtb", + "body": { + "id": "test-request-id", + "ext": { + "prebid":{ + "aliases":{ + "adfalias": "adf" + } + }, + "pt": "gross" + }, + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "mid": "828782", + "priceType": "gross" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "tagid": "828782" + },{ + "id": "test-imp-id2", + "ext": { + "bidder": { + "mid": "828783", + "priceType": "net" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "tagid": "828783" + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "bid": [{ + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{banner html}", + "adomain": [ "test.com" ], + "crid": "test-creative-id", + "ext": { + "prebid": { + "type": "banner" + } + } + }] + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{banner html}", + "crid": "test-creative-id", + "adomain": [ "test.com" ], + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + }] +} diff --git a/adapters/adf/adftest/exemplary/two-banners-different-pricetypes.json b/adapters/adf/adftest/exemplary/two-banners-different-pricetypes.json new file mode 100644 index 00000000000..918ef3ca7f3 --- /dev/null +++ b/adapters/adf/adftest/exemplary/two-banners-different-pricetypes.json @@ -0,0 +1,141 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "mid": "828782", + "priceType": "gross" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + } + },{ + "id": "test-imp-id2", + "ext": { + "bidder": { + "mid": "828783", + "priceType": "net" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + } + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://adx.adform.net/adx/openrtb", + "body": { + "id": "test-request-id", + "ext": { + "prebid": { + }, + "pt": "gross" + }, + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "mid": "828782", + "priceType": "gross" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "tagid": "828782" + },{ + "id": "test-imp-id2", + "ext": { + "bidder": { + "mid": "828783", + "priceType": "net" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "tagid": "828783" + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "bid": [{ + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{banner html}", + "adomain": [ "test.com" ], + "crid": "test-creative-id", + "ext": { + "prebid": { + "type": "banner" + } + } + }] + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{banner html}", + "crid": "test-creative-id", + "adomain": [ "test.com" ], + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + }] +} diff --git a/adapters/adf/params_test.go b/adapters/adf/params_test.go index d075f914082..d46c6528da0 100644 --- a/adapters/adf/params_test.go +++ b/adapters/adf/params_test.go @@ -46,6 +46,8 @@ var validParams = []string{ `{"inv":321,"mname":"12345"}`, `{"mid":123,"inv":321,"mname":"pcl1"}`, `{"mid":"123","inv":321,"mname":"pcl1"}`, + `{"mid":"123","priceType":"gross"}`, + `{"mid":"123","priceType":"net"}`, } var invalidParams = []string{ @@ -62,4 +64,5 @@ var invalidParams = []string{ `{"inv":321}`, `{"inv":"321"}`, `{"mname":"12345"}`, + `{"mid":"123","priceType":"GROSS"}`, } diff --git a/adapters/adform/README.md b/adapters/adform/README.md deleted file mode 100644 index b4c335fcdf6..00000000000 --- a/adapters/adform/README.md +++ /dev/null @@ -1 +0,0 @@ -Please contact if you would like to build and deploy Prebid server and use it with Adform. diff --git a/adapters/adform/adform.go b/adapters/adform/adform.go deleted file mode 100644 index 225c7af35d4..00000000000 --- a/adapters/adform/adform.go +++ /dev/null @@ -1,627 +0,0 @@ -package adform - -import ( - "bytes" - "context" - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "net/http" - "net/url" - "strconv" - "strings" - - "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/pbs" - "golang.org/x/net/context/ctxhttp" -) - -const version = "0.1.3" - -type AdformAdapter struct { - http *adapters.HTTPAdapter - URL *url.URL - version string -} - -type adformRequest struct { - tid string - userAgent string - ip string - advertisingId string - bidderCode string - isSecure bool - referer string - userId string - adUnits []*adformAdUnit - gdprApplies string - consent string - currency string - eids string - url string -} - -type adformAdUnit struct { - MasterTagId json.Number `json:"mid"` - PriceType string `json:"priceType,omitempty"` - KeyValues string `json:"mkv,omitempty"` - KeyWords string `json:"mkw,omitempty"` - CDims string `json:"cdims,omitempty"` - MinPrice float64 `json:"minp,omitempty"` - Url string `json:"url,omitempty"` - - bidId string - adUnitCode string -} - -type adformBid struct { - ResponseType string `json:"response,omitempty"` - Banner string `json:"banner,omitempty"` - Price float64 `json:"win_bid,omitempty"` - Currency string `json:"win_cur,omitempty"` - Width uint64 `json:"width,omitempty"` - Height uint64 `json:"height,omitempty"` - DealId string `json:"deal_id,omitempty"` - CreativeId string `json:"win_crid,omitempty"` - VastContent string `json:"vast_content,omitempty"` -} - -const priceTypeGross = "gross" -const priceTypeNet = "net" -const defaultCurrency = "USD" - -func isPriceTypeValid(priceType string) (string, bool) { - pt := strings.ToLower(priceType) - valid := pt == priceTypeNet || pt == priceTypeGross - - return pt, valid -} - -// ADAPTER Interface - -// Builder builds a new instance of the Adform adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { - uri, err := url.Parse(config.Endpoint) - if err != nil { - return nil, errors.New("unable to parse endpoint") - } - - bidder := &AdformAdapter{ - URL: uri, - version: version, - } - return bidder, nil -} - -// used for cookies and such -func (a *AdformAdapter) Name() string { - return "adform" -} - -func (a *AdformAdapter) SkipNoCookies() bool { - return false -} - -func (a *AdformAdapter) Call(ctx context.Context, request *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { - adformRequest, err := pbsRequestToAdformRequest(a, request, bidder) - if err != nil { - return nil, err - } - - uri := adformRequest.buildAdformUrl(a) - - debug := &pbs.BidderDebug{RequestURI: uri} - if request.IsDebug { - bidder.Debug = append(bidder.Debug, debug) - } - - httpRequest, err := http.NewRequest("GET", uri, nil) - if err != nil { - return nil, err - } - - httpRequest.Header = adformRequest.buildAdformHeaders(a) - - response, err := ctxhttp.Do(ctx, a.http.Client, httpRequest) - if err != nil { - return nil, err - } - - debug.StatusCode = response.StatusCode - - if response.StatusCode == 204 { - return nil, nil - } - - defer response.Body.Close() - body, err := ioutil.ReadAll(response.Body) - if err != nil { - return nil, err - } - responseBody := string(body) - - if response.StatusCode == http.StatusBadRequest { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("HTTP status %d; body: %s", response.StatusCode, responseBody), - } - } - - if response.StatusCode != 200 { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("HTTP status %d; body: %s", response.StatusCode, responseBody), - } - } - - if request.IsDebug { - debug.ResponseBody = responseBody - } - - adformBids, err := parseAdformBids(body) - if err != nil { - return nil, err - } - - bids := toPBSBidSlice(adformBids, adformRequest) - - return bids, nil -} - -func pbsRequestToAdformRequest(a *AdformAdapter, request *pbs.PBSRequest, bidder *pbs.PBSBidder) (*adformRequest, error) { - adUnits := make([]*adformAdUnit, 0, len(bidder.AdUnits)) - for _, adUnit := range bidder.AdUnits { - var adformAdUnit adformAdUnit - if err := json.Unmarshal(adUnit.Params, &adformAdUnit); err != nil { - return nil, err - } - mid, err := adformAdUnit.MasterTagId.Int64() - if err != nil { - return nil, &errortypes.BadInput{ - Message: err.Error(), - } - } - if mid <= 0 { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("master tag(placement) id is invalid=%s", adformAdUnit.MasterTagId), - } - } - adformAdUnit.bidId = adUnit.BidID - adformAdUnit.adUnitCode = adUnit.Code - adUnits = append(adUnits, &adformAdUnit) - } - - userId, _, _ := request.Cookie.GetUID(a.Name()) - - gdprApplies := request.ParseGDPR() - if gdprApplies != "0" && gdprApplies != "1" { - gdprApplies = "" - } - consent := request.ParseConsent() - - return &adformRequest{ - adUnits: adUnits, - ip: request.Device.IP, - advertisingId: request.Device.IFA, - userAgent: request.Device.UA, - bidderCode: bidder.BidderCode, - isSecure: request.Secure == 1, - referer: request.Url, - userId: userId, - tid: request.Tid, - gdprApplies: gdprApplies, - consent: consent, - currency: defaultCurrency, - }, nil -} - -func toPBSBidSlice(adformBids []*adformBid, r *adformRequest) pbs.PBSBidSlice { - bids := make(pbs.PBSBidSlice, 0) - - for i, bid := range adformBids { - adm, bidType := getAdAndType(bid) - if adm == "" { - continue - } - pbsBid := pbs.PBSBid{ - BidID: r.adUnits[i].bidId, - AdUnitCode: r.adUnits[i].adUnitCode, - BidderCode: r.bidderCode, - Price: bid.Price, - Adm: adm, - Width: int64(bid.Width), - Height: int64(bid.Height), - DealId: bid.DealId, - Creative_id: bid.CreativeId, - CreativeMediaType: string(bidType), - } - - bids = append(bids, &pbsBid) - } - - return bids -} - -// COMMON - -func (r *adformRequest) buildAdformUrl(a *AdformAdapter) string { - parameters := url.Values{} - - if r.advertisingId != "" { - parameters.Add("adid", r.advertisingId) - } - parameters.Add("CC", "1") - parameters.Add("rp", "4") - parameters.Add("fd", "1") - parameters.Add("stid", r.tid) - parameters.Add("ip", r.ip) - - priceType := getValidPriceTypeParameter(r.adUnits) - if priceType != "" { - parameters.Add("pt", priceType) - } - - parameters.Add("gdpr", r.gdprApplies) - parameters.Add("gdpr_consent", r.consent) - if r.eids != "" { - parameters.Add("eids", r.eids) - } - - if r.url != "" { - parameters.Add("url", r.url) - } - - URL := *a.URL - URL.RawQuery = parameters.Encode() - - uri := URL.String() - if r.isSecure { - uri = strings.Replace(uri, "http://", "https://", 1) - } - - adUnitsParams := make([]string, 0, len(r.adUnits)) - for _, adUnit := range r.adUnits { - var buffer bytes.Buffer - buffer.WriteString(fmt.Sprintf("mid=%s&rcur=%s", adUnit.MasterTagId, r.currency)) - if adUnit.KeyValues != "" { - buffer.WriteString(fmt.Sprintf("&mkv=%s", adUnit.KeyValues)) - } - if adUnit.KeyWords != "" { - buffer.WriteString(fmt.Sprintf("&mkw=%s", adUnit.KeyWords)) - } - if adUnit.CDims != "" { - buffer.WriteString(fmt.Sprintf("&cdims=%s", adUnit.CDims)) - } - if adUnit.MinPrice > 0 { - buffer.WriteString(fmt.Sprintf("&minp=%.2f", adUnit.MinPrice)) - } - adUnitsParams = append(adUnitsParams, base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(buffer.Bytes())) - } - - return fmt.Sprintf("%s&%s", uri, strings.Join(adUnitsParams, "&")) -} - -func getValidPriceTypeParameter(adUnits []*adformAdUnit) string { - priceTypeParameter := "" - priceType := priceTypeNet - valid := false - for _, adUnit := range adUnits { - pt, v := isPriceTypeValid(adUnit.PriceType) - if v { - valid = v - if pt == priceTypeGross { - priceType = pt - break - } - } - } - - if valid { - priceTypeParameter = priceType - } - return priceTypeParameter -} - -func (r *adformRequest) buildAdformHeaders(a *AdformAdapter) http.Header { - header := http.Header{} - - header.Set("Content-Type", "application/json;charset=utf-8") - header.Set("Accept", "application/json") - header.Set("X-Request-Agent", fmt.Sprintf("PrebidAdapter %s", a.version)) - header.Set("User-Agent", r.userAgent) - header.Set("X-Forwarded-For", r.ip) - if r.referer != "" { - header.Set("Referer", r.referer) - } - - if r.userId != "" { - header.Set("Cookie", fmt.Sprintf("uid=%s;", r.userId)) - } - - return header -} - -func parseAdformBids(response []byte) ([]*adformBid, error) { - var bids []*adformBid - if err := json.Unmarshal(response, &bids); err != nil { - return nil, &errortypes.BadServerResponse{ - Message: err.Error(), - } - } - - return bids, nil -} - -// BIDDER Interface - -func NewAdformLegacyAdapter(httpConfig *adapters.HTTPAdapterConfig, endpointURL string) *AdformAdapter { - var uriObj *url.URL - uriObj, err := url.Parse(endpointURL) - if err != nil { - panic(fmt.Sprintf("Incorrect Adform request url %s, check the configuration, please.", endpointURL)) - } - - return &AdformAdapter{ - http: adapters.NewHTTPAdapter(httpConfig), - URL: uriObj, - version: version, - } -} - -func (a *AdformAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - adformRequest, errors := openRtbToAdformRequest(request) - if len(adformRequest.adUnits) == 0 { - return nil, errors - } - - requestData := adapters.RequestData{ - Method: "GET", - Uri: adformRequest.buildAdformUrl(a), - Body: nil, - Headers: adformRequest.buildAdformHeaders(a), - } - - requests := []*adapters.RequestData{&requestData} - - return requests, errors -} - -func openRtbToAdformRequest(request *openrtb2.BidRequest) (*adformRequest, []error) { - adUnits := make([]*adformAdUnit, 0, len(request.Imp)) - errors := make([]error, 0, len(request.Imp)) - secure := false - url := "" - - for _, imp := range request.Imp { - params, _, _, err := jsonparser.Get(imp.Ext, "bidder") - if err != nil { - errors = append(errors, &errortypes.BadInput{ - Message: err.Error(), - }) - continue - } - var adformAdUnit adformAdUnit - if err := json.Unmarshal(params, &adformAdUnit); err != nil { - errors = append(errors, &errortypes.BadInput{ - Message: err.Error(), - }) - continue - } - - mid, err := adformAdUnit.MasterTagId.Int64() - if err != nil { - errors = append(errors, &errortypes.BadInput{ - Message: err.Error(), - }) - continue - } - if mid <= 0 { - errors = append(errors, &errortypes.BadInput{ - Message: fmt.Sprintf("master tag(placement) id is invalid=%s", adformAdUnit.MasterTagId), - }) - continue - } - - if !secure && imp.Secure != nil && *imp.Secure == 1 { - secure = true - } - - if url == "" { - url = adformAdUnit.Url - } - - adformAdUnit.bidId = imp.ID - adformAdUnit.adUnitCode = imp.ID - adUnits = append(adUnits, &adformAdUnit) - } - - referer := "" - if request.Site != nil { - referer = request.Site.Page - } - - tid := "" - if request.Source != nil { - tid = request.Source.TID - } - - gdprApplies := "" - var extRegs openrtb_ext.ExtRegs - if request.Regs != nil && request.Regs.Ext != nil { - if err := json.Unmarshal(request.Regs.Ext, &extRegs); err != nil { - errors = append(errors, &errortypes.BadInput{ - Message: err.Error(), - }) - } - if extRegs.GDPR != nil && (*extRegs.GDPR == 0 || *extRegs.GDPR == 1) { - gdprApplies = strconv.Itoa(int(*extRegs.GDPR)) - } - } - - eids := "" - consent := "" - if request.User != nil && request.User.Ext != nil { - var extUser openrtb_ext.ExtUser - if err := json.Unmarshal(request.User.Ext, &extUser); err == nil { - consent = extUser.Consent - eids = encodeEids(extUser.Eids) - } - } - - requestCurrency := defaultCurrency - if len(request.Cur) != 0 { - hasDefaultCurrency := false - for _, c := range request.Cur { - if defaultCurrency == c { - hasDefaultCurrency = true - break - } - } - if !hasDefaultCurrency { - requestCurrency = request.Cur[0] - } - } - - return &adformRequest{ - adUnits: adUnits, - ip: getIPSafely(request.Device), - advertisingId: getIFASafely(request.Device), - userAgent: getUASafely(request.Device), - isSecure: secure, - referer: referer, - userId: getBuyerUIDSafely(request.User), - tid: tid, - gdprApplies: gdprApplies, - consent: consent, - currency: requestCurrency, - eids: eids, - url: url, - }, errors -} - -func encodeEids(eids []openrtb_ext.ExtUserEid) string { - if eids == nil { - return "" - } - - eidsMap := make(map[string]map[string][]int) - for _, eid := range eids { - _, ok := eidsMap[eid.Source] - if !ok { - eidsMap[eid.Source] = make(map[string][]int) - } - for _, uid := range eid.Uids { - eidsMap[eid.Source][uid.ID] = append(eidsMap[eid.Source][uid.ID], uid.Atype) - } - } - - encodedEids := "" - if eidsString, err := json.Marshal(eidsMap); err == nil { - encodedEids = base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(eidsString) - } - - return encodedEids -} - -func getIPSafely(device *openrtb2.Device) string { - if device == nil { - return "" - } - return device.IP -} - -func getIFASafely(device *openrtb2.Device) string { - if device == nil { - return "" - } - return device.IFA -} - -func getUASafely(device *openrtb2.Device) string { - if device == nil { - return "" - } - return device.UA -} - -func getBuyerUIDSafely(user *openrtb2.User) string { - if user == nil { - return "" - } - return user.BuyerUID -} - -func (a *AdformAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { - if response.StatusCode == http.StatusNoContent { - return nil, nil - } - - if response.StatusCode == http.StatusBadRequest { - return nil, []error{&errortypes.BadInput{ - Message: fmt.Sprintf("unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), - }} - } - - if response.StatusCode != http.StatusOK { - return nil, []error{&errortypes.BadServerResponse{ - Message: fmt.Sprintf("unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), - }} - } - - adformOutput, err := parseAdformBids(response.Body) - if err != nil { - return nil, []error{err} - } - - bidResponse := toOpenRtbBidResponse(adformOutput, internalRequest) - - return bidResponse, nil -} - -func toOpenRtbBidResponse(adformBids []*adformBid, r *openrtb2.BidRequest) *adapters.BidderResponse { - bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(adformBids)) - currency := bidResponse.Currency - - if len(adformBids) > 0 { - bidResponse.Currency = adformBids[0].Currency - } - - for i, bid := range adformBids { - adm, bidType := getAdAndType(bid) - if adm == "" { - continue - } - - openRtbBid := openrtb2.Bid{ - ID: r.Imp[i].ID, - ImpID: r.Imp[i].ID, - Price: bid.Price, - AdM: adm, - W: int64(bid.Width), - H: int64(bid.Height), - DealID: bid.DealId, - CrID: bid.CreativeId, - } - - bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{Bid: &openRtbBid, BidType: bidType}) - currency = bid.Currency - } - - bidResponse.Currency = currency - - return bidResponse -} - -func getAdAndType(bid *adformBid) (string, openrtb_ext.BidType) { - if bid.ResponseType == "banner" { - return bid.Banner, openrtb_ext.BidTypeBanner - } else if bid.ResponseType == "vast_content" { - return bid.VastContent, openrtb_ext.BidTypeVideo - } - return "", "" -} diff --git a/adapters/adform/adform_test.go b/adapters/adform/adform_test.go deleted file mode 100644 index 53219f4c4c0..00000000000 --- a/adapters/adform/adform_test.go +++ /dev/null @@ -1,773 +0,0 @@ -package adform - -import ( - "bytes" - "context" - "encoding/json" - "net/http" - "net/http/httptest" - "strconv" - "testing" - "time" - - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/cache/dummycache" - "github.com/prebid/prebid-server/pbs" - "github.com/prebid/prebid-server/usersync" - - "fmt" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" - - "github.com/stretchr/testify/assert" -) - -func TestJsonSamples(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderAdform, config.Adapter{ - Endpoint: "https://adx.adform.net/adx"}) - - if buildErr != nil { - t.Fatalf("Builder returned unexpected error %v", buildErr) - } - - adapterstest.RunJSONBidderTest(t, "adformtest", bidder) -} - -func TestEndpointMalformed(t *testing.T) { - _, buildErr := Builder(openrtb_ext.BidderAdform, config.Adapter{ - Endpoint: ` https://malformed`}) - - assert.Error(t, buildErr) -} - -type aTagInfo struct { - mid uint32 - priceType string - keyValues string - keyWords string - code string - cdims string - url string - minp float64 - - price float64 - content string - dealId string - creativeId string -} - -type aBidInfo struct { - deviceIP string - deviceUA string - deviceIFA string - tags []aTagInfo - referrer string - width uint64 - height uint64 - tid string - buyerUID string - secure bool - currency string - delay time.Duration -} - -var adformTestData aBidInfo - -// Legacy auction tests - -func DummyAdformServer(w http.ResponseWriter, r *http.Request) { - errorString := assertAdformServerRequest(adformTestData, r, false) - if errorString != nil { - http.Error(w, *errorString, http.StatusInternalServerError) - return - } - - if adformTestData.delay > 0 { - <-time.After(adformTestData.delay) - } - - adformServerResponse, err := createAdformServerResponse(adformTestData) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - w.Header().Set("Content-Type", "application/json") - w.Write(adformServerResponse) -} - -func createAdformServerResponse(testData aBidInfo) ([]byte, error) { - bids := []adformBid{ - { - ResponseType: "banner", - Banner: testData.tags[0].content, - Price: testData.tags[0].price, - Currency: "EUR", - Width: testData.width, - Height: testData.height, - DealId: testData.tags[0].dealId, - CreativeId: testData.tags[0].creativeId, - }, - {}, - { - ResponseType: "banner", - Banner: testData.tags[2].content, - Price: testData.tags[2].price, - Currency: "EUR", - Width: testData.width, - Height: testData.height, - DealId: testData.tags[2].dealId, - CreativeId: testData.tags[2].creativeId, - }, - { - ResponseType: "vast_content", - VastContent: testData.tags[3].content, - Price: testData.tags[3].price, - Currency: "EUR", - Width: testData.width, - Height: testData.height, - DealId: testData.tags[3].dealId, - CreativeId: testData.tags[3].creativeId, - }, - } - adformServerResponse, err := json.Marshal(bids) - return adformServerResponse, err -} - -func TestAdformBasicResponse(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(DummyAdformServer)) - defer server.Close() - - adapter, ctx, prebidRequest := initTestData(server, t) - - bids, err := adapter.Call(ctx, prebidRequest, prebidRequest.Bidders[0]) - - if err != nil { - t.Fatalf("Should not have gotten adapter error: %v", err) - } - if len(bids) != 3 { - t.Fatalf("Received %d bids instead of 3", len(bids)) - } - expectedTypes := []openrtb_ext.BidType{ - openrtb_ext.BidTypeBanner, - openrtb_ext.BidTypeBanner, - openrtb_ext.BidTypeVideo, - } - - for i, bid := range bids { - - if bid.CreativeMediaType != string(expectedTypes[i]) { - t.Errorf("Expected a %s bid. Got: %s", expectedTypes[i], bid.CreativeMediaType) - } - - matched := false - for _, tag := range adformTestData.tags { - if bid.AdUnitCode == tag.code { - matched = true - if bid.BidderCode != "adform" { - t.Errorf("Incorrect BidderCode '%s'", bid.BidderCode) - } - if bid.Price != tag.price { - t.Errorf("Incorrect bid price '%.2f' expected '%.2f'", bid.Price, tag.price) - } - if bid.Width != int64(adformTestData.width) || bid.Height != int64(adformTestData.height) { - t.Errorf("Incorrect bid size %dx%d, expected %dx%d", bid.Width, bid.Height, adformTestData.width, adformTestData.height) - } - if bid.Adm != tag.content { - t.Errorf("Incorrect bid markup '%s' expected '%s'", bid.Adm, tag.content) - } - if bid.DealId != tag.dealId { - t.Errorf("Incorrect deal id '%s' expected '%s'", bid.DealId, tag.dealId) - } - if bid.Creative_id != tag.creativeId { - t.Errorf("Incorrect creative id '%s' expected '%s'", bid.Creative_id, tag.creativeId) - } - } - } - if !matched { - t.Errorf("Received bid for unknown ad unit '%s'", bid.AdUnitCode) - } - } - - // same test but with request timing out - adformTestData.delay = 5 * time.Millisecond - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) - defer cancel() - - bids, err = adapter.Call(ctx, prebidRequest, prebidRequest.Bidders[0]) - if err == nil { - t.Fatalf("Should have gotten a timeout error: %v", err) - } -} - -func initTestData(server *httptest.Server, t *testing.T) (*AdformAdapter, context.Context, *pbs.PBSRequest) { - adformTestData = createTestData(false) - - // prepare adapter - conf := *adapters.DefaultHTTPAdapterConfig - adapter := NewAdformLegacyAdapter(&conf, server.URL) - - prebidRequest := preparePrebidRequest(server.URL, t) - ctx := context.TODO() - - return adapter, ctx, prebidRequest -} - -func preparePrebidRequest(serverUrl string, t *testing.T) *pbs.PBSRequest { - body := preparePrebidRequestBody(adformTestData, t) - prebidHttpRequest := httptest.NewRequest("POST", serverUrl, body) - prebidHttpRequest.Header.Add("User-Agent", adformTestData.deviceUA) - prebidHttpRequest.Header.Add("Referer", adformTestData.referrer) - prebidHttpRequest.Header.Add("X-Real-IP", adformTestData.deviceIP) - - pbsCookie := usersync.ParseCookieFromRequest(prebidHttpRequest, &config.HostCookie{}) - pbsCookie.TrySync("adform", adformTestData.buyerUID) - fakeWriter := httptest.NewRecorder() - - pbsCookie.SetCookieOnResponse(fakeWriter, false, &config.HostCookie{Domain: ""}, time.Minute) - prebidHttpRequest.Header.Add("Cookie", fakeWriter.Header().Get("Set-Cookie")) - - cacheClient, _ := dummycache.New() - r, err := pbs.ParsePBSRequest(prebidHttpRequest, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, cacheClient, &config.HostCookie{}) - if err != nil { - t.Fatalf("ParsePBSRequest failed: %v", err) - } - if len(r.Bidders) != 1 { - t.Fatalf("ParsePBSRequest returned %d bidders instead of 1", len(r.Bidders)) - } - if r.Bidders[0].BidderCode != "adform" { - t.Fatalf("ParsePBSRequest returned invalid bidder") - } - - // can't be set in preparePrebidRequestBody as will be lost during json serialization and deserialization - // for the adapters which don't support OpenRTB requests the old PBSRequest is created from OpenRTB request - // so User and Regs are copied from OpenRTB request, see legacy.go -> toLegacyRequest - regs := getRegs() - r.Regs = ®s - user := openrtb2.User{ - Ext: getUserExt(), - } - r.User = &user - - return r -} - -func preparePrebidRequestBody(requestData aBidInfo, t *testing.T) *bytes.Buffer { - prebidRequest := pbs.PBSRequest{ - AdUnits: make([]pbs.AdUnit, 4), - Device: &openrtb2.Device{ - UA: requestData.deviceUA, - IP: requestData.deviceIP, - IFA: requestData.deviceIFA, - }, - Tid: requestData.tid, - Secure: 0, - } - for i, tag := range requestData.tags { - prebidRequest.AdUnits[i] = pbs.AdUnit{ - Code: tag.code, - Sizes: []openrtb2.Format{ - { - W: int64(requestData.width), - H: int64(requestData.height), - }, - }, - Bids: []pbs.Bids{ - { - BidderCode: "adform", - BidID: fmt.Sprintf("random-id-from-pbjs-%d", i), - Params: json.RawMessage(formatAdUnitJson(tag)), - }, - }, - } - } - - body := new(bytes.Buffer) - err := json.NewEncoder(body).Encode(prebidRequest) - if err != nil { - t.Fatalf("Json encoding failed: %v", err) - } - fmt.Println("body", body) - return body -} - -// OpenRTB auction tests - -func TestOpenRTBRequest(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderAdform, config.Adapter{ - Endpoint: "https://adx.adform.net"}) - - if buildErr != nil { - t.Fatalf("Builder returned unexpected error %v", buildErr) - } - - testData := createTestData(true) - request := createOpenRtbRequest(&testData) - - httpRequests, errs := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) - - if len(errs) > 0 { - t.Errorf("Got unexpected errors while building HTTP requests: %v", errs) - } - if len(httpRequests) != 1 { - t.Fatalf("Unexpected number of HTTP requests. Got %d. Expected %d", len(httpRequests), 1) - } - - r, err := http.NewRequest(httpRequests[0].Method, httpRequests[0].Uri, bytes.NewReader(httpRequests[0].Body)) - if err != nil { - t.Fatalf("Unexpected request. Got %v", err) - } - r.Header = httpRequests[0].Headers - - errorString := assertAdformServerRequest(testData, r, true) - if errorString != nil { - t.Errorf("Request error: %s", *errorString) - } -} - -func TestOpenRTBIncorrectRequest(t *testing.T) { - bidder := new(AdformAdapter) - request := &openrtb2.BidRequest{ - ID: "test-request-id", - Imp: []openrtb2.Imp{ - {ID: "incorrect-bidder-field", Ext: json.RawMessage(`{"bidder1": { "mid": "32344" }}`)}, - {ID: "incorrect-adform-params", Ext: json.RawMessage(`{"bidder": { : "33" }}`)}, - {ID: "mid-integer", Ext: json.RawMessage(`{"bidder": { "mid": 1.234 }}`)}, - {ID: "mid-greater-then-zero", Ext: json.RawMessage(`{"bidder": { "mid": -1 }}`)}, - }, - Device: &openrtb2.Device{UA: "ua", IP: "ip"}, - User: &openrtb2.User{BuyerUID: "buyerUID"}, - } - - httpRequests, errs := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) - - if len(errs) != len(request.Imp) { - t.Errorf("%d Imp objects should have errors. but was %d errors", len(request.Imp), len(errs)) - } - if len(httpRequests) != 0 { - t.Fatalf("All Imp objects have errors, but requests count: %d. Expected %d", len(httpRequests), 0) - } -} - -func createTestData(secure bool) aBidInfo { - testData := aBidInfo{ - deviceIP: "111.111.111.111", - deviceUA: "Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Mobile/14E8301", - deviceIFA: "6D92078A-8246-4BA4-AE5B-76104861E7DC", - referrer: "http://test.com", - tid: "transaction-id", - buyerUID: "user-id", - tags: []aTagInfo{ - {mid: 32344, keyValues: "color:red,age:30-40", keyWords: "red,blue", cdims: "300x300,400x200", priceType: "gross", code: "code1", price: 1.23, content: "banner-content1", dealId: "dealId1", creativeId: "creativeId1"}, - {mid: 32345, priceType: "net", code: "code2", minp: 23.1, cdims: "300x200"}, // no bid for ad unit - {mid: 32346, code: "code3", price: 1.24, content: "banner-content2", dealId: "dealId2", url: "https://adform.com?a=b"}, - {mid: 32347, code: "code4", content: "vast-xml"}, - }, - secure: secure, - currency: "EUR", - } - return testData -} - -func createOpenRtbRequest(testData *aBidInfo) *openrtb2.BidRequest { - secure := int8(0) - if testData.secure { - secure = int8(1) - } - - bidRequest := &openrtb2.BidRequest{ - ID: "test-request-id", - Imp: make([]openrtb2.Imp, len(testData.tags)), - Site: &openrtb2.Site{ - Page: testData.referrer, - }, - Device: &openrtb2.Device{ - UA: testData.deviceUA, - IP: testData.deviceIP, - IFA: testData.deviceIFA, - }, - Source: &openrtb2.Source{ - TID: testData.tid, - }, - User: &openrtb2.User{ - BuyerUID: testData.buyerUID, - }, - } - for i, tag := range testData.tags { - bidRequest.Imp[i] = openrtb2.Imp{ - ID: tag.code, - Secure: &secure, - Ext: json.RawMessage(fmt.Sprintf("{\"bidder\": %s}", formatAdUnitJson(tag))), - Banner: &openrtb2.Banner{}, - } - } - - regs := getRegs() - bidRequest.Regs = ®s - bidRequest.User.Ext = getUserExt() - - bidRequest.Cur = make([]string, 1) - bidRequest.Cur[0] = testData.currency - - return bidRequest -} - -func TestOpenRTBStandardResponse(t *testing.T) { - testData := createTestData(true) - request := createOpenRtbRequest(&testData) - expectedTypes := []openrtb_ext.BidType{ - openrtb_ext.BidTypeBanner, - openrtb_ext.BidTypeBanner, - openrtb_ext.BidTypeVideo, - } - - responseBody, err := createAdformServerResponse(testData) - if err != nil { - t.Fatalf("Unable to create server response: %v", err) - return - } - httpResponse := &adapters.ResponseData{StatusCode: http.StatusOK, Body: responseBody} - - bidder := new(AdformAdapter) - bidResponse, errs := bidder.MakeBids(request, nil, httpResponse) - - if len(bidResponse.Bids) != 3 { - t.Fatalf("Expected 3 bids. Got %d", len(bidResponse.Bids)) - } - if len(errs) != 0 { - t.Errorf("Expected 0 errors. Got %d", len(errs)) - } - - for i, typeBid := range bidResponse.Bids { - - if typeBid.BidType != expectedTypes[i] { - t.Errorf("Expected a %s bid. Got: %s", expectedTypes[i], typeBid.BidType) - } - bid := typeBid.Bid - matched := false - - for _, tag := range testData.tags { - if bid.ID == tag.code { - matched = true - if bid.Price != tag.price { - t.Errorf("Incorrect bid price '%.2f' expected '%.2f'", bid.Price, tag.price) - } - if bid.W != int64(testData.width) || bid.H != int64(testData.height) { - t.Errorf("Incorrect bid size %dx%d, expected %dx%d", bid.W, bid.H, testData.width, testData.height) - } - if bid.AdM != tag.content { - t.Errorf("Incorrect bid markup '%s' expected '%s'", bid.AdM, tag.content) - } - if bid.DealID != tag.dealId { - t.Errorf("Incorrect deal id '%s' expected '%s'", bid.DealID, tag.dealId) - } - if bid.CrID != tag.creativeId { - t.Errorf("Incorrect creative id '%s' expected '%s'", bid.CrID, tag.creativeId) - } - } - } - if !matched { - t.Errorf("Received bid with unknown id '%s'", bid.ID) - } - } -} - -func TestOpenRTBSurpriseResponse(t *testing.T) { - bidder := new(AdformAdapter) - - bidResponse, errs := bidder.MakeBids(nil, nil, - &adapters.ResponseData{StatusCode: http.StatusNoContent, Body: []byte("")}) - if bidResponse != nil && errs != nil { - t.Fatalf("Expected no bids and no errors. Got %d bids and %d", len(bidResponse.Bids), len(errs)) - } - - bidResponse, errs = bidder.MakeBids(nil, nil, - &adapters.ResponseData{StatusCode: http.StatusServiceUnavailable, Body: []byte("")}) - if bidResponse != nil || len(errs) != 1 { - t.Fatalf("Expected one error and no bids. Got %d bids and %d", len(bidResponse.Bids), len(errs)) - } - - bidResponse, errs = bidder.MakeBids(nil, nil, - &adapters.ResponseData{StatusCode: http.StatusOK, Body: []byte("{:'not-valid-json'}")}) - if bidResponse != nil || len(errs) != 1 { - t.Fatalf("Expected one error and no bids. Got %d bids and %d", len(bidResponse.Bids), len(errs)) - } -} - -// Properties tests - -func TestAdformProperties(t *testing.T) { - adapter := NewAdformLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "adx.adform.net/adx") - - if adapter.SkipNoCookies() != false { - t.Fatalf("should have been false") - } -} - -// helpers - -func getRegs() openrtb2.Regs { - var gdpr int8 = 1 - regsExt := openrtb_ext.ExtRegs{ - GDPR: &gdpr, - } - regs := openrtb2.Regs{} - regsExtData, err := json.Marshal(regsExt) - if err == nil { - regs.Ext = regsExtData - } - return regs -} - -func getUserExt() []byte { - eids := []openrtb_ext.ExtUserEid{ - { - Source: "test.com", - Uids: []openrtb_ext.ExtUserEidUid{ - { - ID: "some_user_id", - Atype: 1, - }, - { - ID: "other_user_id", - }, - }, - }, - { - Source: "test2.org", - Uids: []openrtb_ext.ExtUserEidUid{ - { - ID: "other_user_id", - Atype: 2, - }, - }, - }, - } - - userExt := openrtb_ext.ExtUser{ - Eids: eids, - Consent: "abc", - } - userExtData, err := json.Marshal(userExt) - if err == nil { - return userExtData - } - - return nil -} - -func formatAdUnitJson(tag aTagInfo) string { - return fmt.Sprintf("{ \"mid\": %d%s%s%s%s%s%s}", - tag.mid, - formatAdUnitParam("priceType", tag.priceType), - formatAdUnitParam("mkv", tag.keyValues), - formatAdUnitParam("mkw", tag.keyWords), - formatAdUnitParam("cdims", tag.cdims), - formatAdUnitParam("url", tag.url), - formatDemicalAdUnitParam("minp", tag.minp)) -} - -func formatDemicalAdUnitParam(fieldName string, fieldValue float64) string { - if fieldValue > 0 { - return fmt.Sprintf(", \"%s\": %.2f", fieldName, fieldValue) - } - - return "" -} - -func formatAdUnitParam(fieldName string, fieldValue string) string { - if fieldValue != "" { - return fmt.Sprintf(", \"%s\": \"%s\"", fieldName, fieldValue) - } - - return "" -} - -func assertAdformServerRequest(testData aBidInfo, r *http.Request, isOpenRtb bool) *string { - if ok, err := equal("GET", r.Method, "HTTP method"); !ok { - return err - } - if testData.secure { - if ok, err := equal("https", r.URL.Scheme, "Scheme"); !ok { - return err - } - } - - var midsWithCurrency = "" - var queryString = "" - if isOpenRtb { - midsWithCurrency = "bWlkPTMyMzQ0JnJjdXI9RVVSJm1rdj1jb2xvcjpyZWQsYWdlOjMwLTQwJm1rdz1yZWQsYmx1ZSZjZGltcz0zMDB4MzAwLDQwMHgyMDA&bWlkPTMyMzQ1JnJjdXI9RVVSJmNkaW1zPTMwMHgyMDAmbWlucD0yMy4xMA&bWlkPTMyMzQ2JnJjdXI9RVVS&bWlkPTMyMzQ3JnJjdXI9RVVS" - queryString = "CC=1&adid=6D92078A-8246-4BA4-AE5B-76104861E7DC&eids=eyJ0ZXN0LmNvbSI6eyJvdGhlcl91c2VyX2lkIjpbMF0sInNvbWVfdXNlcl9pZCI6WzFdfSwidGVzdDIub3JnIjp7Im90aGVyX3VzZXJfaWQiOlsyXX19&fd=1&gdpr=1&gdpr_consent=abc&ip=111.111.111.111&pt=gross&rp=4&stid=transaction-id&url=https%3A%2F%2Fadform.com%3Fa%3Db&" + midsWithCurrency - } else { - midsWithCurrency = "bWlkPTMyMzQ0JnJjdXI9VVNEJm1rdj1jb2xvcjpyZWQsYWdlOjMwLTQwJm1rdz1yZWQsYmx1ZSZjZGltcz0zMDB4MzAwLDQwMHgyMDA&bWlkPTMyMzQ1JnJjdXI9VVNEJmNkaW1zPTMwMHgyMDAmbWlucD0yMy4xMA&bWlkPTMyMzQ2JnJjdXI9VVNE&bWlkPTMyMzQ3JnJjdXI9VVNE" // no way to pass currency in legacy adapter - queryString = "CC=1&adid=6D92078A-8246-4BA4-AE5B-76104861E7DC&fd=1&gdpr=1&gdpr_consent=abc&ip=111.111.111.111&pt=gross&rp=4&stid=transaction-id&" + midsWithCurrency - } - - if ok, err := equal(queryString, r.URL.RawQuery, "Query string"); !ok { - return err - } - if ok, err := equal("application/json;charset=utf-8", r.Header.Get("Content-Type"), "Content type"); !ok { - return err - } - if ok, err := equal(testData.deviceUA, r.Header.Get("User-Agent"), "User agent"); !ok { - return err - } - if ok, err := equal(testData.deviceIP, r.Header.Get("X-Forwarded-For"), "Device IP"); !ok { - return err - } - if ok, err := equal(testData.referrer, r.Header.Get("Referer"), "Referer"); !ok { - return err - } - if ok, err := equal(fmt.Sprintf("uid=%s;", testData.buyerUID), r.Header.Get("Cookie"), "Buyer ID"); !ok { - return err - } - return nil -} - -func equal(expected string, actual string, message string) (bool, *string) { - if expected != actual { - message := fmt.Sprintf("%s '%s' doesn't match '%s'", message, actual, expected) - return false, &message - } - return true, nil -} - -// Price type parameter tests - -func TestPriceTypeValidation(t *testing.T) { - // Arrange - priceTypeTestCases := map[string]bool{ - "net": true, - "NET": true, - "nEt": true, - "nt": false, - "gross": true, - "GROSS": true, - "groSS": true, - "gorss": false, - "": false, - } - - // Act - for priceType, expected := range priceTypeTestCases { - _, valid := isPriceTypeValid(priceType) - - // Assert - if expected != valid { - t.Fatalf("Unexpected result for '%s' price type. Got valid = %s. Expected valid = %s", priceType, strconv.FormatBool(valid), strconv.FormatBool(expected)) - } - } -} - -func TestPriceTypeUrlParameterCreation(t *testing.T) { - // Arrange - priceTypeParameterTestCases := map[string][]*adformAdUnit{ - "": {{MasterTagId: "123"}, {MasterTagId: "456"}}, - "net": {{MasterTagId: "123", PriceType: priceTypeNet}, {MasterTagId: "456"}, {MasterTagId: "789", PriceType: priceTypeNet}}, - "gross": {{MasterTagId: "123", PriceType: priceTypeNet}, {MasterTagId: "456", PriceType: priceTypeGross}, {MasterTagId: "789", PriceType: priceTypeNet}}, - } - - // Act - for expected, adUnits := range priceTypeParameterTestCases { - parameter := getValidPriceTypeParameter(adUnits) - - // Assert - if expected != parameter { - t.Fatalf("Unexpected result for price type parameter. Got '%s'. Expected '%s'", parameter, expected) - } - } -} - -// Asserts that toOpenRtbBidResponse() creates a *adapters.BidderResponse with -// the currency of the last valid []*adformBid element and the expected number of bids -func TestToOpenRtbBidResponse(t *testing.T) { - expectedBids := 4 - lastCurrency, anotherCurrency, emptyCurrency := "EUR", "USD", "" - - request := &openrtb2.BidRequest{ - ID: "test-request-id", - Imp: []openrtb2.Imp{ - { - ID: "banner-imp-no1", - Ext: json.RawMessage(`{"bidder1": { "mid": "32341" }}`), - Banner: &openrtb2.Banner{}, - }, - { - ID: "banner-imp-no2", - Ext: json.RawMessage(`{"bidder1": { "mid": "32342" }}`), - Banner: &openrtb2.Banner{}, - }, - { - ID: "banner-imp-no3", - Ext: json.RawMessage(`{"bidder1": { "mid": "32343" }}`), - Banner: &openrtb2.Banner{}, - }, - { - ID: "banner-imp-no4", - Ext: json.RawMessage(`{"bidder1": { "mid": "32344" }}`), - Banner: &openrtb2.Banner{}, - }, - { - ID: "video-imp-no4", - Ext: json.RawMessage(`{"bidder1": { "mid": "32345" }}`), - Banner: &openrtb2.Banner{}, - }, - }, - Device: &openrtb2.Device{UA: "ua", IP: "ip"}, - User: &openrtb2.User{BuyerUID: "buyerUID"}, - } - - testAdformBids := []*adformBid{ - { - ResponseType: "banner", - Banner: "banner-content1", - Price: 1.23, - Currency: anotherCurrency, - Width: 300, - Height: 200, - DealId: "dealId1", - CreativeId: "creativeId1", - }, - {}, - { - ResponseType: "banner", - Banner: "banner-content3", - Price: 1.24, - Currency: emptyCurrency, - Width: 300, - Height: 200, - DealId: "dealId3", - CreativeId: "creativeId3", - }, - { - ResponseType: "banner", - Banner: "banner-content4", - Price: 1.25, - Currency: emptyCurrency, - Width: 300, - Height: 200, - DealId: "dealId4", - CreativeId: "creativeId4", - }, - { - ResponseType: "vast_content", - VastContent: "vast-content", - Price: 1.25, - Currency: lastCurrency, - Width: 300, - Height: 200, - DealId: "dealId4", - CreativeId: "creativeId4", - }, - } - - actualBidResponse := toOpenRtbBidResponse(testAdformBids, request) - - assert.Equalf(t, expectedBids, len(actualBidResponse.Bids), "bid count") - assert.Equalf(t, lastCurrency, actualBidResponse.Currency, "currency") -} diff --git a/adapters/adform/adformtest/exemplary/multiformat-impression.json b/adapters/adform/adformtest/exemplary/multiformat-impression.json deleted file mode 100644 index 5b3067ab927..00000000000 --- a/adapters/adform/adformtest/exemplary/multiformat-impression.json +++ /dev/null @@ -1,99 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "banner-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "mid": 12345 - } - } - }, - { - "id": "video-imp-id", - "video": { - "w": 640, - "h": 480 - }, - "ext": { - "bidder": { - "mid": 54321 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://adx.adform.net/adx?CC=1&fd=1&gdpr=&gdpr_consent=&ip=&rp=4&stid=&bWlkPTEyMzQ1JnJjdXI9VVNE&bWlkPTU0MzIxJnJjdXI9VVNE" - }, - "mockResponse": { - "status": 200, - "body": [ - { - "response": "banner", - "banner": "", - "win_bid": 0.5, - "win_cur": "USD", - "width": 300, - "height": 250, - "deal_id": null, - "win_crid": "20078830" - }, - { - "response": "vast_content", - "vast_content": "", - "win_bid": 0.7, - "win_cur": "USD", - "width": 640, - "height": 480, - "deal_id": "DID-123-22", - "win_crid": "20078831" - } - ] - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "banner-imp-id", - "impid": "banner-imp-id", - "price": 0.5, - "adm": "", - "crid": "20078830", - "w": 300, - "h": 250 - }, - "type": "banner" - }, - { - "bid": { - "id": "video-imp-id", - "impid": "video-imp-id", - "price": 0.7, - "adm": "", - "crid": "20078831", - "dealid": "DID-123-22", - "w": 640, - "h": 480 - }, - "type": "video" - } - ] - } - ] - } diff --git a/adapters/adform/adformtest/exemplary/single-banner-impression.json b/adapters/adform/adformtest/exemplary/single-banner-impression.json deleted file mode 100644 index 8a5f81c8edb..00000000000 --- a/adapters/adform/adformtest/exemplary/single-banner-impression.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "mid": 12345 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://adx.adform.net/adx?CC=1&fd=1&gdpr=&gdpr_consent=&ip=&rp=4&stid=&bWlkPTEyMzQ1JnJjdXI9VVNE" - }, - "mockResponse": { - "status": 200, - "body": [ - { - "response": "banner", - "banner": "", - "win_bid": 0.5, - "win_cur": "USD", - "width": 300, - "height": 250, - "deal_id": null, - "win_crid": "20078830" - } - ] - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-imp-id", - "impid": "test-imp-id", - "price": 0.5, - "adm": "", - "crid": "20078830", - "w": 300, - "h": 250 - }, - "type": "banner" - } - ] - } - ] -} diff --git a/adapters/adform/adformtest/exemplary/single-video-impression.json b/adapters/adform/adformtest/exemplary/single-video-impression.json deleted file mode 100644 index 383e091b3f7..00000000000 --- a/adapters/adform/adformtest/exemplary/single-video-impression.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "video": { - "w": 640, - "h": 480 - }, - "ext": { - "bidder": { - "mid": 54321 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://adx.adform.net/adx?CC=1&fd=1&gdpr=&gdpr_consent=&ip=&rp=4&stid=&bWlkPTU0MzIxJnJjdXI9VVNE" - }, - "mockResponse": { - "status": 200, - "body": [ - { - "response": "vast_content", - "vast_content": "", - "win_bid": 0.5, - "win_cur": "USD", - "width": 640, - "height": 480, - "deal_id": null, - "win_crid": "20078830" - } - ] - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-imp-id", - "impid": "test-imp-id", - "price": 0.5, - "adm": "", - "crid": "20078830", - "w": 640, - "h": 480 - }, - "type": "video" - } - ] - } - ] -} diff --git a/adapters/adform/adformtest/supplemental/regs-ext-nil.json b/adapters/adform/adformtest/supplemental/regs-ext-nil.json deleted file mode 100644 index 377c48b0445..00000000000 --- a/adapters/adform/adformtest/supplemental/regs-ext-nil.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [{ - "id": "test-imp-id", - "banner": { - "format": [{ - "w": 300, - "h": 250 - }] - }, - "ext": { - "bidder": { - "mid": 12345 - } - } - }], - "regs": {} - }, - "httpCalls": [{ - "expectedRequest": { - "uri": "https://adx.adform.net/adx?CC=1&fd=1&gdpr=&gdpr_consent=&ip=&rp=4&stid=&bWlkPTEyMzQ1JnJjdXI9VVNE" - }, - "mockResponse": { - "status": 200, - "body": [{ - "response": "banner", - "banner": "", - "win_bid": 0.5, - "win_cur": "USD", - "width": 300, - "height": 250, - "deal_id": null, - "win_crid": "20078830" - }] - } - }], - "expectedBidResponses": [{ - "currency": "USD", - "bids": [{ - "bid": { - "id": "test-imp-id", - "impid": "test-imp-id", - "price": 0.5, - "adm": "", - "crid": "20078830", - "w": 300, - "h": 250 - }, - "type": "banner" - }] - }] -} \ No newline at end of file diff --git a/adapters/adform/adformtest/supplemental/user-nil.json b/adapters/adform/adformtest/supplemental/user-nil.json deleted file mode 100644 index 96ea1dbff71..00000000000 --- a/adapters/adform/adformtest/supplemental/user-nil.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "mockBidRequest": { - "id": "unsupported-audio-request", - "imp": [ - { - "id": "unsupported-audio-imp", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "ext": { - "bidder": { - "mid": 1, - "priceType": "gross" - } - } - } - ], - "regs": { - "ext": { - "gdpr": 1 - } - }, - "user": { - "ext": { - "consent": "abc2" - } - } - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://adx.adform.net/adx?CC=1&fd=1&gdpr=1&gdpr_consent=abc2&ip=&pt=gross&rp=4&stid=&bWlkPTEmcmN1cj1VU0Q" - }, - "mockResponse": { - "status": 204 - } - } - ], - "expectedBidResponses": [] -} diff --git a/adapters/adform/params_test.go b/adapters/adform/params_test.go deleted file mode 100644 index b392463f426..00000000000 --- a/adapters/adform/params_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package adform - -import ( - "encoding/json" - "testing" - - "github.com/prebid/prebid-server/openrtb_ext" -) - -// This file actually intends to test static/bidder-params/adform.json -// -// These also validate the format of the external API: request.imp[i].ext.adform - -// TestValidParams makes sure that the adform schema accepts all imp.ext fields which we intend to support. -func TestValidParams(t *testing.T) { - validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") - if err != nil { - t.Fatalf("Failed to fetch the json-schemas. %v", err) - } - - for _, validParam := range validParams { - if err := validator.Validate(openrtb_ext.BidderAdform, json.RawMessage(validParam)); err != nil { - t.Errorf("Schema rejected adform params: %s", validParam) - } - } -} - -// TestInvalidParams makes sure that the adform schema rejects all the imp.ext fields we don't support. -func TestInvalidParams(t *testing.T) { - validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") - if err != nil { - t.Fatalf("Failed to fetch the json-schemas. %v", err) - } - - for _, invalidParam := range invalidParams { - if err := validator.Validate(openrtb_ext.BidderAdform, json.RawMessage(invalidParam)); err == nil { - t.Errorf("Schema allowed unexpected params: %s", invalidParam) - } - } -} - -var validParams = []string{ - `{"mid":123}`, - `{"mid":"123"}`, - `{"mid":123,"priceType":"gross"}`, - `{"mid":"123","priceType":"net"}`, - `{"mid":"123","mkv":" color :blue , length : 350"}`, - `{"mid":"123","mkv":"color:"}`, - `{"mid":"123","mkw":"green,male"}`, - `{"mid":"123","mkv":" ","mkw":" "}`, - `{"mid":"123","cdims":"500x300,400x200","mkw":" "}`, - `{"mid":"123","cdims":"500x300","mkv":" ","mkw":" "}`, - `{"mid":"123","minp":2.1}`, - `{"mid":"123","url":"https://adform.com/page"}`, -} - -var invalidParams = []string{ - ``, - `null`, - `true`, - `5`, - `4.2`, - `[]`, - `{}`, - `{"notmid":"123"}`, - `{"mid":"123","priceType":"ne"}`, - `{"mid":"123","mkv":"color:blue,:350"}`, - `{"mid":"123","mkv":"color:blue;length:350"}`, - `{"mid":"123","mkv":"color"}`, - `{"mid":"123","mkv":"color:blue,l&ngth:350"}`, - `{"mid":"123","mkv":"color::blue"}`, - `{"mid":"123","mkw":"fem&le"}`, - `{"mid":"123","minp":"2.1"}`, - `{"mid":"123","cdims":"500x300:400:200","mkw":" "}`, - `{"mid":"123","cdims":"500x300,400:200","mkv":" ","mkw":" "}`, - `{"mid":"123","url":10}`, -} diff --git a/adapters/adhese/adhese.go b/adapters/adhese/adhese.go index 6fc12e3df5e..b23b49b3774 100644 --- a/adapters/adhese/adhese.go +++ b/adapters/adhese/adhese.go @@ -109,7 +109,7 @@ func (a *AdheseAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adap // Compose url endpointParams := macros.EndpointTemplateParams{AccountID: params.Account} - host, err := macros.ResolveMacros(*&a.endpointTemplate, endpointParams) + host, err := macros.ResolveMacros(a.endpointTemplate, endpointParams) if err != nil { errs = append(errs, WrapReqError("Could not compose url from template and request account val: "+err.Error())) return nil, errs diff --git a/adapters/adman/adman.go b/adapters/adman/adman.go index 808951d3aba..fd31bc9d14f 100644 --- a/adapters/adman/adman.go +++ b/adapters/adman/adman.go @@ -25,10 +25,6 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -type admanParams struct { - TagID string `json:"TagID"` -} - // MakeRequests create bid request for adman demand func (a *AdmanAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error diff --git a/adapters/adnuntius/adnuntius.go b/adapters/adnuntius/adnuntius.go new file mode 100644 index 00000000000..f556081ae70 --- /dev/null +++ b/adapters/adnuntius/adnuntius.go @@ -0,0 +1,303 @@ +package adnuntius + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "strconv" + "strings" + + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/util/timeutil" +) + +type QueryString map[string]string +type adapter struct { + time timeutil.Time + endpoint string +} +type adnAdunit struct { + AuId string `json:"auId"` + TargetId string `json:"targetId"` +} + +type AdnResponse struct { + AdUnits []struct { + AuId string + TargetId string + Html string + ResponseId string + Ads []struct { + Bid struct { + Amount float64 + Currency string + } + AdId string + CreativeWidth string + CreativeHeight string + CreativeId string + LineItemId string + Html string + DestinationUrls map[string]string + } + } +} +type adnMetaData struct { + Usi string `json:"usi,omitempty"` +} +type adnRequest struct { + AdUnits []adnAdunit `json:"adUnits"` + MetaData adnMetaData `json:"metaData,omitempty"` + Context string `json:"context,omitempty"` +} + +const defaultNetwork = "default" +const defaultSite = "unknown" +const minutesInHour = 60 + +// Builder builds a new instance of the Adnuntius adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &adapter{ + time: &timeutil.RealTime{}, + endpoint: config.Endpoint, + } + + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + return a.generateRequests(*request) +} + +func setHeaders() http.Header { + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + return headers +} + +func makeEndpointUrl(ortbRequest openrtb2.BidRequest, a *adapter) (string, []error) { + uri, err := url.Parse(a.endpoint) + if err != nil { + return "", []error{fmt.Errorf("failed to parse Adnuntius endpoint: %v", err)} + } + + gdpr, consent, err := getGDPR(&ortbRequest) + if err != nil { + return "", []error{fmt.Errorf("failed to parse Adnuntius endpoint: %v", err)} + } + + _, offset := a.time.Now().Zone() + tzo := -offset / minutesInHour + + q := uri.Query() + if gdpr != "" && consent != "" { + q.Set("gdpr", gdpr) + q.Set("consentString", consent) + } + q.Set("tzo", fmt.Sprint(tzo)) + q.Set("format", "json") + + url := a.endpoint + "?" + q.Encode() + return url, nil +} + +/* + Generate the requests to Adnuntius to reduce the amount of requests going out. +*/ +func (a *adapter) generateRequests(ortbRequest openrtb2.BidRequest) ([]*adapters.RequestData, []error) { + var requestData []*adapters.RequestData + networkAdunitMap := make(map[string][]adnAdunit) + headers := setHeaders() + + endpoint, err := makeEndpointUrl(ortbRequest, a) + if err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("failed to parse URL: %s", err), + }} + } + + for _, imp := range ortbRequest.Imp { + if imp.Banner == nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("ignoring imp id=%s, Adnuntius supports only Banner", imp.ID), + }} + } + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Error unmarshalling ExtImpBidder: %s", err.Error()), + }} + } + + var adnuntiusExt openrtb_ext.ImpExtAdnunitus + if err := json.Unmarshal(bidderExt.Bidder, &adnuntiusExt); err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Error unmarshalling ExtImpBmtm: %s", err.Error()), + }} + } + + network := defaultNetwork + if adnuntiusExt.Network != "" { + network = adnuntiusExt.Network + } + + networkAdunitMap[network] = append( + networkAdunitMap[network], + adnAdunit{ + AuId: adnuntiusExt.Auid, + TargetId: fmt.Sprintf("%s-%s", adnuntiusExt.Auid, imp.ID), + }) + } + + site := defaultSite + if ortbRequest.Site != nil && ortbRequest.Site.Page != "" { + site = ortbRequest.Site.Page + } + + for _, networkAdunits := range networkAdunitMap { + + adnuntiusRequest := adnRequest{ + AdUnits: networkAdunits, + Context: site, + } + + ortbUser := ortbRequest.User + if ortbUser != nil { + ortbUserId := ortbRequest.User.ID + if ortbUserId != "" { + adnuntiusRequest.MetaData.Usi = ortbRequest.User.ID + } + } + + adnJson, err := json.Marshal(adnuntiusRequest) + if err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Error unmarshalling adnuntius request: %s", err.Error()), + }} + } + + requestData = append(requestData, &adapters.RequestData{ + Method: http.MethodPost, + Uri: endpoint, + Body: adnJson, + Headers: headers, + }) + + } + + return requestData, nil +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + + if response.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Status code: %d, Request malformed", response.StatusCode), + }} + } + + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Status code: %d, Something went wrong with your request", response.StatusCode), + }} + } + + var adnResponse AdnResponse + if err := json.Unmarshal(response.Body, &adnResponse); err != nil { + return nil, []error{err} + } + + bidResponse, bidErr := generateBidResponse(&adnResponse, request) + if bidErr != nil { + return nil, bidErr + } + + return bidResponse, nil +} + +func getGDPR(request *openrtb2.BidRequest) (string, string, error) { + gdpr := "" + var extRegs openrtb_ext.ExtRegs + if request.Regs != nil { + if err := json.Unmarshal(request.Regs.Ext, &extRegs); err != nil { + return "", "", fmt.Errorf("failed to parse ExtRegs in Adnuntius GDPR check: %v", err) + } + if extRegs.GDPR != nil && (*extRegs.GDPR == 0 || *extRegs.GDPR == 1) { + gdpr = strconv.Itoa(int(*extRegs.GDPR)) + } + } + + consent := "" + if request.User != nil && request.User.Ext != nil { + var extUser openrtb_ext.ExtUser + if err := json.Unmarshal(request.User.Ext, &extUser); err != nil { + return "", "", fmt.Errorf("failed to parse ExtUser in Adnuntius GDPR check: %v", err) + } + consent = extUser.Consent + } + + return gdpr, consent, nil +} + +func generateBidResponse(adnResponse *AdnResponse, request *openrtb2.BidRequest) (*adapters.BidderResponse, []error) { + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(adnResponse.AdUnits)) + var currency string + + for i, adunit := range adnResponse.AdUnits { + + if len(adunit.Ads) > 0 { + + ad := adunit.Ads[0] + + currency = ad.Bid.Currency + + creativeWidth, widthErr := strconv.ParseInt(ad.CreativeWidth, 10, 64) + if widthErr != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Value of width: %s is not a string", ad.CreativeWidth), + }} + } + + creativeHeight, heightErr := strconv.ParseInt(ad.CreativeHeight, 10, 64) + if heightErr != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Value of height: %s is not a string", ad.CreativeHeight), + }} + } + + adDomain := []string{} + for _, url := range ad.DestinationUrls { + domainArray := strings.Split(url, "/") + domain := strings.Replace(domainArray[2], "www.", "", -1) + adDomain = append(adDomain, domain) + } + + bid := openrtb2.Bid{ + ID: ad.AdId, + ImpID: request.Imp[i].ID, + W: creativeWidth, + H: creativeHeight, + AdID: ad.AdId, + CID: ad.LineItemId, + CrID: ad.CreativeId, + Price: ad.Bid.Amount * 1000, + AdM: adunit.Html, + ADomain: adDomain, + } + + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: "banner", + }) + } + + } + bidResponse.Currency = currency + return bidResponse, nil +} diff --git a/adapters/adnuntius/adnuntius_test.go b/adapters/adnuntius/adnuntius_test.go new file mode 100644 index 00000000000..270f27a9366 --- /dev/null +++ b/adapters/adnuntius/adnuntius_test.go @@ -0,0 +1,46 @@ +package adnuntius + +import ( + "testing" + "time" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderAdnuntius, config.Adapter{ + Endpoint: "http://whatever.url"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + assertTzo(t, bidder) + replaceRealTimeWithKnownTime(bidder) + + adapterstest.RunJSONBidderTest(t, "adnuntiustest", bidder) +} + +func assertTzo(t *testing.T, bidder adapters.Bidder) { + bidderAdnuntius, _ := bidder.(*adapter) + assert.NotNil(t, bidderAdnuntius.time) +} + +// FakeTime implements the Time interface +type FakeTime struct { + time time.Time +} + +func (ft *FakeTime) Now() time.Time { + return ft.time +} + +func replaceRealTimeWithKnownTime(bidder adapters.Bidder) { + bidderAdnuntius, _ := bidder.(*adapter) + bidderAdnuntius.time = &FakeTime{ + time: time.Date(2016, 1, 1, 12, 30, 15, 0, time.UTC), + } +} diff --git a/adapters/adnuntius/adnuntiustest/exemplary/simple-banner.json b/adapters/adnuntius/adnuntiustest/exemplary/simple-banner.json new file mode 100644 index 00000000000..6e943810aa9 --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/exemplary/simple-banner.json @@ -0,0 +1,99 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "auId": "123" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.url?format=json&tzo=0", + "body": { + "adUnits": [ + { + "auId": "123", + "targetId": "123-test-imp-id" + } + ], + "context": "prebid.org", + "metaData": {} + } + }, + "mockResponse": { + "status": 200, + "body": { + "adUnits": [ + { + "auId": "0000000000000123", + "targetId": "123-test-imp-id", + "html": "", + "responseId": "adn-rsp-900646517", + "ads": [ + { + "destinationUrls": { + "url": "http://www.google.com" + }, + "bid": { + "amount": 20.0, + "currency": "NOK" + }, + "adId": "adn-id-1559784094", + "creativeWidth": "980", + "creativeHeight": "240", + "creativeId": "jn9hpzvlsf8cpdmm", + "lineItemId": "q7y9qm5b0xt9htrv" + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "adn-id-1559784094", + "impid": "test-imp-id", + "price": 20000, + "adm": "", + "adid": "adn-id-1559784094", + "adomain": ["google.com"], + "cid": "q7y9qm5b0xt9htrv", + "crid": "jn9hpzvlsf8cpdmm", + "w": 980, + "h": 240 + }, + "type": "banner" + } + ], + "currency": "NOK" + } + ] +} diff --git a/adapters/adnuntius/adnuntiustest/supplemental/check-gdpr.json b/adapters/adnuntius/adnuntiustest/supplemental/check-gdpr.json new file mode 100644 index 00000000000..e195429cef9 --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/supplemental/check-gdpr.json @@ -0,0 +1,109 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "id": "1kjh3429kjh295jkl", + "ext": { + "consent": "CONSENT_STRING" + } + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "auId": "123" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.url?consentString=CONSENT_STRING&format=json&gdpr=1&tzo=0", + "body": { + "adUnits": [ + { + "auId": "123", + "targetId": "123-test-imp-id" + } + ], + "metaData": { + "usi": "1kjh3429kjh295jkl" + }, + "context": "unknown" + } + }, + "mockResponse": { + "status": 200, + "body": { + "adUnits": [ + { + "auId": "0000000000000123", + "targetId": "123-test-imp-id", + "html": "", + "responseId": "adn-rsp-900646517", + "ads": [ + { + "destinationUrls": { + "url": "http://www.google.com" + }, + "bid": { + "amount": 20.0, + "currency": "NOK" + }, + "adId": "adn-id-1559784094", + "creativeWidth": "980", + "creativeHeight": "240", + "creativeId": "jn9hpzvlsf8cpdmm", + "lineItemId": "q7y9qm5b0xt9htrv" + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "adn-id-1559784094", + "impid": "test-imp-id", + "price": 20000, + "adm": "", + "adid": "adn-id-1559784094", + "adomain": ["google.com"], + "cid": "q7y9qm5b0xt9htrv", + "crid": "jn9hpzvlsf8cpdmm", + "w": 980, + "h": 240 + }, + "type": "banner" + } + ], + "currency": "NOK" + } + ] +} diff --git a/adapters/adnuntius/adnuntiustest/supplemental/check-userId.json b/adapters/adnuntius/adnuntiustest/supplemental/check-userId.json new file mode 100644 index 00000000000..8305bcdb59a --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/supplemental/check-userId.json @@ -0,0 +1,101 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "id": "1kjh3429kjh295jkl" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "auId": "123" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.url?format=json&tzo=0", + "body": { + "adUnits": [ + { + "auId": "123", + "targetId": "123-test-imp-id" + } + ], + "metaData": { + "usi": "1kjh3429kjh295jkl" + }, + "context": "unknown" + } + }, + "mockResponse": { + "status": 200, + "body": { + "adUnits": [ + { + "auId": "0000000000000123", + "targetId": "123-test-imp-id", + "html": "", + "responseId": "adn-rsp-900646517", + "ads": [ + { + "destinationUrls": { + "url": "http://www.google.com" + }, + "bid": { + "amount": 20.0, + "currency": "NOK" + }, + "adId": "adn-id-1559784094", + "creativeWidth": "980", + "creativeHeight": "240", + "creativeId": "jn9hpzvlsf8cpdmm", + "lineItemId": "q7y9qm5b0xt9htrv" + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "adn-id-1559784094", + "impid": "test-imp-id", + "price": 20000, + "adm": "", + "adid": "adn-id-1559784094", + "adomain": ["google.com"], + "cid": "q7y9qm5b0xt9htrv", + "crid": "jn9hpzvlsf8cpdmm", + "w": 980, + "h": 240 + }, + "type": "banner" + } + ], + "currency": "NOK" + } + ] +} diff --git a/adapters/adnuntius/adnuntiustest/supplemental/height-error.json b/adapters/adnuntius/adnuntiustest/supplemental/height-error.json new file mode 100644 index 00000000000..ba388f1deea --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/supplemental/height-error.json @@ -0,0 +1,84 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "auId": "123" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.url?format=json&tzo=0", + "body": { + "adUnits": [ + { + "auId": "123", + "targetId": "123-test-imp-id" + } + ], + "context": "prebid.org", + "metaData": {} + } + }, + "mockResponse": { + "status": 200, + "body": { + "adUnits": [ + { + "auId": "0000000000000123", + "targetId": "123-test-imp-id", + "html": "", + "responseId": "adn-rsp-900646517", + "ads": [ + { + "destinationUrls": { + "url": "http://www.google.com" + }, + "bid": { + "amount": 20.0, + "currency": "NOK" + }, + "adId": "adn-id-1559784094", + "creativeWidth": "abc", + "creativeHeight": 240, + "creativeId": "jn9hpzvlsf8cpdmm", + "lineItemId": "q7y9qm5b0xt9htrv" + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal number into Go struct field .AdUnits.Ads.CreativeHeight of type string", + "comparison": "literal" + } + ] +} diff --git a/adapters/adnuntius/adnuntiustest/supplemental/native-error.json b/adapters/adnuntius/adnuntiustest/supplemental/native-error.json new file mode 100644 index 00000000000..a8203e581ef --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/supplemental/native-error.json @@ -0,0 +1,25 @@ +{ + "mockBidRequest": { + "id": "unsupported-native-request", + "imp": [ + { + "id": "unsupported-native-imp", + "native": { + "ver": "1.1" + }, + "ext": { + "bidder": { + "auId": "1" + } + } + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "ignoring imp id=unsupported-native-imp, Adnuntius supports only Banner", + "comparison": "literal" + } + ] +} diff --git a/adapters/adnuntius/adnuntiustest/supplemental/status-400.json b/adapters/adnuntius/adnuntiustest/supplemental/status-400.json new file mode 100644 index 00000000000..c6da39dadd5 --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/supplemental/status-400.json @@ -0,0 +1,54 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "auI": "1" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.url?format=json&tzo=0", + "body": { + "adUnits": [ + { + "auId": "", + "targetId": "-test-imp-id" + } + ], + "context": "unknown", + "metaData": {} + } + }, + "mockResponse": { + "status": 400, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Status code: 400, Request malformed", + "comparison": "literal" + } + ] +} diff --git a/adapters/adnuntius/adnuntiustest/supplemental/test-networks.json b/adapters/adnuntius/adnuntiustest/supplemental/test-networks.json new file mode 100644 index 00000000000..727cf19498f --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/supplemental/test-networks.json @@ -0,0 +1,97 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-network", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "auId": "123", + "network": "test" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.url?format=json&tzo=0", + "body": { + "adUnits": [ + { + "auId": "123", + "targetId": "123-test-network" + } + ], + "context": "unknown", + "metaData": {} + } + }, + "mockResponse": { + "status": 200, + "body": { + "adUnits": [ + { + "auId": "0000000000000123", + "targetId": "123-test-imp-id", + "html": "", + "responseId": "adn-rsp-900646517", + "ads": [ + { + "destinationUrls": { + "url": "http://www.google.com" + }, + "bid": { + "amount": 20.0, + "currency": "NOK" + }, + "adId": "adn-id-1559784094", + "creativeWidth": "980", + "creativeHeight": "240", + "creativeId": "jn9hpzvlsf8cpdmm", + "lineItemId": "q7y9qm5b0xt9htrv" + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "adn-id-1559784094", + "impid": "test-network", + "price": 20000, + "adm": "", + "adid": "adn-id-1559784094", + "adomain": ["google.com"], + "cid": "q7y9qm5b0xt9htrv", + "crid": "jn9hpzvlsf8cpdmm", + "w": 980, + "h": 240 + }, + "type": "banner" + } + ], + "currency": "NOK" + } + ] +} diff --git a/adapters/adnuntius/adnuntiustest/supplemental/video-error.json b/adapters/adnuntius/adnuntiustest/supplemental/video-error.json new file mode 100644 index 00000000000..357cb165f36 --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/supplemental/video-error.json @@ -0,0 +1,26 @@ +{ + "mockBidRequest": { + "id": "unsupported-native-request", + "imp": [ + { + "id": "unsupported-native-imp", + "video": { + "w": 728, + "h": 90 + }, + "ext": { + "bidder": { + "auId": "1" + } + } + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "ignoring imp id=unsupported-native-imp, Adnuntius supports only Banner", + "comparison": "literal" + } + ] +} diff --git a/adapters/adnuntius/adnuntiustest/supplemental/width-error.json b/adapters/adnuntius/adnuntiustest/supplemental/width-error.json new file mode 100644 index 00000000000..2659841197f --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/supplemental/width-error.json @@ -0,0 +1,84 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "auId": "123" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.url?format=json&tzo=0", + "body": { + "adUnits": [ + { + "auId": "123", + "targetId": "123-test-imp-id" + } + ], + "context": "prebid.org", + "metaData": {} + } + }, + "mockResponse": { + "status": 200, + "body": { + "adUnits": [ + { + "auId": "0000000000000123", + "targetId": "123-test-imp-id", + "html": "", + "responseId": "adn-rsp-900646517", + "ads": [ + { + "destinationUrls": { + "url": "http://www.google.com" + }, + "bid": { + "amount": 20.0, + "currency": "NOK" + }, + "adId": "adn-id-1559784094", + "creativeWidth": 980, + "creativeHeight": "abc", + "creativeId": "jn9hpzvlsf8cpdmm", + "lineItemId": "q7y9qm5b0xt9htrv" + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal number into Go struct field .AdUnits.Ads.CreativeWidth of type string", + "comparison": "literal" + } + ] +} diff --git a/adapters/adnuntius/params_test.go b/adapters/adnuntius/params_test.go new file mode 100644 index 00000000000..dd8fafc79a9 --- /dev/null +++ b/adapters/adnuntius/params_test.go @@ -0,0 +1,60 @@ +package adnuntius + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +// This file actually intends to test static/bidder-params/adnuntius.json +// These also validate the format of the external API: request.imp[i].ext.adnuntius +// TestValidParams makes sure that the adnuntius schema accepts all imp.ext fields which we intend to support. + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderAdnuntius, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected adnuntius params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the adnuntius schema rejects all the imp.ext fields we don't support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderAdnuntius, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"auId":"123"}`, + `{"auId":"123", "network":"test"}`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{"auId":123}`, + `{"auID":"123"}`, + `{"network":123}`, + `{"network":123, "auID":123}`, + `{"network":"test", "auID":123}`, + `{"network":test, "auID":"123"}`, +} diff --git a/adapters/adview/adview.go b/adapters/adview/adview.go index c3e78b4221c..e987298139f 100644 --- a/adapters/adview/adview.go +++ b/adapters/adview/adview.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "net/http" + "strings" "text/template" "github.com/mxmCherry/openrtb/v15/openrtb2" @@ -58,6 +59,22 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte } } + // Check if imp comes with bid floor amount defined in a foreign currency + if imp.BidFloor > 0 && imp.BidFloorCur != "" && strings.ToUpper(imp.BidFloorCur) != "USD" { + // Convert to US dollars + convertedValue, err := requestInfo.ConvertCurrency(imp.BidFloor, imp.BidFloorCur, "USD") + if err != nil { + return nil, []error{err} + } + // Update after conversion. All imp elements inside request.Imp are shallow copies + // therefore, their non-pointer values are not shared memory and are safe to modify. + imp.BidFloorCur = "USD" + imp.BidFloor = convertedValue + } + + // Set the CUR of bid to USD after converting all floors + request.Cur = []string{"USD"} + url, err := a.buildEndpointURL(&advImpExt) if err != nil { return nil, []error{err} @@ -100,7 +117,8 @@ func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.R } bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) - bidResponse.Currency = response.Cur + bidResponse.Currency = "USD" //we just support USD for resp + var errors []error for _, seatBid := range response.SeatBid { for i, bid := range seatBid.Bid { diff --git a/adapters/adview/adviewtest/exemplary/banner-app-format.json b/adapters/adview/adviewtest/exemplary/banner-app-format.json index 49956cdc519..0ab4951511e 100644 --- a/adapters/adview/adviewtest/exemplary/banner-app-format.json +++ b/adapters/adview/adviewtest/exemplary/banner-app-format.json @@ -50,6 +50,7 @@ "uri": "https://bid.adview.com/agent/thirdAdxService/accountid01", "body": { "id": "some-request-id", + "cur": ["USD"], "device": { "ua": "test-user-agent", "ip": "123.123.123.123", @@ -134,6 +135,7 @@ ], "expectedBidResponses": [ { + "currency": "USD", "bids": [ { "bid": { diff --git a/adapters/adview/adviewtest/exemplary/banner-app.json b/adapters/adview/adviewtest/exemplary/banner-app.json index e8697229f4a..6aad1e8dc05 100644 --- a/adapters/adview/adviewtest/exemplary/banner-app.json +++ b/adapters/adview/adviewtest/exemplary/banner-app.json @@ -46,6 +46,7 @@ "uri": "https://bid.adview.com/agent/thirdAdxService/accountid01", "body": { "id": "some-request-id", + "cur": ["USD"], "device": { "ua": "test-user-agent", "ip": "123.123.123.123", @@ -124,6 +125,7 @@ ], "expectedBidResponses": [ { + "currency": "USD", "bids": [ { "bid": { diff --git a/adapters/adview/adviewtest/exemplary/native-app.json b/adapters/adview/adviewtest/exemplary/native-app.json index df776a36d30..804494e5ff5 100644 --- a/adapters/adview/adviewtest/exemplary/native-app.json +++ b/adapters/adview/adviewtest/exemplary/native-app.json @@ -46,6 +46,7 @@ "uri": "https://bid.adview.com/agent/thirdAdxService/accountid01", "body": { "id": "some-request-id", + "cur": ["USD"], "device": { "ua": "test-user-agent", "ip": "123.123.123.123", @@ -122,6 +123,7 @@ ], "expectedBidResponses": [ { + "currency": "USD", "bids":[ { "bid": { diff --git a/adapters/adview/adviewtest/exemplary/video-app.json b/adapters/adview/adviewtest/exemplary/video-app.json index 2136a8f7f33..57c9b85598b 100644 --- a/adapters/adview/adviewtest/exemplary/video-app.json +++ b/adapters/adview/adviewtest/exemplary/video-app.json @@ -51,6 +51,7 @@ "uri": "https://bid.adview.com/agent/thirdAdxService/accountid01", "body": { "id": "some-request-id", + "cur": ["USD"], "device": { "ua": "test-user-agent", "ip": "123.123.123.123", @@ -133,6 +134,7 @@ ], "expectedBidResponses": [ { + "currency": "USD", "bids":[ { "bid": { diff --git a/adapters/adview/adviewtest/supplemental/bad-request.json b/adapters/adview/adviewtest/supplemental/bad-request.json index 131e6d830a2..0f10fe79062 100644 --- a/adapters/adview/adviewtest/supplemental/bad-request.json +++ b/adapters/adview/adviewtest/supplemental/bad-request.json @@ -20,6 +20,7 @@ "uri": "https://bid.adview.com/agent/thirdAdxService/accountid01", "body": { "id": "test-request-id", + "cur": ["USD"], "imp": [{ "ext": { "bidder": { diff --git a/adapters/adview/adviewtest/supplemental/currency_rate_not_found.json b/adapters/adview/adviewtest/supplemental/currency_rate_not_found.json new file mode 100644 index 00000000000..61d1558cfe4 --- /dev/null +++ b/adapters/adview/adviewtest/supplemental/currency_rate_not_found.json @@ -0,0 +1,44 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "bidfloor": 1.00, + "bidfloorcur": "JPY", + "ext":{ + "bidder": { + "placementId": "posid00001", + "accountId": "accountid01" + } + } + } + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "MXN": { + "USD": 0.05 + } + }, + "usepbsrates": false + } + } + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Currency conversion rate not found: 'JPY' => 'USD'", + "comparison": "literal" + } + ] +} diff --git a/adapters/adview/adviewtest/supplemental/empty-response.json b/adapters/adview/adviewtest/supplemental/empty-response.json index 5bd86bc99a1..c080b18d4bb 100644 --- a/adapters/adview/adviewtest/supplemental/empty-response.json +++ b/adapters/adview/adviewtest/supplemental/empty-response.json @@ -20,6 +20,7 @@ "uri": "https://bid.adview.com/agent/thirdAdxService/accountid01", "body": { "id": "test-request-id", + "cur": ["USD"], "imp": [{ "ext": { "bidder": { diff --git a/adapters/adview/adviewtest/supplemental/nobid-response.json b/adapters/adview/adviewtest/supplemental/nobid-response.json index f2c8864d4b1..9e9ab678a22 100644 --- a/adapters/adview/adviewtest/supplemental/nobid-response.json +++ b/adapters/adview/adviewtest/supplemental/nobid-response.json @@ -20,6 +20,7 @@ "uri": "https://bid.adview.com/agent/thirdAdxService/accountid01", "body": { "id": "test-request-id", + "cur": ["USD"], "imp": [{ "ext": { "bidder": { diff --git a/adapters/adview/adviewtest/supplemental/server-error.json b/adapters/adview/adviewtest/supplemental/server-error.json index 7894be1b70a..de3bc9d3721 100644 --- a/adapters/adview/adviewtest/supplemental/server-error.json +++ b/adapters/adview/adviewtest/supplemental/server-error.json @@ -20,6 +20,7 @@ "uri": "https://bid.adview.com/agent/thirdAdxService/accountid01", "body": { "id": "test-request-id", + "cur": ["USD"], "imp": [{ "ext": { "bidder": { diff --git a/adapters/adview/adviewtest/supplemental/unparsable-response.json b/adapters/adview/adviewtest/supplemental/unparsable-response.json index cef70ba8771..18bc2e0f4ed 100644 --- a/adapters/adview/adviewtest/supplemental/unparsable-response.json +++ b/adapters/adview/adviewtest/supplemental/unparsable-response.json @@ -20,6 +20,7 @@ "uri": "https://bid.adview.com/agent/thirdAdxService/accountid01", "body": { "id": "test-request-id", + "cur": ["USD"], "imp": [{ "ext": { "bidder": { diff --git a/adapters/appnexus/appnexus.go b/adapters/appnexus/appnexus.go index b1004601774..3695f541532 100644 --- a/adapters/appnexus/appnexus.go +++ b/adapters/appnexus/appnexus.go @@ -1,8 +1,6 @@ package appnexus import ( - "bytes" - "context" "encoding/json" "errors" "fmt" @@ -15,9 +13,6 @@ import ( "github.com/buger/jsonparser" "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/pbs" - - "golang.org/x/net/context/ctxhttp" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/errortypes" @@ -27,22 +22,12 @@ import ( const defaultPlatformID int = 5 -type AppNexusAdapter struct { - http *adapters.HTTPAdapter +type adapter struct { URI string iabCategoryMap map[string]string hbSource int } -// used for cookies and such -func (a *AppNexusAdapter) Name() string { - return "adnxs" -} - -func (a *AppNexusAdapter) SkipNoCookies() bool { - return false -} - type KeyVal struct { Key string `json:"key,omitempty"` Values []string `json:"value,omitempty"` @@ -52,21 +37,6 @@ type appnexusAdapterOptions struct { IabCategories map[string]string `json:"iab_categories"` } -type appnexusParams struct { - LegacyPlacementId int `json:"placementId"` - LegacyInvCode string `json:"invCode"` - LegacyTrafficSourceCode string `json:"trafficSourceCode"` - PlacementId int `json:"placement_id"` - InvCode string `json:"inv_code"` - Member string `json:"member"` - Keywords []KeyVal `json:"keywords"` - TrafficSourceCode string `json:"traffic_source_code"` - Reserve float64 `json:"reserve"` - Position string `json:"position"` - UsePmtRule *bool `json:"use_pmt_rule"` - PrivateSizes json.RawMessage `json:"private_sizes"` -} - type appnexusImpExtAppnexus struct { PlacementID int `json:"placement_id,omitempty"` Keywords string `json:"keywords,omitempty"` @@ -115,181 +85,7 @@ type appnexusReqExt struct { var maxImpsPerReq = 10 -func (a *AppNexusAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { - supportedMediaTypes := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} - anReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), supportedMediaTypes) - - if err != nil { - return nil, err - } - uri := a.URI - for i, unit := range bidder.AdUnits { - var params appnexusParams - err := json.Unmarshal(unit.Params, ¶ms) - if err != nil { - return nil, err - } - // Accept legacy Appnexus parameters if we don't have modern ones - // Don't worry if both is set as validation rules should prevent, and this is temporary anyway. - if params.PlacementId == 0 && params.LegacyPlacementId != 0 { - params.PlacementId = params.LegacyPlacementId - } - if params.InvCode == "" && params.LegacyInvCode != "" { - params.InvCode = params.LegacyInvCode - } - if params.TrafficSourceCode == "" && params.LegacyTrafficSourceCode != "" { - params.TrafficSourceCode = params.LegacyTrafficSourceCode - } - - if params.PlacementId == 0 && (params.InvCode == "" || params.Member == "") { - return nil, &errortypes.BadInput{ - Message: "No placement or member+invcode provided", - } - } - - // Fixes some segfaults. Since this is legacy code, I'm not looking into it too deeply - if len(anReq.Imp) <= i { - break - } - if params.InvCode != "" { - anReq.Imp[i].TagID = params.InvCode - if params.Member != "" { - // this assumes that the same member ID is used across all tags, which should be the case - uri = appendMemberId(a.URI, params.Member) - } - - } - if params.Reserve > 0 { - anReq.Imp[i].BidFloor = params.Reserve // TODO: we need to factor in currency here if non-USD - } - if anReq.Imp[i].Banner != nil && params.Position != "" { - if params.Position == "above" { - anReq.Imp[i].Banner.Pos = openrtb2.AdPositionAboveTheFold.Ptr() - } else if params.Position == "below" { - anReq.Imp[i].Banner.Pos = openrtb2.AdPositionBelowTheFold.Ptr() - } - } - - kvs := make([]string, 0, len(params.Keywords)*2) - for _, kv := range params.Keywords { - if len(kv.Values) == 0 { - kvs = append(kvs, kv.Key) - } else { - for _, val := range kv.Values { - kvs = append(kvs, fmt.Sprintf("%s=%s", kv.Key, val)) - } - - } - } - - keywordStr := strings.Join(kvs, ",") - - impExt := appnexusImpExt{Appnexus: appnexusImpExtAppnexus{ - PlacementID: params.PlacementId, - TrafficSourceCode: params.TrafficSourceCode, - Keywords: keywordStr, - UsePmtRule: params.UsePmtRule, - PrivateSizes: params.PrivateSizes, - }} - anReq.Imp[i].Ext, err = json.Marshal(&impExt) - } - - reqJSON, err := json.Marshal(anReq) - if err != nil { - return nil, err - } - - debug := &pbs.BidderDebug{ - RequestURI: uri, - } - - if req.IsDebug { - debug.RequestBody = string(reqJSON) - bidder.Debug = append(bidder.Debug, debug) - } - - httpReq, err := http.NewRequest("POST", uri, bytes.NewBuffer(reqJSON)) - httpReq.Header.Add("Content-Type", "application/json;charset=utf-8") - httpReq.Header.Add("Accept", "application/json") - - anResp, err := ctxhttp.Do(ctx, a.http.Client, httpReq) - if err != nil { - return nil, err - } - - debug.StatusCode = anResp.StatusCode - - if anResp.StatusCode == 204 { - return nil, nil - } - - defer anResp.Body.Close() - body, err := ioutil.ReadAll(anResp.Body) - if err != nil { - return nil, err - } - responseBody := string(body) - - if anResp.StatusCode == http.StatusBadRequest { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("HTTP status %d; body: %s", anResp.StatusCode, responseBody), - } - } - - if anResp.StatusCode != http.StatusOK { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("HTTP status %d; body: %s", anResp.StatusCode, responseBody), - } - } - - if req.IsDebug { - debug.ResponseBody = responseBody - } - - var bidResp openrtb2.BidResponse - err = json.Unmarshal(body, &bidResp) - if err != nil { - return nil, err - } - - bids := make(pbs.PBSBidSlice, 0) - - for _, sb := range bidResp.SeatBid { - for _, bid := range sb.Bid { - bidID := bidder.LookupBidID(bid.ImpID) - if bidID == "" { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unknown ad unit code '%s'", bid.ImpID), - } - } - - pbid := pbs.PBSBid{ - BidID: bidID, - AdUnitCode: bid.ImpID, - BidderCode: bidder.BidderCode, - Price: bid.Price, - Adm: bid.AdM, - Creative_id: bid.CrID, - Width: bid.W, - Height: bid.H, - DealId: bid.DealID, - NURL: bid.NURL, - } - - var impExt appnexusBidExt - if err := json.Unmarshal(bid.Ext, &impExt); err == nil { - if mediaType, err := getMediaTypeForBid(&impExt); err == nil { - pbid.CreativeMediaType = string(mediaType) - bids = append(bids, &pbid) - } - } - } - } - - return bids, nil -} - -func (a *AppNexusAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { memberIds := make(map[string]bool) errs := make([]error, 0, len(request.Imp)) @@ -383,7 +179,7 @@ func (a *AppNexusAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ad requests := make([]*adapters.RequestData, 0, len(podImps)) for _, podImps := range podImps { - reqExt.Appnexus.AdPodId = generatePodId() + reqExt.Appnexus.AdPodId = generatePodID() reqs, errors := splitRequests(podImps, request, reqExt, thisURI, errs) requests = append(requests, reqs...) @@ -395,7 +191,7 @@ func (a *AppNexusAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ad return splitRequests(imps, request, reqExt, thisURI, errs) } -func generatePodId() string { +func generatePodID() string { val := rand.Int63() return fmt.Sprint(val) } @@ -561,7 +357,7 @@ func makeKeywordStr(keywords []*openrtb_ext.ExtImpAppnexusKeyVal) string { return strings.Join(kvs, ",") } -func (a *AppNexusAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -596,7 +392,7 @@ func (a *AppNexusAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa bid.Cat = []string{iabCategory} } else if len(bid.Cat) > 1 { //create empty categories array to force bid to be rejected - bid.Cat = make([]string, 0, 0) + bid.Cat = make([]string, 0) } impVideo := &openrtb_ext.ExtBidPrebidVideo{ @@ -638,7 +434,7 @@ func getMediaTypeForBid(bid *appnexusBidExt) (openrtb_ext.BidType, error) { } // getIabCategoryForBid maps an appnexus brand id to an IAB category. -func (a *AppNexusAdapter) getIabCategoryForBid(bid *appnexusBidExt) (string, error) { +func (a *adapter) getIabCategoryForBid(bid *appnexusBidExt) (string, error) { brandIDString := strconv.Itoa(bid.Appnexus.BrandCategory) if iabCategory, ok := a.iabCategoryMap[brandIDString]; ok { return iabCategory, nil @@ -657,7 +453,7 @@ func appendMemberId(uri string, memberId string) string { // Builder builds a new instance of the AppNexus adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { - bidder := &AppNexusAdapter{ + bidder := &adapter{ URI: config.Endpoint, iabCategoryMap: loadCategoryMapFromFileSystem(), hbSource: resolvePlatformID(config.PlatformID), @@ -665,16 +461,6 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -// NewAppNexusLegacyAdapter builds a legacy version of the AppNexus adapter. -func NewAppNexusLegacyAdapter(httpConfig *adapters.HTTPAdapterConfig, endpoint, platformID string) *AppNexusAdapter { - return &AppNexusAdapter{ - http: adapters.NewHTTPAdapter(httpConfig), - URI: endpoint, - iabCategoryMap: loadCategoryMapFromFileSystem(), - hbSource: resolvePlatformID(platformID), - } -} - func resolvePlatformID(platformID string) int { if len(platformID) > 0 { if val, err := strconv.Atoi(platformID); err == nil { diff --git a/adapters/appnexus/appnexus_test.go b/adapters/appnexus/appnexus_test.go index cad52348134..6399be1a5bb 100644 --- a/adapters/appnexus/appnexus_test.go +++ b/adapters/appnexus/appnexus_test.go @@ -1,29 +1,17 @@ package appnexus import ( - "bytes" - "context" "encoding/json" - "io/ioutil" - "net/http" - "net/http/httptest" "regexp" "testing" - "time" - - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/stretchr/testify/assert" - - "github.com/prebid/prebid-server/cache/dummycache" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/pbs" - "github.com/prebid/prebid-server/usersync" - - "fmt" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" + + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/stretchr/testify/assert" ) func TestJsonSamples(t *testing.T) { @@ -58,7 +46,7 @@ func TestMemberQueryParam(t *testing.T) { } func TestVideoSinglePod(t *testing.T) { - var a AppNexusAdapter + var a adapter a.URI = "http://test.com/openrtb2" a.hbSource = 5 @@ -96,7 +84,7 @@ func TestVideoSinglePod(t *testing.T) { } func TestVideoSinglePodManyImps(t *testing.T) { - var a AppNexusAdapter + var a adapter a.URI = "http://test.com/openrtb2" a.hbSource = 5 @@ -154,7 +142,7 @@ func TestVideoSinglePodManyImps(t *testing.T) { } func TestVideoTwoPods(t *testing.T) { - var a AppNexusAdapter + var a adapter a.URI = "http://test.com/openrtb2" a.hbSource = 5 @@ -206,7 +194,7 @@ func TestVideoTwoPods(t *testing.T) { } func TestVideoTwoPodsManyImps(t *testing.T) { - var a AppNexusAdapter + var a adapter a.URI = "http://test.com/openrtb2" a.hbSource = 5 @@ -281,396 +269,3 @@ func TestVideoTwoPodsManyImps(t *testing.T) { assert.Len(t, podIds, 2, "Incorrect number of unique pod ids") } - -// ---------------------------------------------------------------------------- -// Code below this line tests the legacy, non-openrtb code flow. It can be deleted after we -// clean up the existing code and make everything openrtb2. - -type anTagInfo struct { - code string - invCode string - placementID int - trafficSourceCode string - in_keywords string - out_keywords string - reserve float64 - position string - bid float64 - content string - mediaType string -} - -type anBidInfo struct { - memberID string - domain string - page string - accountID int - siteID int - tags []anTagInfo - deviceIP string - deviceUA string - buyerUID string - delay time.Duration -} - -var andata anBidInfo - -func DummyAppNexusServer(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - body, err := ioutil.ReadAll(r.Body) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - var breq openrtb2.BidRequest - err = json.Unmarshal(body, &breq) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - memberID := r.FormValue("member_id") - if memberID != andata.memberID { - http.Error(w, fmt.Sprintf("Member ID '%s' doesn't match '%s", memberID, andata.memberID), http.StatusInternalServerError) - return - } - - resp := openrtb2.BidResponse{ - ID: breq.ID, - BidID: "a-random-id", - Cur: "USD", - SeatBid: []openrtb2.SeatBid{ - { - Seat: "Buyer Member ID", - Bid: make([]openrtb2.Bid, 0, 2), - }, - }, - } - - for i, imp := range breq.Imp { - var aix appnexusImpExt - err = json.Unmarshal(imp.Ext, &aix) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // Either placementID or member+invCode must be specified - has_placement := false - if aix.Appnexus.PlacementID != 0 { - if aix.Appnexus.PlacementID != andata.tags[i].placementID { - http.Error(w, fmt.Sprintf("Placement ID '%d' doesn't match '%d", aix.Appnexus.PlacementID, - andata.tags[i].placementID), http.StatusInternalServerError) - return - } - has_placement = true - } - if memberID != "" && imp.TagID != "" { - if imp.TagID != andata.tags[i].invCode { - http.Error(w, fmt.Sprintf("Inv Code '%s' doesn't match '%s", imp.TagID, - andata.tags[i].invCode), http.StatusInternalServerError) - return - } - has_placement = true - } - if !has_placement { - http.Error(w, fmt.Sprintf("Either placement or member+inv not present"), http.StatusInternalServerError) - return - } - - if aix.Appnexus.Keywords != andata.tags[i].out_keywords { - http.Error(w, fmt.Sprintf("Keywords '%s' doesn't match '%s", aix.Appnexus.Keywords, - andata.tags[i].out_keywords), http.StatusInternalServerError) - return - } - - if aix.Appnexus.TrafficSourceCode != andata.tags[i].trafficSourceCode { - http.Error(w, fmt.Sprintf("Traffic source code '%s' doesn't match '%s", aix.Appnexus.TrafficSourceCode, - andata.tags[i].trafficSourceCode), http.StatusInternalServerError) - return - } - if imp.BidFloor != andata.tags[i].reserve { - http.Error(w, fmt.Sprintf("Bid floor '%.2f' doesn't match '%.2f", imp.BidFloor, - andata.tags[i].reserve), http.StatusInternalServerError) - return - } - if imp.Banner == nil && imp.Video == nil { - http.Error(w, fmt.Sprintf("No banner or app object sent"), http.StatusInternalServerError) - return - } - if (imp.Banner == nil && andata.tags[i].mediaType == "banner") || (imp.Banner != nil && andata.tags[i].mediaType != "banner") { - http.Error(w, fmt.Sprintf("Invalid impression type - banner"), http.StatusInternalServerError) - return - } - if (imp.Video == nil && andata.tags[i].mediaType == "video") || (imp.Video != nil && andata.tags[i].mediaType != "video") { - http.Error(w, fmt.Sprintf("Invalid impression type - video"), http.StatusInternalServerError) - return - } - - if imp.Banner != nil { - if len(imp.Banner.Format) == 0 { - http.Error(w, fmt.Sprintf("Empty imp.banner.format array"), http.StatusInternalServerError) - return - } - if andata.tags[i].position == "above" && *imp.Banner.Pos != openrtb2.AdPosition(1) { - http.Error(w, fmt.Sprintf("Mismatch in position - expected 1 for atf"), http.StatusInternalServerError) - return - } - if andata.tags[i].position == "below" && *imp.Banner.Pos != openrtb2.AdPosition(3) { - http.Error(w, fmt.Sprintf("Mismatch in position - expected 3 for btf"), http.StatusInternalServerError) - return - } - } - if imp.Video != nil { - // TODO: add more validations - if len(imp.Video.MIMEs) == 0 { - http.Error(w, fmt.Sprintf("Empty imp.video.mimes array"), http.StatusInternalServerError) - return - } - if len(imp.Video.Protocols) == 0 { - http.Error(w, fmt.Sprintf("Empty imp.video.protocols array"), http.StatusInternalServerError) - return - } - for _, protocol := range imp.Video.Protocols { - if protocol < 1 || protocol > 8 { - http.Error(w, fmt.Sprintf("Invalid video protocol %d", protocol), http.StatusInternalServerError) - return - } - } - } - - resBid := openrtb2.Bid{ - ID: "random-id", - ImpID: imp.ID, - Price: andata.tags[i].bid, - AdM: andata.tags[i].content, - Ext: json.RawMessage(fmt.Sprintf(`{"appnexus":{"bid_ad_type":%d}}`, bidTypeToInt(andata.tags[i].mediaType))), - } - - if imp.Video != nil { - resBid.Attr = []openrtb2.CreativeAttribute{openrtb2.CreativeAttribute(6)} - } - resp.SeatBid[0].Bid = append(resp.SeatBid[0].Bid, resBid) - } - - // TODO: are all of these valid for app? - if breq.Site == nil { - http.Error(w, fmt.Sprintf("No site object sent"), http.StatusInternalServerError) - return - } - if breq.Site.Domain != andata.domain { - http.Error(w, fmt.Sprintf("Domain '%s' doesn't match '%s", breq.Site.Domain, andata.domain), http.StatusInternalServerError) - return - } - if breq.Site.Page != andata.page { - http.Error(w, fmt.Sprintf("Page '%s' doesn't match '%s", breq.Site.Page, andata.page), http.StatusInternalServerError) - return - } - if breq.Device.UA != andata.deviceUA { - http.Error(w, fmt.Sprintf("UA '%s' doesn't match '%s", breq.Device.UA, andata.deviceUA), http.StatusInternalServerError) - return - } - if breq.Device.IP != andata.deviceIP { - http.Error(w, fmt.Sprintf("IP '%s' doesn't match '%s", breq.Device.IP, andata.deviceIP), http.StatusInternalServerError) - return - } - if breq.User.BuyerUID != andata.buyerUID { - http.Error(w, fmt.Sprintf("User ID '%s' doesn't match '%s", breq.User.BuyerUID, andata.buyerUID), http.StatusInternalServerError) - return - } - - if andata.delay > 0 { - <-time.After(andata.delay) - } - - js, err := json.Marshal(resp) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - w.Header().Set("Content-Type", "application/json") - w.Write(js) -} - -func bidTypeToInt(bidType string) int { - switch bidType { - case "banner": - return 0 - case "video": - return 1 - case "audio": - return 2 - case "native": - return 3 - default: - return -1 - } -} -func TestAppNexusLegacyBasicResponse(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(DummyAppNexusServer)) - defer server.Close() - - andata = anBidInfo{ - domain: "nytimes.com", - page: "https://www.nytimes.com/2017/05/04/movies/guardians-of-the-galaxy-2-review-chris-pratt.html?hpw&rref=movies&action=click&pgtype=Homepage&module=well-region®ion=bottom-well&WT.nav=bottom-well&_r=0", - tags: make([]anTagInfo, 2), - deviceIP: "25.91.96.36", - deviceUA: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.1 Safari/603.1.30", - buyerUID: "23482348223", - memberID: "958", - } - andata.tags[0] = anTagInfo{ - code: "first-tag", - placementID: 8394, - bid: 1.67, - trafficSourceCode: "ppc-exchange", - content: "huh", - in_keywords: "[{ \"key\": \"genre\", \"value\": [\"jazz\", \"pop\"] }, {\"key\": \"myEmptyKey\", \"value\": []}]", - out_keywords: "genre=jazz,genre=pop,myEmptyKey", - reserve: 1.50, - position: "below", - mediaType: "banner", - } - andata.tags[1] = anTagInfo{ - code: "second-tag", - invCode: "leftbottom", - bid: 3.22, - trafficSourceCode: "taboola", - content: "yow!", - in_keywords: "[{ \"key\": \"genre\", \"value\": [\"rock\", \"pop\"] }, {\"key\": \"myKey\", \"value\": [\"myVal\"]}]", - out_keywords: "genre=rock,genre=pop,myKey=myVal", - reserve: 0.75, - position: "above", - mediaType: "video", - } - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewAppNexusLegacyAdapter(&conf, server.URL, "") - - pbin := pbs.PBSRequest{ - AdUnits: make([]pbs.AdUnit, 2), - } - for i, tag := range andata.tags { - var params json.RawMessage - if tag.placementID > 0 { - params = json.RawMessage(fmt.Sprintf("{\"placementId\": %d, \"member\": \"%s\", \"keywords\": %s, "+ - "\"trafficSourceCode\": \"%s\", \"reserve\": %.3f, \"position\": \"%s\"}", - tag.placementID, andata.memberID, tag.in_keywords, tag.trafficSourceCode, tag.reserve, tag.position)) - } else { - params = json.RawMessage(fmt.Sprintf("{\"invCode\": \"%s\", \"member\": \"%s\", \"keywords\": %s, "+ - "\"trafficSourceCode\": \"%s\", \"reserve\": %.3f, \"position\": \"%s\"}", - tag.invCode, andata.memberID, tag.in_keywords, tag.trafficSourceCode, tag.reserve, tag.position)) - } - - pbin.AdUnits[i] = pbs.AdUnit{ - Code: tag.code, - MediaTypes: []string{tag.mediaType}, - Sizes: []openrtb2.Format{ - { - W: 300, - H: 600, - }, - { - W: 300, - H: 250, - }, - }, - Bids: []pbs.Bids{ - { - BidderCode: "appnexus", - BidID: fmt.Sprintf("random-id-from-pbjs-%d", i), - Params: params, - }, - }, - } - if tag.mediaType == "video" { - pbin.AdUnits[i].Video = pbs.PBSVideo{ - Mimes: []string{"video/mp4"}, - Minduration: 15, - Maxduration: 30, - Startdelay: 5, - Skippable: 0, - PlaybackMethod: 1, - Protocols: []int8{2, 3}, - } - } - } - - body := new(bytes.Buffer) - err := json.NewEncoder(body).Encode(pbin) - if err != nil { - t.Fatalf("Json encoding failed: %v", err) - } - - req := httptest.NewRequest("POST", server.URL, body) - req.Header.Add("Referer", andata.page) - req.Header.Add("User-Agent", andata.deviceUA) - req.Header.Add("X-Real-IP", andata.deviceIP) - - pc := usersync.ParseCookieFromRequest(req, &config.HostCookie{}) - pc.TrySync("adnxs", andata.buyerUID) - fakewriter := httptest.NewRecorder() - - pc.SetCookieOnResponse(fakewriter, false, &config.HostCookie{Domain: ""}, 90*24*time.Hour) - req.Header.Add("Cookie", fakewriter.Header().Get("Set-Cookie")) - - cacheClient, _ := dummycache.New() - hcc := config.HostCookie{} - - pbReq, err := pbs.ParsePBSRequest(req, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, cacheClient, &hcc) - if err != nil { - t.Fatalf("ParsePBSRequest failed: %v", err) - } - if len(pbReq.Bidders) != 1 { - t.Fatalf("ParsePBSRequest returned %d bidders instead of 1", len(pbReq.Bidders)) - } - if pbReq.Bidders[0].BidderCode != "appnexus" { - t.Fatalf("ParsePBSRequest returned invalid bidder") - } - - ctx := context.TODO() - bids, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - if len(bids) != 2 { - t.Fatalf("Received %d bids instead of 2", len(bids)) - } - for _, bid := range bids { - matched := false - for _, tag := range andata.tags { - if bid.AdUnitCode == tag.code { - matched = true - if bid.CreativeMediaType != tag.mediaType { - t.Errorf("Incorrect Creative MediaType '%s'. Expected '%s'", bid.CreativeMediaType, tag.mediaType) - } - if bid.BidderCode != "appnexus" { - t.Errorf("Incorrect BidderCode '%s'", bid.BidderCode) - } - if bid.Price != tag.bid { - t.Errorf("Incorrect bid price '%.2f' expected '%.2f'", bid.Price, tag.bid) - } - if bid.Adm != tag.content { - t.Errorf("Incorrect bid markup '%s' expected '%s'", bid.Adm, tag.content) - } - } - } - if !matched { - t.Errorf("Received bid for unknown ad unit '%s'", bid.AdUnitCode) - } - } - - // same test but with request timing out - andata.delay = 5 * time.Millisecond - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) - defer cancel() - - bids, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err == nil { - t.Fatalf("Should have gotten a timeout error: %v", err) - } -} diff --git a/adapters/beachfront/beachfront.go b/adapters/beachfront/beachfront.go index 8af61eb9a5a..6eaf5ad9b18 100644 --- a/adapters/beachfront/beachfront.go +++ b/adapters/beachfront/beachfront.go @@ -23,7 +23,7 @@ const defaultVideoEndpoint = "https://reachms.bfmio.com/bid.json?exchange_id" const nurlVideoEndpointSuffix = "&prebidserver" const beachfrontAdapterName = "BF_PREBID_S2S" -const beachfrontAdapterVersion = "0.9.2" +const beachfrontAdapterVersion = "1.0.0" const minBidFloor = 0.01 @@ -110,7 +110,7 @@ type beachfrontVideoBidExtension struct { } func (a *BeachfrontAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - beachfrontRequests, errs := preprocess(request) + beachfrontRequests, errs := preprocess(request, reqInfo) headers := http.Header{} headers.Add("Content-Type", "application/json;charset=utf-8") @@ -140,7 +140,6 @@ func (a *BeachfrontAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo * var nurlBump = 0 var admBump = 0 - // At most, I only ever have one banner request, and it does not need the cookie, so doing it first. if len(beachfrontRequests.Banner.Slots) > 0 { bytes, err := json.Marshal(beachfrontRequests.Banner) @@ -199,7 +198,7 @@ func (a *BeachfrontAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo * return reqs, errs } -func preprocess(request *openrtb2.BidRequest) (beachfrontReqs beachfrontRequests, errs []error) { +func preprocess(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) (beachfrontReqs beachfrontRequests, errs []error) { var videoImps = make([]openrtb2.Imp, 0) var bannerImps = make([]openrtb2.Imp, 0) @@ -221,7 +220,7 @@ func preprocess(request *openrtb2.BidRequest) (beachfrontReqs beachfrontRequests if len(bannerImps) > 0 { request.Imp = bannerImps - beachfrontReqs.Banner, errs = getBannerRequest(request) + beachfrontReqs.Banner, errs = getBannerRequest(request, reqInfo) } if len(videoImps) > 0 { @@ -229,8 +228,9 @@ func preprocess(request *openrtb2.BidRequest) (beachfrontReqs beachfrontRequests var videoList []beachfrontVideoRequest request.Imp = videoImps + request.Ext = nil - videoList, videoErrs = getVideoRequests(request) + videoList, videoErrs = getVideoRequests(request, reqInfo) errs = append(errs, videoErrs...) for i := 0; i < len(videoList); i++ { @@ -269,11 +269,7 @@ func getSchain(request *openrtb2.BidRequest) (openrtb_ext.ExtRequestPrebidSChain return schain, json.Unmarshal(request.Source.Ext, &schain) } -/* -getBannerRequest, singular. A "Slot" is an "imp," and each Slot can have an AppId, so just one -request to the beachfront banner endpoint gets all banner Imps. -*/ -func getBannerRequest(request *openrtb2.BidRequest) (beachfrontBannerRequest, []error) { +func getBannerRequest(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) (beachfrontBannerRequest, []error) { var bfr beachfrontBannerRequest var errs = make([]error, 0, len(request.Imp)) @@ -289,12 +285,16 @@ func getBannerRequest(request *openrtb2.BidRequest) (beachfrontBannerRequest, [] appid, err := getAppId(beachfrontExt, openrtb_ext.BidTypeBanner) if err != nil { - // Failed to get an appid, so this request is junk. errs = append(errs, err) continue } - setBidFloor(&beachfrontExt, &request.Imp[i]) + if fatal, err := setBidFloor(&beachfrontExt, &request.Imp[i], reqInfo); err != nil { + errs = append(errs, err) + if fatal { + continue + } + } slot := beachfrontSlot{ Id: appid, @@ -392,7 +392,7 @@ func fallBackDeviceType(request *openrtb2.BidRequest) openrtb2.DeviceType { return openrtb2.DeviceTypeMobileTablet } -func getVideoRequests(request *openrtb2.BidRequest) ([]beachfrontVideoRequest, []error) { +func getVideoRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]beachfrontVideoRequest, []error) { var bfReqs = make([]beachfrontVideoRequest, len(request.Imp)) var errs = make([]error, 0, len(request.Imp)) var failedRequestIndicies = make([]int, 0) @@ -401,7 +401,6 @@ func getVideoRequests(request *openrtb2.BidRequest) ([]beachfrontVideoRequest, [ beachfrontExt, err := getBeachfrontExtension(request.Imp[i]) if err != nil { - // Failed to extract the beachfrontExt, so this request is junk. failedRequestIndicies = append(failedRequestIndicies, i) errs = append(errs, err) continue @@ -411,7 +410,6 @@ func getVideoRequests(request *openrtb2.BidRequest) ([]beachfrontVideoRequest, [ bfReqs[i].AppId = appid if err != nil { - // Failed to get an appid, so this request is junk. failedRequestIndicies = append(failedRequestIndicies, i) errs = append(errs, err) continue @@ -457,7 +455,6 @@ func getVideoRequests(request *openrtb2.BidRequest) ([]beachfrontVideoRequest, [ } if deviceCopy.DeviceType == 0 { - // More fine graned deviceType methods will be added in the future deviceCopy.DeviceType = fallBackDeviceType(request) } bfReqs[i].Request.Device = &deviceCopy @@ -467,7 +464,13 @@ func getVideoRequests(request *openrtb2.BidRequest) ([]beachfrontVideoRequest, [ imp.Banner = nil imp.Ext = nil imp.Secure = &secure - setBidFloor(&beachfrontExt, &imp) + if fatal, err := setBidFloor(&beachfrontExt, &imp, reqInfo); err != nil { + errs = append(errs, err) + if fatal { + failedRequestIndicies = append(failedRequestIndicies, i) + continue + } + } if imp.Video.H == 0 && imp.Video.W == 0 { imp.Video.W = defaultVideoWidth @@ -485,7 +488,6 @@ func getVideoRequests(request *openrtb2.BidRequest) ([]beachfrontVideoRequest, [ } - // Strip out any failed requests if len(failedRequestIndicies) > 0 { for i := 0; i < len(failedRequestIndicies); i++ { bfReqs = removeVideoElement(bfReqs, failedRequestIndicies[i]) @@ -520,8 +522,6 @@ func (a *BeachfrontAdapter) MakeBids(internalRequest *openrtb2.BidRequest, exter var errs = make([]error, 0) var xtrnal openrtb2.BidRequest - // For video, which uses RTB for the external request, this will unmarshal as expected. For banner, it will - // only get the User struct and everything else will be nil if err := json.Unmarshal(externalRequest.Body, &xtrnal); err != nil { errs = append(errs, err) } else { @@ -563,22 +563,51 @@ func (a *BeachfrontAdapter) MakeBids(internalRequest *openrtb2.BidRequest, exter return bidResponse, errs } -func setBidFloor(ext *openrtb_ext.ExtImpBeachfront, imp *openrtb2.Imp) { - var floor float64 +func setBidFloor(ext *openrtb_ext.ExtImpBeachfront, imp *openrtb2.Imp, reqInfo *adapters.ExtraRequestInfo) (bool, error) { + var initialImpBidfloor float64 = imp.BidFloor + var err error + + if imp.BidFloorCur != "" && strings.ToUpper(imp.BidFloorCur) != "USD" && imp.BidFloor > 0 { + imp.BidFloor, err = reqInfo.ConvertCurrency(imp.BidFloor, imp.BidFloorCur, "USD") - if imp.BidFloor > 0 { - floor = imp.BidFloor - } else if ext.BidFloor > 0 { - floor = ext.BidFloor - } else { - floor = minBidFloor + var convertedFromCurrency = imp.BidFloorCur + imp.BidFloorCur = "USD" + + if err != nil { + if ext.BidFloor > minBidFloor { + imp.BidFloor = ext.BidFloor + return false, &errortypes.Warning{ + Message: fmt.Sprintf("The following error was recieved from the currency converter while attempting to convert the imp.bidfloor value of %.2f from %s to USD:\n%s\nThe provided value of imp.ext.beachfront.bidfloor, %.2f USD is being used as a fallback.", + initialImpBidfloor, + convertedFromCurrency, + err, + ext.BidFloor, + ), + } + } else { + return true, &errortypes.BadInput{ + Message: fmt.Sprintf("The following error was recieved from the currency converter while attempting to convert the imp.bidfloor value of %.2f from %s to USD:\n%s\nA value of imp.ext.beachfront.bidfloor was not provided. The bid is being skipped.", + initialImpBidfloor, + convertedFromCurrency, + err, + ), + } + } + } + } + + if imp.BidFloor < ext.BidFloor { + imp.BidFloor = ext.BidFloor } - if floor <= minBidFloor { - floor = 0 + if imp.BidFloor > minBidFloor { + imp.BidFloorCur = "USD" + } else { + imp.BidFloor = 0 + imp.BidFloorCur = "" } - imp.BidFloor = floor + return false, nil } func (a *BeachfrontAdapter) getBidType(externalRequest *adapters.RequestData) openrtb_ext.BidType { diff --git a/adapters/beachfront/beachfronttest/exemplary/adm-video-app.json b/adapters/beachfront/beachfronttest/exemplary/adm-video-app.json index e20201bb35d..47507ba1cf7 100644 --- a/adapters/beachfront/beachfronttest/exemplary/adm-video-app.json +++ b/adapters/beachfront/beachfronttest/exemplary/adm-video-app.json @@ -6,7 +6,6 @@ "id": "video1", "ext": { "bidder": { - "bidfloor": 3.01, "appId": "videoAppId1" } }, @@ -43,7 +42,6 @@ "video/mp4" ] }, - "bidfloor": 3.01, "id": "video1", "secure": 0 } diff --git a/adapters/beachfront/beachfronttest/exemplary/adm-video.json b/adapters/beachfront/beachfronttest/exemplary/adm-video.json index 404c35adeeb..8fd8a4597e7 100644 --- a/adapters/beachfront/beachfronttest/exemplary/adm-video.json +++ b/adapters/beachfront/beachfronttest/exemplary/adm-video.json @@ -6,7 +6,6 @@ "id": "video1", "ext": { "bidder": { - "bidfloor": 3.01, "appId": "videoAppId1" } }, @@ -43,7 +42,6 @@ "video/mp4" ] }, - "bidfloor": 3.01, "id": "video1", "secure": 1 } diff --git a/adapters/beachfront/beachfronttest/exemplary/banner.json b/adapters/beachfront/beachfronttest/exemplary/banner.json index 11db97285a7..1707f3bbca9 100644 --- a/adapters/beachfront/beachfronttest/exemplary/banner.json +++ b/adapters/beachfront/beachfronttest/exemplary/banner.json @@ -57,7 +57,7 @@ "dnt": 0, "ua": "", "adapterName": "BF_PREBID_S2S", - "adapterVersion": "0.9.2", + "adapterVersion": "1.0.0", "user": {}, "schain": { "complete": 0, diff --git a/adapters/beachfront/beachfronttest/exemplary/nurl-video.json b/adapters/beachfront/beachfronttest/exemplary/nurl-video.json index b9b1881eb25..4a2a2d84926 100644 --- a/adapters/beachfront/beachfronttest/exemplary/nurl-video.json +++ b/adapters/beachfront/beachfronttest/exemplary/nurl-video.json @@ -7,7 +7,6 @@ "ext": { "bidder": { "videoResponseType": "nurl", - "bidfloor": 0.01, "appId": "videoAppId1" } }, diff --git a/adapters/beachfront/beachfronttest/supplemental/adm-video-app-alphanum-bundle.json b/adapters/beachfront/beachfronttest/supplemental/adm-video-app-alphanum-bundle.json index 943a4583ae9..aad4d1f59e1 100644 --- a/adapters/beachfront/beachfronttest/supplemental/adm-video-app-alphanum-bundle.json +++ b/adapters/beachfront/beachfronttest/supplemental/adm-video-app-alphanum-bundle.json @@ -6,7 +6,6 @@ "id": "video1", "ext": { "bidder": { - "bidfloor": 3.01, "appId": "videoAppId1" } }, @@ -43,7 +42,6 @@ "video/mp4" ] }, - "bidfloor": 3.01, "id": "video1", "secure": 0 } diff --git a/adapters/beachfront/beachfronttest/supplemental/adm-video-app-malformed-bundle.json b/adapters/beachfront/beachfronttest/supplemental/adm-video-app-malformed-bundle.json index 9d0ca24ab8b..6d2b3a120b1 100644 --- a/adapters/beachfront/beachfronttest/supplemental/adm-video-app-malformed-bundle.json +++ b/adapters/beachfront/beachfronttest/supplemental/adm-video-app-malformed-bundle.json @@ -6,7 +6,6 @@ "id": "video1", "ext": { "bidder": { - "bidfloor": 3.01, "appId": "videoAppId1" } }, @@ -43,7 +42,6 @@ "video/mp4" ] }, - "bidfloor": 3.01, "id": "video1", "secure": 0 } diff --git a/adapters/beachfront/beachfronttest/supplemental/adm-video-by-explicit.json b/adapters/beachfront/beachfronttest/supplemental/adm-video-by-explicit.json index 25b4a1c4456..7855639cd80 100644 --- a/adapters/beachfront/beachfronttest/supplemental/adm-video-by-explicit.json +++ b/adapters/beachfront/beachfronttest/supplemental/adm-video-by-explicit.json @@ -7,7 +7,6 @@ "ext": { "bidder": { "videoResponseType": "adm", - "bidfloor": 3.01, "appId": "videoAppId1" } }, @@ -44,7 +43,6 @@ "video/mp4" ] }, - "bidfloor": 3.01, "id": "video1", "secure": 1 } diff --git a/adapters/beachfront/beachfronttest/supplemental/adm-video-no-cat.json b/adapters/beachfront/beachfronttest/supplemental/adm-video-no-cat.json index edde1301dcc..2e6bbc5d56a 100644 --- a/adapters/beachfront/beachfronttest/supplemental/adm-video-no-cat.json +++ b/adapters/beachfront/beachfronttest/supplemental/adm-video-no-cat.json @@ -6,7 +6,6 @@ "id": "video1", "ext": { "bidder": { - "bidfloor": 3.01, "appId": "videoAppId1" } }, @@ -43,7 +42,6 @@ "video/mp4" ] }, - "bidfloor": 3.01, "id": "video1", "secure": 1 } diff --git a/adapters/beachfront/beachfronttest/supplemental/adm-video-no-ip.json b/adapters/beachfront/beachfronttest/supplemental/adm-video-no-ip.json index d2cc2bc9ad3..0fe36fafc76 100644 --- a/adapters/beachfront/beachfronttest/supplemental/adm-video-no-ip.json +++ b/adapters/beachfront/beachfronttest/supplemental/adm-video-no-ip.json @@ -6,7 +6,6 @@ "id": "video1", "ext": { "bidder": { - "bidfloor": 3.01, "appId": "videoAppId1" } }, @@ -40,7 +39,6 @@ "video/mp4" ] }, - "bidfloor": 3.01, "id": "video1", "secure": 1 } diff --git a/adapters/beachfront/beachfronttest/supplemental/adm-video-schain.json b/adapters/beachfront/beachfronttest/supplemental/adm-video-schain.json index f249da4722e..56565acc8a7 100644 --- a/adapters/beachfront/beachfronttest/supplemental/adm-video-schain.json +++ b/adapters/beachfront/beachfronttest/supplemental/adm-video-schain.json @@ -6,7 +6,6 @@ "id": "video1", "ext": { "bidder": { - "bidfloor": 3.01, "appId": "videoAppId1" } }, @@ -60,7 +59,6 @@ "video/mp4" ] }, - "bidfloor": 3.01, "id": "video1", "secure": 1 } diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-204-with-body.json b/adapters/beachfront/beachfronttest/supplemental/banner-204-with-body.json index af165c2600b..d9a2d14fe32 100644 --- a/adapters/beachfront/beachfronttest/supplemental/banner-204-with-body.json +++ b/adapters/beachfront/beachfronttest/supplemental/banner-204-with-body.json @@ -58,7 +58,7 @@ "dnt": 0, "ua": "", "adapterName": "BF_PREBID_S2S", - "adapterVersion": "0.9.2", + "adapterVersion": "1.0.0", "user": { }, "schain": { diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-204.json b/adapters/beachfront/beachfronttest/supplemental/banner-204.json index 1c00f52c258..eb24f592b02 100644 --- a/adapters/beachfront/beachfronttest/supplemental/banner-204.json +++ b/adapters/beachfront/beachfronttest/supplemental/banner-204.json @@ -58,7 +58,7 @@ "dnt": 0, "ua": "", "adapterName": "BF_PREBID_S2S", - "adapterVersion": "0.9.2", + "adapterVersion": "1.0.0", "user": { }, "schain": { diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video-by-explicit.json b/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video-by-explicit.json index 283ba761fb9..57853983dfa 100644 --- a/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video-by-explicit.json +++ b/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video-by-explicit.json @@ -7,7 +7,6 @@ "ext": { "bidder": { "videoResponseType": "adm", - "bidfloor": 0.41, "appIds": { "banner": "bannerAppId1", "video": "videoAppId1" @@ -48,8 +47,8 @@ "slots": [ { "slot": "mix1", + "bidfloor": 0, "id": "bannerAppId1", - "bidfloor": 0.41, "sizes": [ { "w": 300, @@ -72,7 +71,7 @@ "dnt": 0, "user": {}, "adapterName": "BF_PREBID_S2S", - "adapterVersion": "0.9.2", + "adapterVersion": "1.0.0", "requestId": "banner-and-video", "schain": { "complete": 0, @@ -109,7 +108,6 @@ "video/mp4" ] }, - "bidfloor": 0.41, "secure": 1, "id": "mix1" } diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video-expected-204-response-on-banner.json b/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video-expected-204-response-on-banner.json index 3026c1338af..9b0f1863d46 100644 --- a/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video-expected-204-response-on-banner.json +++ b/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video-expected-204-response-on-banner.json @@ -6,7 +6,6 @@ "id": "mix1", "ext": { "bidder": { - "bidfloor": 0.41, "appIds": { "banner": "bannerAppId1", "video": "videoAppId1" @@ -48,7 +47,7 @@ { "slot": "mix1", "id": "bannerAppId1", - "bidfloor": 0.41, + "bidfloor": 0, "sizes": [ { "w": 300, @@ -71,7 +70,7 @@ "dnt": 0, "user": {}, "adapterName": "BF_PREBID_S2S", - "adapterVersion": "0.9.2", + "adapterVersion": "1.0.0", "requestId": "banner-and-video", "schain": { "complete": 0, @@ -99,7 +98,6 @@ "video/mp4" ] }, - "bidfloor": 0.41, "secure": 1, "id": "mix1" } diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video.json b/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video.json index 73aa7df0ac8..95073918079 100644 --- a/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video.json +++ b/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video.json @@ -6,7 +6,6 @@ "id": "mix1", "ext": { "bidder": { - "bidfloor": 0.41, "appIds": { "banner": "bannerAppId1", "video": "videoAppId1" @@ -48,7 +47,7 @@ { "slot": "mix1", "id": "bannerAppId1", - "bidfloor": 0.41, + "bidfloor": 0, "sizes": [ { "w": 300, @@ -71,7 +70,7 @@ "dnt": 0, "user": {}, "adapterName": "BF_PREBID_S2S", - "adapterVersion": "0.9.2", + "adapterVersion": "1.0.0", "requestId": "banner-and-video", "schain": { "complete": 0, @@ -108,7 +107,6 @@ "video/mp4" ] }, - "bidfloor": 0.41, "secure": 1, "id": "mix1" } diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-and-nurl-video.json b/adapters/beachfront/beachfronttest/supplemental/banner-and-nurl-video.json index f1e06544890..a05edc4000a 100644 --- a/adapters/beachfront/beachfronttest/supplemental/banner-and-nurl-video.json +++ b/adapters/beachfront/beachfronttest/supplemental/banner-and-nurl-video.json @@ -7,7 +7,6 @@ "ext": { "bidder": { "videoResponseType": "nurl", - "bidfloor": 0.41, "appIds": { "banner": "bannerAppId1", "video": "videoAppId1" @@ -46,7 +45,7 @@ { "slot": "mix1", "id": "bannerAppId1", - "bidfloor": 0.41, + "bidfloor": 0, "sizes": [ { "w": 300, @@ -69,7 +68,7 @@ "dnt": 0, "user": {}, "adapterName": "BF_PREBID_S2S", - "adapterVersion": "0.9.2", + "adapterVersion": "1.0.0", "requestId": "banner-and-video", "schain": { "complete": 0, @@ -106,7 +105,6 @@ "video/mp4" ] }, - "bidfloor": 0.41, "secure": 1, "id": "mix1" } diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-bad-request-400.json b/adapters/beachfront/beachfronttest/supplemental/banner-bad-request-400.json index 7463e2bf374..1e54fea6183 100644 --- a/adapters/beachfront/beachfronttest/supplemental/banner-bad-request-400.json +++ b/adapters/beachfront/beachfronttest/supplemental/banner-bad-request-400.json @@ -6,7 +6,6 @@ }, "imp": [ { - "bidfloor": 0.02, "banner": { "format": [ { @@ -17,7 +16,6 @@ }, "ext": { "bidder": { - "bidfloor": 5.02, "appId": "dudAppId" } } @@ -34,7 +32,7 @@ { "slot": "", "id": "dudAppId", - "bidfloor": 0.02, + "bidfloor": 0, "sizes": [ { "w": 300, @@ -57,7 +55,7 @@ "dnt": 0, "ua": "", "adapterName": "BF_PREBID_S2S", - "adapterVersion": "0.9.2", + "adapterVersion": "1.0.0", "user": {}, "schain": { "complete": 0, diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-schain.json b/adapters/beachfront/beachfronttest/supplemental/banner-schain.json index 4856e3e9982..b2cede7afb1 100644 --- a/adapters/beachfront/beachfronttest/supplemental/banner-schain.json +++ b/adapters/beachfront/beachfronttest/supplemental/banner-schain.json @@ -74,7 +74,7 @@ "dnt": 0, "ua": "", "adapterName": "BF_PREBID_S2S", - "adapterVersion": "0.9.2", + "adapterVersion": "1.0.0", "user": {}, "schain": { "complete": 1, diff --git a/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_adm_video.json b/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_adm_video.json index 51c676f5076..fe558c80247 100644 --- a/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_adm_video.json +++ b/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_adm_video.json @@ -6,7 +6,6 @@ "id": "video1", "ext": { "bidder": { - "bidfloor": 3.01, "appId": "videoAppId1" } }, @@ -43,7 +42,6 @@ "video/mp4" ] }, - "bidfloor": 3.01, "id": "video1", "secure": 1 } diff --git a/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_banner.json b/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_banner.json index dc709071ac2..9b83e0d39e3 100644 --- a/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_banner.json +++ b/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_banner.json @@ -6,7 +6,6 @@ }, "imp": [ { - "bidfloor": 0.02, "banner": { "format": [ { @@ -17,7 +16,6 @@ }, "ext": { "bidder": { - "bidfloor": 0.02, "appId": "bannerAppId1" } } @@ -33,7 +31,7 @@ { "slot": "", "id": "bannerAppId1", - "bidfloor": 0.02, + "bidfloor": 0, "sizes": [ { "w": 300, @@ -56,7 +54,7 @@ "dnt": 0, "ua": "", "adapterName": "BF_PREBID_S2S", - "adapterVersion": "0.9.2", + "adapterVersion": "1.0.0", "user": {}, "schain": { "complete": 0, diff --git a/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_nurl_video.json b/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_nurl_video.json index 7990b0a1bb1..ff467f7b317 100644 --- a/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_nurl_video.json +++ b/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_nurl_video.json @@ -6,7 +6,6 @@ "id": "video1", "ext": { "bidder": { - "bidfloor": 3.01, "appId": "videoAppId1", "videoResponseType": "nurl" } @@ -44,7 +43,6 @@ "video/mp4" ] }, - "bidfloor": 3.01, "id": "video1", "secure": 1 } diff --git a/adapters/beachfront/beachfronttest/supplemental/bidfloor-four-variations-on-nothing-adm-video.json b/adapters/beachfront/beachfronttest/supplemental/bidfloor-four-variations-on-nothing-adm-video.json deleted file mode 100644 index 147efb78332..00000000000 --- a/adapters/beachfront/beachfronttest/supplemental/bidfloor-four-variations-on-nothing-adm-video.json +++ /dev/null @@ -1,436 +0,0 @@ -{ - "mockBidRequest": { - "id": "adm-video", - "imp": [ - { - "id": "video1", - "ext": { - "bidder": { - "bidfloor": 0.01, - "appId": "videoAppId1" - } - }, - "video": { - "mimes": [ - "video/mp4" - ], - "context": "instream", - "w": 300, - "h": 250 - } - }, { - "id": "video2", - "bidfloor": 0.01, - "ext": { - "bidder": { - "appId": "videoAppId1" - } - }, - "video": { - "mimes": [ - "video/mp4" - ], - "context": "instream", - "w": 300, - "h": 250 - } - }, { - "id": "video3", - "ext": { - "bidder": { - "appId": "videoAppId1" - } - }, - "video": { - "mimes": [ - "video/mp4" - ], - "context": "instream", - "w": 300, - "h": 250 - } - }, { - "id": "video4", - "bidfloor": 0.01, - "ext": { - "bidder": { - "bidfloor": 10.01, - "appId": "videoAppId1" - } - }, - "video": { - "mimes": [ - "video/mp4" - ], - "context": "instream", - "w": 300, - "h": 250 - } - } - ], - "site": { - "page": "https://some.domain.us/some/page.html" - }, - "device":{ - "ip":"192.168.168.168" - } - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://qa.beachrtb.com/bid.json?exchange_id=videoAppId1", - "body": { - "id": "adm-video", - "imp": [ - { - "video": { - "w": 300, - "h": 250, - "mimes": [ - "video/mp4" - ] - }, - "id": "video1", - "secure": 1 - } - ], - "site": { - "page": "https://some.domain.us/some/page.html", - "domain": "some.domain.us" - }, - "cur": [ - "USD" - ], - "device":{ - "devicetype": 2, - "ip":"192.168.168.168" - } - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "adm-video", - "seatBid": [ - { - "bid": [ - { - "id": "5fd7c8a6ff2f1f0d42ee6427", - "impid": "video1", - "price": 20, - "adm": "http://example.com/vast.xml", - "adid": "1088", - "adomain": [ - "beachfront.io" - ], - "cid": "277", - "crid": "532", - "cat":["IAB2"], - "w": 300, - "h": 250, - "ext": { - "duration": 30 - } - } - ], - "seat": "bfio-s-1" - } - ] - } - } - }, { - "expectedRequest": { - "uri": "https://qa.beachrtb.com/bid.json?exchange_id=videoAppId1", - "body": { - "id": "adm-video", - "imp": [ - { - "video": { - "w": 300, - "h": 250, - "mimes": [ - "video/mp4" - ] - }, - "id": "video2", - "secure": 1 - } - ], - "site": { - "page": "https://some.domain.us/some/page.html", - "domain": "some.domain.us" - }, - "cur": [ - "USD" - ], - "device":{ - "devicetype": 2, - "ip":"192.168.168.168" - } - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "adm-video", - "seatBid": [ - { - "bid": [ - { - "id": "5fd7c8a6ff2f1f0d42ee6427", - "impid": "video2", - "price": 20, - "adm": "http://example.com/vast.xml", - "adid": "1088", - "adomain": [ - "beachfront.io" - ], - "cid": "277", - "crid": "532", - "cat":["IAB2"], - "w": 300, - "h": 250, - "ext": { - "duration": 30 - } - } - ], - "seat": "bfio-s-1" - } - ] - } - } - }, { - "expectedRequest": { - "uri": "https://qa.beachrtb.com/bid.json?exchange_id=videoAppId1", - "body": { - "id": "adm-video", - "imp": [ - { - "video": { - "w": 300, - "h": 250, - "mimes": [ - "video/mp4" - ] - }, - "id": "video3", - "secure": 1 - } - ], - "site": { - "page": "https://some.domain.us/some/page.html", - "domain": "some.domain.us" - }, - "cur": [ - "USD" - ], - "device":{ - "devicetype": 2, - "ip":"192.168.168.168" - } - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "adm-video", - "seatBid": [ - { - "bid": [ - { - "id": "5fd7c8a6ff2f1f0d42ee6427", - "impid": "video3", - "price": 20, - "adm": "http://example.com/vast.xml", - "adid": "1088", - "adomain": [ - "beachfront.io" - ], - "cid": "277", - "crid": "532", - "cat":["IAB2"], - "w": 300, - "h": 250, - "ext": { - "duration": 30 - } - } - ], - "seat": "bfio-s-1" - } - ] - } - } - }, { - "expectedRequest": { - "uri": "https://qa.beachrtb.com/bid.json?exchange_id=videoAppId1", - "body": { - "id": "adm-video", - "imp": [ - { - "video": { - "w": 300, - "h": 250, - "mimes": [ - "video/mp4" - ] - }, - "id": "video4", - "secure": 1 - } - ], - "site": { - "page": "https://some.domain.us/some/page.html", - "domain": "some.domain.us" - }, - "cur": [ - "USD" - ], - "device":{ - "devicetype": 2, - "ip":"192.168.168.168" - } - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "adm-video", - "seatBid": [ - { - "bid": [ - { - "id": "5fd7c8a6ff2f1f0d42ee6427", - "impid": "video4", - "price": 20, - "adm": "http://example.com/vast.xml", - "adid": "1088", - "adomain": [ - "beachfront.io" - ], - "cid": "277", - "crid": "532", - "cat":["IAB2"], - "w": 300, - "h": 250, - "ext": { - "duration": 30 - } - } - ], - "seat": "bfio-s-1" - } - ] - } - } - - } - ], - - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "video1AdmVideo", - "impid": "video1", - "price": 20, - "adm": "http://example.com/vast.xml", - "adid": "1088", - "adomain": [ - "beachfront.io" - ], - "cid": "277", - "crid": "532", - "cat":["IAB2"], - "w": 300, - "h": 250, - "ext": { - "duration": 30 - } - }, - "type": "video" - } - ] - }, { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "video2AdmVideo", - "impid": "video2", - "price": 20, - "adm": "http://example.com/vast.xml", - "adid": "1088", - "adomain": [ - "beachfront.io" - ], - "cid": "277", - "crid": "532", - "cat":["IAB2"], - "w": 300, - "h": 250, - "ext": { - "duration": 30 - } - }, - "type": "video" - } - ] - }, { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "video3AdmVideo", - "impid": "video3", - "price": 20, - "adm": "http://example.com/vast.xml", - "adid": "1088", - "adomain": [ - "beachfront.io" - ], - "cid": "277", - "crid": "532", - "cat":["IAB2"], - "w": 300, - "h": 250, - "ext": { - "duration": 30 - } - }, - "type": "video" - } - ] - }, { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "video4AdmVideo", - "impid": "video4", - "price": 20, - "adm": "http://example.com/vast.xml", - "adid": "1088", - "adomain": [ - "beachfront.io" - ], - "cid": "277", - "crid": "532", - "cat":["IAB2"], - "w": 300, - "h": 250, - "ext": { - "duration": 30 - } - }, - "type": "video" - } - ] - - } - ] -} diff --git a/adapters/beachfront/beachfronttest/supplemental/currency-adm-video-converted.json b/adapters/beachfront/beachfronttest/supplemental/currency-adm-video-converted.json new file mode 100644 index 00000000000..ba361ef445f --- /dev/null +++ b/adapters/beachfront/beachfronttest/supplemental/currency-adm-video-converted.json @@ -0,0 +1,145 @@ +{ + "mockBidRequest": { + "id": "adm-video", + "imp": [ + { + "id": "video1", + "bidfloor": 1.80, + "bidfloorcur": "EUR", + "ext": { + "bidder": { + "bidfloor": 1.98, + "appId": "videoAppId1" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "context": "instream", + "w": 300, + "h": 250 + } + } + ], + "site": { + "page": "https://some.domain.us/some/page.html" + }, + "device":{ + "ip":"192.168.168.168" + }, + "ext": { + "prebid": { + "currency": { + "rates": { + "MXN":{ + "USD": 0.05, + "EUR": 0.03 + }, + "EUR": { + "USD": 1.20 + } + }, + "usepbsrates": false + } + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://qa.beachrtb.com/bid.json?exchange_id=videoAppId1", + "body": { + "id": "adm-video", + "imp": [ + { + "bidfloor": 2.16, + "bidfloorcur": "USD", + "video": { + "w": 300, + "h": 250, + "mimes": [ + "video/mp4" + ] + }, + "id": "video1", + "secure": 1 + } + ], + "site": { + "page": "https://some.domain.us/some/page.html", + "domain": "some.domain.us" + }, + "cur": [ + "USD" + ], + "device":{ + "devicetype": 2, + "ip":"192.168.168.168" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "adm-video", + "seatBid": [ + { + "bid": [ + { + "id": "5fd7c8a6ff2f1f0d42ee6427", + "impid": "video1", + "price": 20, + "adm": "http://example.com/vast.xml", + "adid": "1088", + "adomain": [ + "beachfront.io" + ], + "cat":["IAB2"], + "cid": "277", + "crid": "532", + "w": 300, + "h": 250, + "ext": { + "duration": 30 + } + } + ], + "seat": "bfio-s-1" + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "video1AdmVideo", + "impid": "video1", + "price": 20, + "adm": "http://example.com/vast.xml", + "adid": "1088", + "adomain": [ + "beachfront.io" + ], + "cid": "277", + "crid": "532", + "cat":["IAB2"], + "w": 300, + "h": 250, + "ext": { + "duration": 30 + } + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/beachfront/beachfronttest/supplemental/currency-adm-video-ext-wins.json b/adapters/beachfront/beachfronttest/supplemental/currency-adm-video-ext-wins.json new file mode 100644 index 00000000000..b0bf84fa7b4 --- /dev/null +++ b/adapters/beachfront/beachfronttest/supplemental/currency-adm-video-ext-wins.json @@ -0,0 +1,128 @@ +{ + "mockBidRequest": { + "id": "adm-video", + "imp": [ + { + "id": "video1", + "bidfloor": 1.90, + "ext": { + "bidder": { + "bidfloor": 2.90, + "appId": "videoAppId1" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "context": "instream", + "w": 300, + "h": 250 + } + } + ], + "site": { + "page": "https://some.domain.us/some/page.html" + }, + "device":{ + "ip":"192.168.168.168" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://qa.beachrtb.com/bid.json?exchange_id=videoAppId1", + "body": { + "id": "adm-video", + "imp": [ + { + "bidfloor": 2.90, + "bidfloorcur": "USD", + "video": { + "w": 300, + "h": 250, + "mimes": [ + "video/mp4" + ] + }, + "id": "video1", + "secure": 1 + } + ], + "site": { + "page": "https://some.domain.us/some/page.html", + "domain": "some.domain.us" + }, + "cur": [ + "USD" + ], + "device":{ + "devicetype": 2, + "ip":"192.168.168.168" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "adm-video", + "seatBid": [ + { + "bid": [ + { + "id": "5fd7c8a6ff2f1f0d42ee6427", + "impid": "video1", + "price": 20, + "adm": "http://example.com/vast.xml", + "adid": "1088", + "adomain": [ + "beachfront.io" + ], + "cat":["IAB2"], + "cid": "277", + "crid": "532", + "w": 300, + "h": 250, + "ext": { + "duration": 30 + } + } + ], + "seat": "bfio-s-1" + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "video1AdmVideo", + "impid": "video1", + "price": 20, + "adm": "http://example.com/vast.xml", + "adid": "1088", + "adomain": [ + "beachfront.io" + ], + "cid": "277", + "crid": "532", + "cat":["IAB2"], + "w": 300, + "h": 250, + "ext": { + "duration": 30 + } + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/beachfront/beachfronttest/supplemental/currency-adm-video-imp-wins.json b/adapters/beachfront/beachfronttest/supplemental/currency-adm-video-imp-wins.json new file mode 100644 index 00000000000..379fe1965e7 --- /dev/null +++ b/adapters/beachfront/beachfronttest/supplemental/currency-adm-video-imp-wins.json @@ -0,0 +1,128 @@ +{ + "mockBidRequest": { + "id": "adm-video", + "imp": [ + { + "id": "video1", + "bidfloor": 2.01, + "ext": { + "bidder": { + "bidfloor": 1.90, + "appId": "videoAppId1" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "context": "instream", + "w": 300, + "h": 250 + } + } + ], + "site": { + "page": "https://some.domain.us/some/page.html" + }, + "device":{ + "ip":"192.168.168.168" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://qa.beachrtb.com/bid.json?exchange_id=videoAppId1", + "body": { + "id": "adm-video", + "imp": [ + { + "bidfloor": 2.01, + "bidfloorcur": "USD", + "video": { + "w": 300, + "h": 250, + "mimes": [ + "video/mp4" + ] + }, + "id": "video1", + "secure": 1 + } + ], + "site": { + "page": "https://some.domain.us/some/page.html", + "domain": "some.domain.us" + }, + "cur": [ + "USD" + ], + "device":{ + "devicetype": 2, + "ip":"192.168.168.168" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "adm-video", + "seatBid": [ + { + "bid": [ + { + "id": "5fd7c8a6ff2f1f0d42ee6427", + "impid": "video1", + "price": 20, + "adm": "http://example.com/vast.xml", + "adid": "1088", + "adomain": [ + "beachfront.io" + ], + "cat":["IAB2"], + "cid": "277", + "crid": "532", + "w": 300, + "h": 250, + "ext": { + "duration": 30 + } + } + ], + "seat": "bfio-s-1" + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "video1AdmVideo", + "impid": "video1", + "price": 20, + "adm": "http://example.com/vast.xml", + "adid": "1088", + "adomain": [ + "beachfront.io" + ], + "cid": "277", + "crid": "532", + "cat":["IAB2"], + "w": 300, + "h": 250, + "ext": { + "duration": 30 + } + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/beachfront/beachfronttest/supplemental/currency-adm-video-warning-and-fallback.json b/adapters/beachfront/beachfronttest/supplemental/currency-adm-video-warning-and-fallback.json new file mode 100644 index 00000000000..71b3161b894 --- /dev/null +++ b/adapters/beachfront/beachfronttest/supplemental/currency-adm-video-warning-and-fallback.json @@ -0,0 +1,151 @@ +{ + "mockBidRequest": { + "id": "adm-video", + "imp": [ + { + "id": "video1", + "bidfloor": 1.90, + "bidfloorcur": "XYZ", + "ext": { + "bidder": { + "bidfloor": 1.20, + "appId": "videoAppId1" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "context": "instream", + "w": 300, + "h": 250 + } + } + ], + "site": { + "page": "https://some.domain.us/some/page.html" + }, + "device":{ + "ip":"192.168.168.168" + }, + "ext": { + "prebid": { + "currency": { + "rates": { + "MXN":{ + "USD": 0.05, + "EUR": 0.03 + }, + "EUR": { + "USD": 1.20 + } + }, + "usepbsrates": false + } + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://qa.beachrtb.com/bid.json?exchange_id=videoAppId1", + "body": { + "id": "adm-video", + "imp": [ + { + "bidfloor": 1.20, + "bidfloorcur": "USD", + "video": { + "w": 300, + "h": 250, + "mimes": [ + "video/mp4" + ] + }, + "id": "video1", + "secure": 1 + } + ], + "site": { + "page": "https://some.domain.us/some/page.html", + "domain": "some.domain.us" + }, + "cur": [ + "USD" + ], + "device":{ + "devicetype": 2, + "ip":"192.168.168.168" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "adm-video", + "seatBid": [ + { + "bid": [ + { + "id": "5fd7c8a6ff2f1f0d42ee6427", + "impid": "video1", + "price": 20, + "adm": "http://example.com/vast.xml", + "adid": "1088", + "adomain": [ + "beachfront.io" + ], + "cat":["IAB2"], + "cid": "277", + "crid": "532", + "w": 300, + "h": 250, + "ext": { + "duration": 30 + } + } + ], + "seat": "bfio-s-1" + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "video1AdmVideo", + "impid": "video1", + "price": 20, + "adm": "http://example.com/vast.xml", + "adid": "1088", + "adomain": [ + "beachfront.io" + ], + "cid": "277", + "crid": "532", + "cat":["IAB2"], + "w": 300, + "h": 250, + "ext": { + "duration": 30 + } + }, + "type": "video" + } + ] + } + ], + "expectedMakeRequestsErrors": [ + { + "value": "The following error was recieved from the currency converter while attempting to convert the imp.bidfloor value of 1.90 from XYZ to USD:\ncurrency: tag is not a recognized currency\nThe provided value of imp.ext.beachfront.bidfloor, 1.20 USD is being used as a fallback.", + "comparison": "literal" + } + ] +} diff --git a/adapters/beachfront/beachfronttest/supplemental/currency-adm-video-warning-no-fallback.json b/adapters/beachfront/beachfronttest/supplemental/currency-adm-video-warning-no-fallback.json new file mode 100644 index 00000000000..8f611156cd4 --- /dev/null +++ b/adapters/beachfront/beachfronttest/supplemental/currency-adm-video-warning-no-fallback.json @@ -0,0 +1,55 @@ +{ + "mockBidRequest": { + "id": "adm-video", + "imp": [ + { + "id": "video1", + "bidfloor": 1.90, + "bidfloorcur": "XYZ", + "ext": { + "bidder": { + "appId": "videoAppId1" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "context": "instream", + "w": 300, + "h": 250 + } + } + ], + "site": { + "page": "https://some.domain.us/some/page.html" + }, + "device":{ + "ip":"192.168.168.168" + }, + "ext": { + "prebid": { + "currency": { + "rates": { + "MXN":{ + "USD": 0.05, + "EUR": 0.03 + }, + "EUR": { + "USD": 1.20 + } + }, + "usepbsrates": false + } + } + } + }, + + "expectedMakeRequestsErrors": [ + { + "value": "The following error was recieved from the currency converter while attempting to convert the imp.bidfloor value of 1.90 from XYZ to USD:\ncurrency: tag is not a recognized currency\nA value of imp.ext.beachfront.bidfloor was not provided. The bid is being skipped.", + "comparison": "literal" + } + ] + +} diff --git a/adapters/beachfront/beachfronttest/supplemental/currency-adm-video-warning-zeroed-fallback.json b/adapters/beachfront/beachfronttest/supplemental/currency-adm-video-warning-zeroed-fallback.json new file mode 100644 index 00000000000..8f611156cd4 --- /dev/null +++ b/adapters/beachfront/beachfronttest/supplemental/currency-adm-video-warning-zeroed-fallback.json @@ -0,0 +1,55 @@ +{ + "mockBidRequest": { + "id": "adm-video", + "imp": [ + { + "id": "video1", + "bidfloor": 1.90, + "bidfloorcur": "XYZ", + "ext": { + "bidder": { + "appId": "videoAppId1" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "context": "instream", + "w": 300, + "h": 250 + } + } + ], + "site": { + "page": "https://some.domain.us/some/page.html" + }, + "device":{ + "ip":"192.168.168.168" + }, + "ext": { + "prebid": { + "currency": { + "rates": { + "MXN":{ + "USD": 0.05, + "EUR": 0.03 + }, + "EUR": { + "USD": 1.20 + } + }, + "usepbsrates": false + } + } + } + }, + + "expectedMakeRequestsErrors": [ + { + "value": "The following error was recieved from the currency converter while attempting to convert the imp.bidfloor value of 1.90 from XYZ to USD:\ncurrency: tag is not a recognized currency\nA value of imp.ext.beachfront.bidfloor was not provided. The bid is being skipped.", + "comparison": "literal" + } + ] + +} diff --git a/adapters/beachfront/beachfronttest/supplemental/bidfloor-test-ext-wins-adm-video.json b/adapters/beachfront/beachfronttest/supplemental/currency-adm-video-zero-by-min-both.json similarity index 95% rename from adapters/beachfront/beachfronttest/supplemental/bidfloor-test-ext-wins-adm-video.json rename to adapters/beachfront/beachfronttest/supplemental/currency-adm-video-zero-by-min-both.json index eed3da992d6..c4d85323ada 100644 --- a/adapters/beachfront/beachfronttest/supplemental/bidfloor-test-ext-wins-adm-video.json +++ b/adapters/beachfront/beachfronttest/supplemental/currency-adm-video-zero-by-min-both.json @@ -4,9 +4,10 @@ "imp": [ { "id": "video1", + "bidfloor": 0.01, "ext": { "bidder": { - "bidfloor": 3.01, + "bidfloor": 0.01, "appId": "videoAppId1" } }, @@ -43,7 +44,6 @@ "video/mp4" ] }, - "bidfloor": 3.01, "id": "video1", "secure": 1 } @@ -71,15 +71,15 @@ { "id": "5fd7c8a6ff2f1f0d42ee6427", "impid": "video1", - "price": 200, + "price": 20, "adm": "http://example.com/vast.xml", "adid": "1088", "adomain": [ "beachfront.io" ], + "cat":["IAB2"], "cid": "277", "crid": "532", - "cat":["IAB2"], "w": 300, "h": 250, "ext": { @@ -103,7 +103,7 @@ "bid": { "id": "video1AdmVideo", "impid": "video1", - "price": 200, + "price": 20, "adm": "http://example.com/vast.xml", "adid": "1088", "adomain": [ diff --git a/adapters/beachfront/beachfronttest/supplemental/bidfloor-test-imp-wins-adm-video.json b/adapters/beachfront/beachfronttest/supplemental/currency-adm-video-zero-by-min-ext.json similarity index 94% rename from adapters/beachfront/beachfronttest/supplemental/bidfloor-test-imp-wins-adm-video.json rename to adapters/beachfront/beachfronttest/supplemental/currency-adm-video-zero-by-min-ext.json index edc63f2f5ee..f4ac4e041c2 100644 --- a/adapters/beachfront/beachfronttest/supplemental/bidfloor-test-imp-wins-adm-video.json +++ b/adapters/beachfront/beachfronttest/supplemental/currency-adm-video-zero-by-min-ext.json @@ -4,10 +4,9 @@ "imp": [ { "id": "video1", - "bidfloor": 45.21, "ext": { "bidder": { - "bidfloor": 103.01, + "bidfloor": 0.01, "appId": "videoAppId1" } }, @@ -44,7 +43,6 @@ "video/mp4" ] }, - "bidfloor": 45.21, "id": "video1", "secure": 1 } @@ -72,15 +70,15 @@ { "id": "5fd7c8a6ff2f1f0d42ee6427", "impid": "video1", - "price": 200, + "price": 20, "adm": "http://example.com/vast.xml", "adid": "1088", "adomain": [ "beachfront.io" ], + "cat":["IAB2"], "cid": "277", "crid": "532", - "cat":["IAB2"], "w": 300, "h": 250, "ext": { @@ -104,7 +102,7 @@ "bid": { "id": "video1AdmVideo", "impid": "video1", - "price": 200, + "price": 20, "adm": "http://example.com/vast.xml", "adid": "1088", "adomain": [ diff --git a/adapters/beachfront/beachfronttest/supplemental/currency-adm-video-zero-by-min-imp.json b/adapters/beachfront/beachfronttest/supplemental/currency-adm-video-zero-by-min-imp.json new file mode 100644 index 00000000000..a574e99b7c6 --- /dev/null +++ b/adapters/beachfront/beachfronttest/supplemental/currency-adm-video-zero-by-min-imp.json @@ -0,0 +1,125 @@ +{ + "mockBidRequest": { + "id": "adm-video", + "imp": [ + { + "id": "video1", + "bidfloor": 0.01, + "ext": { + "bidder": { + "appId": "videoAppId1" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "context": "instream", + "w": 300, + "h": 250 + } + } + ], + "site": { + "page": "https://some.domain.us/some/page.html" + }, + "device":{ + "ip":"192.168.168.168" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://qa.beachrtb.com/bid.json?exchange_id=videoAppId1", + "body": { + "id": "adm-video", + "imp": [ + { + "video": { + "w": 300, + "h": 250, + "mimes": [ + "video/mp4" + ] + }, + "id": "video1", + "secure": 1 + } + ], + "site": { + "page": "https://some.domain.us/some/page.html", + "domain": "some.domain.us" + }, + "cur": [ + "USD" + ], + "device":{ + "devicetype": 2, + "ip":"192.168.168.168" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "adm-video", + "seatBid": [ + { + "bid": [ + { + "id": "5fd7c8a6ff2f1f0d42ee6427", + "impid": "video1", + "price": 20, + "adm": "http://example.com/vast.xml", + "adid": "1088", + "adomain": [ + "beachfront.io" + ], + "cat":["IAB2"], + "cid": "277", + "crid": "532", + "w": 300, + "h": 250, + "ext": { + "duration": 30 + } + } + ], + "seat": "bfio-s-1" + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "video1AdmVideo", + "impid": "video1", + "price": 20, + "adm": "http://example.com/vast.xml", + "adid": "1088", + "adomain": [ + "beachfront.io" + ], + "cid": "277", + "crid": "532", + "cat":["IAB2"], + "w": 300, + "h": 250, + "ext": { + "duration": 30 + } + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/beachfront/beachfronttest/supplemental/six-nine-combo.json b/adapters/beachfront/beachfronttest/supplemental/six-nine-combo.json index 1779248edfc..294bd13d5f6 100644 --- a/adapters/beachfront/beachfronttest/supplemental/six-nine-combo.json +++ b/adapters/beachfront/beachfronttest/supplemental/six-nine-combo.json @@ -200,7 +200,7 @@ "dnt": 0, "user": {}, "adapterName": "BF_PREBID_S2S", - "adapterVersion": "0.9.2", + "adapterVersion": "1.0.0", "ip": "255.255.255.255", "requestId": "six-nine-combo", "schain": { @@ -258,6 +258,7 @@ "id": "ADMVideoWithBannerImp1", "secure": 0, "bidfloor": 1.01, + "bidfloorcur": "USD", "video": { "mimes": [ "video/mp4" @@ -317,6 +318,7 @@ "id": "ADMVideoWithBannerImp2", "secure": 0, "bidfloor": 1.01, + "bidfloorcur": "USD", "video": { "mimes": [ "video/mp4" @@ -375,6 +377,7 @@ { "id": "ADMVideoImp6", "bidfloor": 1.01, + "bidfloorcur": "USD", "secure": 0, "video": { "mimes": [ @@ -435,6 +438,7 @@ { "id": "NURLVideoImp3", "bidfloor": 1.01, + "bidfloorcur": "USD", "video": { "mimes": [ "video/mp4" diff --git a/adapters/beachfront/beachfronttest/supplemental/two-four-combo.json b/adapters/beachfront/beachfronttest/supplemental/two-four-combo.json index 75f37188d5c..7310d0b4f19 100644 --- a/adapters/beachfront/beachfronttest/supplemental/two-four-combo.json +++ b/adapters/beachfront/beachfronttest/supplemental/two-four-combo.json @@ -7,7 +7,8 @@ "ext": { "bidder": { "videoResponseType": "adm", - "bidfloor": 1.01, + "bidfloor": 1.02, + "bidfloorcur": "USD", "appIds": { "banner": "bannerAppId1", "video": "videoAppId1" @@ -36,6 +37,7 @@ "bidder": { "videoResponsetype": "nurl", "bidfloor": 1.01, + "bidfloorcur": "USD", "appIds": { "banner": "bannerAppId2", "video": "videoAppId1" @@ -75,7 +77,7 @@ { "slot":"ComboADMVideoWithBannerImp1", "id":"bannerAppId1", - "bidfloor":1.01, + "bidfloor":1.02, "sizes":[ { "w":300, @@ -110,7 +112,7 @@ }, "adapterName":"BF_PREBID_S2S", - "adapterVersion":"0.9.2", + "adapterVersion":"1.0.0", "ip":"255.255.255.255", "requestId":"two-four-combo", "schain": { @@ -159,7 +161,8 @@ "w":300, "h":400 }, - "bidfloor":1.01, + "bidfloor":1.02, + "bidfloorcur": "USD", "secure":0 } ], @@ -229,6 +232,7 @@ "h":150 }, "bidfloor":1.01, + "bidfloorcur": "USD", "secure":0 } ], diff --git a/adapters/coinzilla/coinzilla.go b/adapters/coinzilla/coinzilla.go new file mode 100644 index 00000000000..8535133d152 --- /dev/null +++ b/adapters/coinzilla/coinzilla.go @@ -0,0 +1,85 @@ +package coinzilla + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" +) + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +type adapter struct { + endpoint string +} + +func (adapter *adapter) MakeRequests(openRTBRequest *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) (requestsToBidder []*adapters.RequestData, errs []error) { + if len(openRTBRequest.Imp) == 0 { + return nil, []error{&errortypes.BadInput{ + Message: "No impression in the bid request", + }} + } + openRTBRequestJSON, err := json.Marshal(openRTBRequest) + if err != nil { + errs = append(errs, err) + return nil, errs + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + headers.Add("x-openrtb-version", "2.5") + + requestToBidder := &adapters.RequestData{ + Method: "POST", + Uri: adapter.endpoint, + Body: openRTBRequestJSON, + Headers: headers, + } + requestsToBidder = append(requestsToBidder, requestToBidder) + + return requestsToBidder, errs +} + +func (adapter *adapter) MakeBids(openRTBRequest *openrtb2.BidRequest, requestToBidder *adapters.RequestData, bidderRawResponse *adapters.ResponseData) (bidderResponse *adapters.BidderResponse, errs []error) { + switch bidderRawResponse.StatusCode { + case http.StatusOK: + break + case http.StatusNoContent: + return nil, nil + default: + err := &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected code: %d. Run with request.debug = 1", bidderRawResponse.StatusCode), + } + return nil, []error{err} + } + + var openRTBBidderResponse openrtb2.BidResponse + if err := json.Unmarshal(bidderRawResponse.Body, &openRTBBidderResponse); err != nil { + return nil, []error{err} + } + + bidsCapacity := len(openRTBBidderResponse.SeatBid[0].Bid) + bidderResponse = adapters.NewBidderResponseWithBidsCapacity(bidsCapacity) + bidderResponse.Currency = openRTBBidderResponse.Cur + var typedBid *adapters.TypedBid + for _, seatBid := range openRTBBidderResponse.SeatBid { + for i := range seatBid.Bid { + typedBid = &adapters.TypedBid{Bid: &seatBid.Bid[i], BidType: "banner"} + bidderResponse.Bids = append(bidderResponse.Bids, typedBid) + } + } + + return bidderResponse, nil +} diff --git a/adapters/coinzilla/coinzilla_test.go b/adapters/coinzilla/coinzilla_test.go new file mode 100644 index 00000000000..7b0763e40fa --- /dev/null +++ b/adapters/coinzilla/coinzilla_test.go @@ -0,0 +1,23 @@ +package coinzilla + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" +) + +const testsDir = "coinzillatest" +const testsBidderEndpoint = "http://test-request.com/prebid" + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderCoinzilla, config.Adapter{ + Endpoint: testsBidderEndpoint}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, testsDir, bidder) +} diff --git a/adapters/coinzilla/coinzillatest/exemplary/multi-banners.json b/adapters/coinzilla/coinzillatest/exemplary/multi-banners.json new file mode 100644 index 00000000000..dcbfe3e1797 --- /dev/null +++ b/adapters/coinzilla/coinzillatest/exemplary/multi-banners.json @@ -0,0 +1,117 @@ +{ + "mockBidRequest": { + "id": "testRequestIdMulti", + "site": { + "page": "https://publisher-website.com/web" + }, + "imp": [{ + "id": "testImpressionId", + "banner": { + "format": [{ + "w": 300, + "h": 250 + },{ + "w": 728, + "h": 90 + }] + }, + "ext": { + "bidder": { + "placementId": "testPlacementId" + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://test-request.com/prebid", + "body": { + "id": "testRequestIdMulti", + "site": { + "page": "https://publisher-website.com/web" + }, + "imp": [{ + "id": "testImpressionId", + "banner": { + "format": [{ + "w": 300, + "h": 250 + },{ + "w": 728, + "h": 90 + }] + }, + "ext": { + "bidder": { + "placementId": "testPlacementId" + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "testRequestIdMulti", + "seatbid": [{ + "seat": "coinzilla", + "bid": [{ + "id": "uniqueId#1", + "impid": "testImpressionId", + "price": 2.35, + "adid": "advertiserUniqueId#1", + "adm": "iframe-content#1", + "cid": "campaignUniqueId#1", + "crid": "creativeUniqueId#1", + "h": 250, + "w": 300 + },{ + "id": "uniqueId#2", + "impid": "testImpressionId", + "price": 1.93, + "adid": "advertiserUniqueId#2", + "adm": "iframe-content#2", + "cid": "campaignUniqueId#2", + "crid": "creativeUniqueId#2", + "h": 90, + "w": 728 + }] + }], + "cur": "USD" + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "uniqueId#1", + "impid": "testImpressionId", + "price": 2.35, + "adm": "iframe-content#1", + "adid": "advertiserUniqueId#1", + "cid": "campaignUniqueId#1", + "crid": "creativeUniqueId#1", + "w": 300, + "h": 250 + }, + "type": "banner" + },{ + "bid": { + "id": "uniqueId#2", + "impid": "testImpressionId", + "price": 1.93, + "adm": "iframe-content#2", + "adid": "advertiserUniqueId#2", + "cid": "campaignUniqueId#2", + "crid": "creativeUniqueId#2", + "w": 728, + "h": 90 + }, + "type": "banner" + } + ] + }] +} diff --git a/adapters/coinzilla/coinzillatest/exemplary/multi-imp.json b/adapters/coinzilla/coinzillatest/exemplary/multi-imp.json new file mode 100644 index 00000000000..f17ee95db5b --- /dev/null +++ b/adapters/coinzilla/coinzillatest/exemplary/multi-imp.json @@ -0,0 +1,137 @@ +{ + "mockBidRequest": { + "id": "testRequestIdSingle", + "site": { + "page": "https://publisher-website.com/web" + }, + "imp": [{ + "id": "testImpressionIdFirst", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "placementId": "testPlacementIdFirst" + } + } + },{ + "id": "testImpressionIdSecond", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "placementId": "testPlacementIdSecond" + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://test-request.com/prebid", + "body": { + "id": "testRequestIdSingle", + "site": { + "page": "https://publisher-website.com/web" + }, + "imp": [{ + "id": "testImpressionIdFirst", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "placementId": "testPlacementIdFirst" + } + } + }, + { + "id": "testImpressionIdSecond", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "placementId": "testPlacementIdSecond" + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "testRequestIdSingle", + "seatbid": [{ + "seat": "coinzilla", + "bid": [{ + "id": "uniqueIdFirst", + "impid": "testImpressionIdFirst", + "price": 2.35, + "adid": "advertiserUniqueIdFirst", + "adm": "iframe-content", + "cid": "campaignUniqueId", + "crid": "creativeUniqueId", + "h": 250, + "w": 300 + },{ + "id": "uniqueIdSecond", + "impid": "testImpressionIdSecond", + "price": 2.35, + "adid": "advertiserUniqueIdSecond", + "adm": "iframe-content", + "cid": "campaignUniqueId", + "crid": "creativeUniqueId", + "h": 250, + "w": 300 + }] + }], + "cur": "USD" + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "uniqueIdFirst", + "impid": "testImpressionIdFirst", + "price": 2.35, + "adm": "iframe-content", + "adid": "advertiserUniqueIdFirst", + "cid": "campaignUniqueId", + "crid": "creativeUniqueId", + "w": 300, + "h": 250 + }, + "type": "banner" + },{ + "bid": { + "id": "uniqueIdSecond", + "impid": "testImpressionIdSecond", + "price": 2.35, + "adm": "iframe-content", + "adid": "advertiserUniqueIdSecond", + "cid": "campaignUniqueId", + "crid": "creativeUniqueId", + "w": 300, + "h": 250 + }, + "type": "banner" + }] + }] +} diff --git a/adapters/coinzilla/coinzillatest/exemplary/single-banner.json b/adapters/coinzilla/coinzillatest/exemplary/single-banner.json new file mode 100644 index 00000000000..5d609266dcf --- /dev/null +++ b/adapters/coinzilla/coinzillatest/exemplary/single-banner.json @@ -0,0 +1,87 @@ +{ + "mockBidRequest": { + "id": "testRequestIdSingle", + "site": { + "page": "https://publisher-website.com/web" + }, + "imp": [{ + "id": "testImpressionId", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "placementId": "testPlacementId" + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://test-request.com/prebid", + "body": { + "id": "testRequestIdSingle", + "site": { + "page": "https://publisher-website.com/web" + }, + "imp": [{ + "id": "testImpressionId", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "placementId": "testPlacementId" + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "testRequestIdSingle", + "seatbid": [{ + "seat": "coinzilla", + "bid": [{ + "id": "uniqueId", + "impid": "testImpressionId", + "price": 2.35, + "adid": "advertiserUniqueId", + "adm": "iframe-content", + "cid": "campaignUniqueId", + "crid": "creativeUniqueId", + "h": 250, + "w": 300 + }] + }], + "cur": "USD" + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "uniqueId", + "impid": "testImpressionId", + "price": 2.35, + "adm": "iframe-content", + "adid": "advertiserUniqueId", + "cid": "campaignUniqueId", + "crid": "creativeUniqueId", + "w": 300, + "h": 250 + }, + "type": "banner" + }] + }] +} diff --git a/adapters/coinzilla/coinzillatest/supplemental/bad-request.json b/adapters/coinzilla/coinzillatest/supplemental/bad-request.json new file mode 100644 index 00000000000..3787ff71841 --- /dev/null +++ b/adapters/coinzilla/coinzillatest/supplemental/bad-request.json @@ -0,0 +1,59 @@ +{ + "mockBidRequest": { + "id": "testRequestIssues", + "site": { + "page": "https://publisher-website.com/web" + }, + "imp": [{ + "id": "testImpressionId", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "placementId": "testPlacementId" + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://test-request.com/prebid", + "body": { + "id": "testRequestIssues", + "site": { + "page": "https://publisher-website.com/web" + }, + "imp": [{ + "id": "testImpressionId", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "placementId": "testPlacementId" + } + } + }] + } + }, + "mockResponse": { + "status": 400, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected code: 400. Run with request.debug = 1", + "comparison": "literal" + } + ] +} diff --git a/adapters/coinzilla/coinzillatest/supplemental/no-bid.json b/adapters/coinzilla/coinzillatest/supplemental/no-bid.json new file mode 100644 index 00000000000..cff63b6b983 --- /dev/null +++ b/adapters/coinzilla/coinzillatest/supplemental/no-bid.json @@ -0,0 +1,55 @@ +{ + "mockBidRequest": { + "id": "testRequestNoBid", + "site": { + "page": "https://publisher-website.com/web" + }, + "imp": [{ + "id": "testImpressionId", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "placementId": "testPlacementId" + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://test-request.com/prebid", + "body": { + "id": "testRequestNoBid", + "site": { + "page": "https://publisher-website.com/web" + }, + "imp": [{ + "id": "testImpressionId", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "placementId": "testPlacementId" + } + } + }] + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + + "expectedBidResponses": [] +} diff --git a/adapters/coinzilla/params_test.go b/adapters/coinzilla/params_test.go new file mode 100644 index 00000000000..fa24b144769 --- /dev/null +++ b/adapters/coinzilla/params_test.go @@ -0,0 +1,45 @@ +package coinzilla + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range validParams { + if err := validator.Validate(openrtb_ext.BidderCoinzilla, json.RawMessage(p)); err != nil { + t.Errorf("Schema rejected valid params: %s", p) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderCoinzilla, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} + +var validParams = []string{ + `{"placementId": "1sad231"}`, +} + +var invalidParams = []string{ + `{"placementId": 42}`, + `null`, + `{}`, + `{"placementId":""}`, +} diff --git a/adapters/connectad/connectad.go b/adapters/connectad/connectad.go index 9827ebcea7b..5c30e3a6adc 100644 --- a/adapters/connectad/connectad.go +++ b/adapters/connectad/connectad.go @@ -18,10 +18,6 @@ type ConnectAdAdapter struct { endpoint string } -type connectadImpExt struct { - ConnectAd openrtb_ext.ExtImpConnectAd `json:"connectad"` -} - // Builder builds a new instance of the ConnectAd adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { bidder := &ConnectAdAdapter{ diff --git a/adapters/consumable/instant.go b/adapters/consumable/instant.go index 5a32fef8837..a6162d44e22 100644 --- a/adapters/consumable/instant.go +++ b/adapters/consumable/instant.go @@ -9,7 +9,7 @@ type instant interface { // Send a real instance when you construct it in adapter_map.go type realInstant struct{} -func (_ realInstant) Now() time.Time { +func (realInstant) Now() time.Time { return time.Now() } diff --git a/adapters/consumable/utils.go b/adapters/consumable/utils.go deleted file mode 100644 index 64e4872c619..00000000000 --- a/adapters/consumable/utils.go +++ /dev/null @@ -1,20 +0,0 @@ -package consumable - -import ( - netUrl "net/url" -) - -/** - * Creates a snippet of HTML that retrieves the specified `url` - * Returns HTML snippet that contains the img src = set to `url` - */ -func createTrackPixelHtml(url *string) string { - if url == nil { - return "" - } - - escapedUrl := netUrl.QueryEscape(*url) - img := "
" + - "
" - return img -} diff --git a/adapters/conversant/cnvr_legacy.go b/adapters/conversant/cnvr_legacy.go deleted file mode 100644 index eff1afc5d32..00000000000 --- a/adapters/conversant/cnvr_legacy.go +++ /dev/null @@ -1,291 +0,0 @@ -package conversant - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/pbs" - "golang.org/x/net/context/ctxhttp" -) - -type ConversantLegacyAdapter struct { - http *adapters.HTTPAdapter - URI string -} - -// Corresponds to the bidder name in cookies and requests -func (a *ConversantLegacyAdapter) Name() string { - return "conversant" -} - -// Return true so no request will be sent unless user has been sync'ed. -func (a *ConversantLegacyAdapter) SkipNoCookies() bool { - return true -} - -type conversantParams struct { - SiteID string `json:"site_id"` - Secure *int8 `json:"secure"` - TagID string `json:"tag_id"` - Position *int8 `json:"position"` - BidFloor float64 `json:"bidfloor"` - Mobile *int8 `json:"mobile"` - MIMEs []string `json:"mimes"` - API []int8 `json:"api"` - Protocols []int8 `json:"protocols"` - MaxDuration *int64 `json:"maxduration"` -} - -func (a *ConversantLegacyAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { - mediaTypes := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} - cnvrReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), mediaTypes) - - if err != nil { - return nil, err - } - - // Create a map of impression objects for both request creation - // and response parsing. - - impMap := make(map[string]*openrtb2.Imp, len(cnvrReq.Imp)) - for idx := range cnvrReq.Imp { - impMap[cnvrReq.Imp[idx].ID] = &cnvrReq.Imp[idx] - } - - // Fill in additional info from custom params - - for _, unit := range bidder.AdUnits { - var params conversantParams - - imp := impMap[unit.Code] - if imp == nil { - // Skip ad units that do not have corresponding impressions. - continue - } - - err := json.Unmarshal(unit.Params, ¶ms) - if err != nil { - return nil, &errortypes.BadInput{ - Message: err.Error(), - } - } - - // Fill in additional Site info - if params.SiteID != "" { - if cnvrReq.Site != nil { - cnvrReq.Site.ID = params.SiteID - } - if cnvrReq.App != nil { - cnvrReq.App.ID = params.SiteID - } - } - - if params.Mobile != nil && !(cnvrReq.Site == nil) { - cnvrReq.Site.Mobile = *params.Mobile - } - - // Fill in additional impression info - - imp.DisplayManager = "prebid-s2s" - imp.DisplayManagerVer = "1.0.1" - imp.BidFloor = params.BidFloor - imp.TagID = params.TagID - - var position *openrtb2.AdPosition - if params.Position != nil { - position = openrtb2.AdPosition(*params.Position).Ptr() - } - - if imp.Banner != nil { - imp.Banner.Pos = position - } else if imp.Video != nil { - imp.Video.Pos = position - - if len(params.API) > 0 { - imp.Video.API = make([]openrtb2.APIFramework, 0, len(params.API)) - for _, api := range params.API { - imp.Video.API = append(imp.Video.API, openrtb2.APIFramework(api)) - } - } - - // Include protocols, mimes, and max duration if specified - // These properties can also be specified in ad unit's video object, - // but are overridden if the custom params object also contains them. - - if len(params.Protocols) > 0 { - imp.Video.Protocols = make([]openrtb2.Protocol, 0, len(params.Protocols)) - for _, protocol := range params.Protocols { - imp.Video.Protocols = append(imp.Video.Protocols, openrtb2.Protocol(protocol)) - } - } - - if len(params.MIMEs) > 0 { - imp.Video.MIMEs = make([]string, len(params.MIMEs)) - copy(imp.Video.MIMEs, params.MIMEs) - } - - if params.MaxDuration != nil { - imp.Video.MaxDuration = *params.MaxDuration - } - } - - // Take care not to override the global secure flag - - if (imp.Secure == nil || *imp.Secure == 0) && params.Secure != nil { - imp.Secure = params.Secure - } - } - - // Do a quick check on required parameters - - if cnvrReq.Site != nil && cnvrReq.Site.ID == "" { - return nil, &errortypes.BadInput{ - Message: "Missing site id", - } - } - - if cnvrReq.App != nil && cnvrReq.App.ID == "" { - return nil, &errortypes.BadInput{ - Message: "Missing app id", - } - } - - // Start capturing debug info - - debug := &pbs.BidderDebug{ - RequestURI: a.URI, - } - - if cnvrReq.Device == nil { - cnvrReq.Device = &openrtb2.Device{} - } - - // Convert request to json to be sent over http - - j, _ := json.Marshal(cnvrReq) - - if req.IsDebug { - debug.RequestBody = string(j) - bidder.Debug = append(bidder.Debug, debug) - } - - httpReq, err := http.NewRequest("POST", a.URI, bytes.NewBuffer(j)) - httpReq.Header.Add("Content-Type", "application/json") - httpReq.Header.Add("Accept", "application/json") - - resp, err := ctxhttp.Do(ctx, a.http.Client, httpReq) - if err != nil { - return nil, err - } - - defer resp.Body.Close() - - if req.IsDebug { - debug.StatusCode = resp.StatusCode - } - - if resp.StatusCode == 204 { - return nil, nil - } - - body, err := ioutil.ReadAll(resp.Body) - - if err != nil { - return nil, err - } - - if resp.StatusCode == http.StatusBadRequest { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("HTTP status: %d, body: %s", resp.StatusCode, string(body)), - } - } - - if resp.StatusCode != 200 { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("HTTP status: %d, body: %s", resp.StatusCode, string(body)), - } - } - - if req.IsDebug { - debug.ResponseBody = string(body) - } - - var bidResp openrtb2.BidResponse - - err = json.Unmarshal(body, &bidResp) - if err != nil { - return nil, &errortypes.BadServerResponse{ - Message: err.Error(), - } - } - - bids := make(pbs.PBSBidSlice, 0) - - for _, seatbid := range bidResp.SeatBid { - for _, bid := range seatbid.Bid { - if bid.Price <= 0 { - continue - } - - imp := impMap[bid.ImpID] - if imp == nil { - // All returned bids should have a matching impression - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unknown impression id '%s'", bid.ImpID), - } - } - - bidID := bidder.LookupBidID(bid.ImpID) - if bidID == "" { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unknown ad unit code '%s'", bid.ImpID), - } - } - - pbsBid := pbs.PBSBid{ - BidID: bidID, - AdUnitCode: bid.ImpID, - Price: bid.Price, - Creative_id: bid.CrID, - BidderCode: bidder.BidderCode, - } - - if imp.Video != nil { - pbsBid.CreativeMediaType = "video" - pbsBid.NURL = bid.AdM // Assign to NURL so it'll be interpreted as a vastUrl - pbsBid.Width = imp.Video.W - pbsBid.Height = imp.Video.H - } else { - pbsBid.CreativeMediaType = "banner" - pbsBid.NURL = bid.NURL - pbsBid.Adm = bid.AdM - pbsBid.Width = bid.W - pbsBid.Height = bid.H - } - - bids = append(bids, &pbsBid) - } - } - - if len(bids) == 0 { - return nil, nil - } - - return bids, nil -} - -func NewConversantLegacyAdapter(config *adapters.HTTPAdapterConfig, uri string) *ConversantLegacyAdapter { - a := adapters.NewHTTPAdapter(config) - - return &ConversantLegacyAdapter{ - http: a, - URI: uri, - } -} diff --git a/adapters/conversant/cnvr_legacy_test.go b/adapters/conversant/cnvr_legacy_test.go deleted file mode 100644 index fc34a93fae2..00000000000 --- a/adapters/conversant/cnvr_legacy_test.go +++ /dev/null @@ -1,853 +0,0 @@ -package conversant - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/cache/dummycache" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/pbs" - "github.com/prebid/prebid-server/usersync" -) - -// Constants - -const ExpectedSiteID string = "12345" -const ExpectedDisplayManager string = "prebid-s2s" -const ExpectedBuyerUID string = "AQECT_o7M1FLbQJK8QFmAQEBAQE" -const ExpectedNURL string = "http://test.dotomi.com" -const ExpectedAdM string = "" -const ExpectedCrID string = "98765" - -const DefaultParam = `{"site_id": "12345"}` - -// Test properties of Adapter interface - -func TestConversantProperties(t *testing.T) { - an := NewConversantLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "someUrl") - - assertNotEqual(t, an.Name(), "", "Missing family name") - assertTrue(t, an.SkipNoCookies(), "SkipNoCookies should be true") -} - -// Test empty bid requests - -func TestConversantEmptyBid(t *testing.T) { - an := NewConversantLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "someUrl") - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{} - _, err := an.Call(ctx, &pbReq, &pbBidder) - assertTrue(t, err != nil, "No error received for an invalid request") -} - -// Test required parameters, which is just the site id for now - -func TestConversantRequiredParameters(t *testing.T) { - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.Error(w, http.StatusText(http.StatusNoContent), http.StatusNoContent) - }), - ) - defer server.Close() - - an := NewConversantLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - ctx := context.TODO() - - testParams := func(params ...string) (pbs.PBSBidSlice, error) { - req, err := CreateBannerRequest(params...) - if err != nil { - return nil, err - } - return an.Call(ctx, req, req.Bidders[0]) - } - - var err error - - if _, err = testParams(`{}`); err == nil { - t.Fatal("Failed to catch missing site id") - } -} - -// Test handling of 404 - -func TestConversantBadStatus(t *testing.T) { - // Create a test http server that returns after 2 milliseconds - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) - }), - ) - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantLegacyAdapter(&conf, server.URL) - - ctx := context.TODO() - pbReq, err := CreateBannerRequest(DefaultParam) - if err != nil { - t.Fatal("Failed to create a banner request", err) - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - assertTrue(t, err != nil, "Failed to catch 404 error") -} - -// Test handling of HTTP timeout - -func TestConversantTimeout(t *testing.T) { - // Create a test http server that returns after 2 milliseconds - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - <-time.After(2 * time.Millisecond) - }), - ) - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantLegacyAdapter(&conf, server.URL) - - // Create a context that expires before http returns - - ctx, cancel := context.WithTimeout(context.Background(), 0) - defer cancel() - - // Create a basic request - pbReq, err := CreateBannerRequest(DefaultParam) - if err != nil { - t.Fatal("Failed to create a banner request", err) - } - - // Attempt to process the request, which should hit a timeout - // immediately - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err == nil || err != context.DeadlineExceeded { - t.Fatal("No timeout recevied for timed out request", err) - } -} - -// Test handling of 204 - -func TestConversantNoBid(t *testing.T) { - // Create a test http server that returns after 2 milliseconds - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.Error(w, http.StatusText(http.StatusNoContent), http.StatusNoContent) - }), - ) - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantLegacyAdapter(&conf, server.URL) - - ctx := context.TODO() - pbReq, err := CreateBannerRequest(DefaultParam) - if err != nil { - t.Fatal("Failed to create a banner request", err) - } - - resp, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - if resp != nil || err != nil { - t.Fatal("Failed to handle empty bid", err) - } -} - -// Verify an outgoing openrtp request is created correctly - -func TestConversantRequestDefault(t *testing.T) { - server, lastReq := CreateServer() - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantLegacyAdapter(&conf, server.URL) - - ctx := context.TODO() - pbReq, err := CreateBannerRequest(DefaultParam) - if err != nil { - t.Fatal("Failed to create a banner request", err) - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatal("Failed to retrieve bids", err) - } - - assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") - imp := &lastReq.Imp[0] - - assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") - assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") - assertEqual(t, int(lastReq.Site.Mobile), 0, "Request site mobile flag") - assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") - assertTrue(t, imp.Video == nil, "Request video should be nil") - assertEqual(t, int(*imp.Secure), 0, "Request secure") - assertEqual(t, imp.BidFloor, 0.0, "Request bid floor") - assertEqual(t, imp.TagID, "", "Request tag id") - assertTrue(t, imp.Banner.Pos == nil, "Request pos") - assertEqual(t, int(*imp.Banner.W), 300, "Request width") - assertEqual(t, int(*imp.Banner.H), 250, "Request height") -} - -// Verify inapp video request -func TestConversantInappVideoRequest(t *testing.T) { - server, lastReq := CreateServer() - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantLegacyAdapter(&conf, server.URL) - - requestParam := `{"secure": 1, "site_id": "12345"}` - appParam := `{ "bundle": "com.naver.linewebtoon" }` - videoParam := `{ "mimes": ["video/x-ms-wmv"], - "protocols": [1, 2], - "maxduration": 90 }` - - ctx := context.TODO() - pbReq := CreateRequest(requestParam) - pbReq, err := ConvertToVideoRequest(pbReq, videoParam) - if err != nil { - t.Fatal("failed to parse request") - } - pbReq, err = ConvertToAppRequest(pbReq, appParam) - if err != nil { - t.Fatal("failed to parse request") - } - pbReq, err = ParseRequest(pbReq) - if err != nil { - t.Fatal("failed to parse request") - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - - imp := &lastReq.Imp[0] - assertEqual(t, int(imp.Video.W), 300, "Request width") - assertEqual(t, int(imp.Video.H), 250, "Request height") - assertEqual(t, lastReq.App.ID, "12345", "App Id") -} - -// Verify inapp video request -func TestConversantInappBannerRequest(t *testing.T) { - server, lastReq := CreateServer() - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantLegacyAdapter(&conf, server.URL) - - param := `{ "secure": 1, - "site_id": "12345", - "tag_id": "top", - "position": 2, - "bidfloor": 1.01 }` - appParam := `{ "bundle": "com.naver.linewebtoon" }` - - ctx := context.TODO() - pbReq, _ := CreateBannerRequest(param) - pbReq, err := ConvertToAppRequest(pbReq, appParam) - if err != nil { - t.Fatal("failed to parse request") - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - - imp := &lastReq.Imp[0] - assertEqual(t, lastReq.App.ID, "12345", "App Id") - assertEqual(t, int(*imp.Banner.W), 300, "Request width") - assertEqual(t, int(*imp.Banner.H), 250, "Request height") -} - -// Verify an outgoing openrtp request with additional conversant parameters is -// processed correctly - -func TestConversantRequest(t *testing.T) { - server, lastReq := CreateServer() - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantLegacyAdapter(&conf, server.URL) - - param := `{ "site_id": "12345", - "secure": 1, - "tag_id": "top", - "position": 2, - "bidfloor": 1.01, - "mobile": 1 }` - - ctx := context.TODO() - pbReq, err := CreateBannerRequest(param) - if err != nil { - t.Fatal("Failed to create a banner request", err) - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatal("Failed to retrieve bids", err) - } - - assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") - imp := &lastReq.Imp[0] - - assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") - assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") - assertEqual(t, int(lastReq.Site.Mobile), 1, "Request site mobile flag") - assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") - assertTrue(t, imp.Video == nil, "Request video should be nil") - assertEqual(t, int(*imp.Secure), 1, "Request secure") - assertEqual(t, imp.BidFloor, 1.01, "Request bid floor") - assertEqual(t, imp.TagID, "top", "Request tag id") - assertEqual(t, int(*imp.Banner.Pos), 2, "Request pos") - assertEqual(t, int(*imp.Banner.W), 300, "Request width") - assertEqual(t, int(*imp.Banner.H), 250, "Request height") -} - -// Verify openrtp responses are converted correctly - -func TestConversantResponse(t *testing.T) { - prices := []float64{0.01, 0.0, 2.01} - server, lastReq := CreateServer(prices...) - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantLegacyAdapter(&conf, server.URL) - - param := `{ "site_id": "12345", - "secure": 1, - "tag_id": "top", - "position": 2, - "bidfloor": 1.01, - "mobile" : 1}` - - ctx := context.TODO() - pbReq, err := CreateBannerRequest(param, param, param) - if err != nil { - t.Fatal("Failed to create a banner request", err) - } - - resp, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatal("Failed to retrieve bids", err) - } - - prices, imps := FilterZeroPrices(prices, lastReq.Imp) - - assertEqual(t, len(resp), len(prices), "Bad number of responses") - - for i, bid := range resp { - assertEqual(t, bid.Price, prices[i], "Bad price in response") - assertEqual(t, bid.AdUnitCode, imps[i].ID, "Bad bid id in response") - - if bid.Price > 0 { - assertEqual(t, bid.Adm, ExpectedAdM, "Bad ad markup in response") - assertEqual(t, bid.NURL, ExpectedNURL, "Bad notification url in response") - assertEqual(t, bid.Creative_id, ExpectedCrID, "Bad creative id in response") - assertEqual(t, bid.Width, *imps[i].Banner.W, "Bad width in response") - assertEqual(t, bid.Height, *imps[i].Banner.H, "Bad height in response") - } - } -} - -// Test video request - -func TestConversantBasicVideoRequest(t *testing.T) { - server, lastReq := CreateServer() - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantLegacyAdapter(&conf, server.URL) - - param := `{ "site_id": "12345", - "tag_id": "bottom left", - "position": 3, - "bidfloor": 1.01 }` - - ctx := context.TODO() - pbReq, err := CreateVideoRequest(param) - if err != nil { - t.Fatal("Failed to create a video request", err) - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatal("Failed to retrieve bids", err) - } - - assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") - imp := &lastReq.Imp[0] - - assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") - assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") - assertEqual(t, int(lastReq.Site.Mobile), 0, "Request site mobile flag") - assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") - assertTrue(t, imp.Banner == nil, "Request banner should be nil") - assertEqual(t, int(*imp.Secure), 0, "Request secure") - assertEqual(t, imp.BidFloor, 1.01, "Request bid floor") - assertEqual(t, imp.TagID, "bottom left", "Request tag id") - assertEqual(t, int(*imp.Video.Pos), 3, "Request pos") - assertEqual(t, int(imp.Video.W), 300, "Request width") - assertEqual(t, int(imp.Video.H), 250, "Request height") - - assertEqual(t, len(imp.Video.MIMEs), 1, "Request video MIMEs entries") - assertEqual(t, imp.Video.MIMEs[0], "video/mp4", "Requst video MIMEs type") - assertTrue(t, imp.Video.Protocols == nil, "Request video protocols") - assertEqual(t, imp.Video.MaxDuration, int64(0), "Request video 0 max duration") - assertTrue(t, imp.Video.API == nil, "Request video api should be nil") -} - -// Test video request with parameters in custom params object - -func TestConversantVideoRequestWithParams(t *testing.T) { - server, lastReq := CreateServer() - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantLegacyAdapter(&conf, server.URL) - - param := `{ "site_id": "12345", - "tag_id": "bottom left", - "position": 3, - "bidfloor": 1.01, - "mimes": ["video/x-ms-wmv"], - "protocols": [1, 2], - "api": [1, 2], - "maxduration": 90 }` - - ctx := context.TODO() - pbReq, err := CreateVideoRequest(param) - if err != nil { - t.Fatal("Failed to create a video request", err) - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatal("Failed to retrieve bids", err) - } - - assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") - imp := &lastReq.Imp[0] - - assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") - assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") - assertEqual(t, int(lastReq.Site.Mobile), 0, "Request site mobile flag") - assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") - assertTrue(t, imp.Banner == nil, "Request banner should be nil") - assertEqual(t, int(*imp.Secure), 0, "Request secure") - assertEqual(t, imp.BidFloor, 1.01, "Request bid floor") - assertEqual(t, imp.TagID, "bottom left", "Request tag id") - assertEqual(t, int(*imp.Video.Pos), 3, "Request pos") - assertEqual(t, int(imp.Video.W), 300, "Request width") - assertEqual(t, int(imp.Video.H), 250, "Request height") - - assertEqual(t, len(imp.Video.MIMEs), 1, "Request video MIMEs entries") - assertEqual(t, imp.Video.MIMEs[0], "video/x-ms-wmv", "Requst video MIMEs type") - assertEqual(t, len(imp.Video.Protocols), 2, "Request video protocols") - assertEqual(t, imp.Video.Protocols[0], openrtb2.Protocol(1), "Request video protocols 1") - assertEqual(t, imp.Video.Protocols[1], openrtb2.Protocol(2), "Request video protocols 2") - assertEqual(t, imp.Video.MaxDuration, int64(90), "Request video 0 max duration") - assertEqual(t, len(imp.Video.API), 2, "Request video api should be nil") - assertEqual(t, imp.Video.API[0], openrtb2.APIFramework(1), "Request video api 1") - assertEqual(t, imp.Video.API[1], openrtb2.APIFramework(2), "Request video api 2") -} - -// Test video request with parameters in the video object - -func TestConversantVideoRequestWithParams2(t *testing.T) { - server, lastReq := CreateServer() - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantLegacyAdapter(&conf, server.URL) - - param := `{ "site_id": "12345" }` - videoParam := `{ "mimes": ["video/x-ms-wmv"], - "protocols": [1, 2], - "maxduration": 90 }` - - ctx := context.TODO() - pbReq := CreateRequest(param) - pbReq, err := ConvertToVideoRequest(pbReq, videoParam) - if err != nil { - t.Fatal("Failed to convert to a video request", err) - } - pbReq, err = ParseRequest(pbReq) - if err != nil { - t.Fatal("Failed to parse video request", err) - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatal("Failed to retrieve bids", err) - } - - assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") - imp := &lastReq.Imp[0] - - assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") - assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") - assertEqual(t, int(lastReq.Site.Mobile), 0, "Request site mobile flag") - assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") - assertTrue(t, imp.Banner == nil, "Request banner should be nil") - assertEqual(t, int(*imp.Secure), 0, "Request secure") - assertEqual(t, imp.BidFloor, 0.0, "Request bid floor") - assertEqual(t, int(imp.Video.W), 300, "Request width") - assertEqual(t, int(imp.Video.H), 250, "Request height") - - assertEqual(t, len(imp.Video.MIMEs), 1, "Request video MIMEs entries") - assertEqual(t, imp.Video.MIMEs[0], "video/x-ms-wmv", "Requst video MIMEs type") - assertEqual(t, len(imp.Video.Protocols), 2, "Request video protocols") - assertEqual(t, imp.Video.Protocols[0], openrtb2.Protocol(1), "Request video protocols 1") - assertEqual(t, imp.Video.Protocols[1], openrtb2.Protocol(2), "Request video protocols 2") - assertEqual(t, imp.Video.MaxDuration, int64(90), "Request video 0 max duration") -} - -// Test video responses - -func TestConversantVideoResponse(t *testing.T) { - prices := []float64{0.01, 0.0, 2.01} - server, lastReq := CreateServer(prices...) - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantLegacyAdapter(&conf, server.URL) - - param := `{ "site_id": "12345", - "secure": 1, - "tag_id": "top", - "position": 2, - "bidfloor": 1.01, - "mobile" : 1}` - - ctx := context.TODO() - pbReq, err := CreateVideoRequest(param, param, param) - if err != nil { - t.Fatal("Failed to create a video request", err) - } - - resp, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatal("Failed to retrieve bids", err) - } - - prices, imps := FilterZeroPrices(prices, lastReq.Imp) - - assertEqual(t, len(resp), len(prices), "Bad number of responses") - - for i, bid := range resp { - assertEqual(t, bid.Price, prices[i], "Bad price in response") - assertEqual(t, bid.AdUnitCode, imps[i].ID, "Bad bid id in response") - - if bid.Price > 0 { - assertEqual(t, bid.Adm, "", "Bad ad markup in response") - assertEqual(t, bid.NURL, ExpectedAdM, "Bad notification url in response") - assertEqual(t, bid.Creative_id, ExpectedCrID, "Bad creative id in response") - assertEqual(t, bid.Width, imps[i].Video.W, "Bad width in response") - assertEqual(t, bid.Height, imps[i].Video.H, "Bad height in response") - } - } -} - -// Helpers to create a banner and video requests - -func CreateRequest(params ...string) *pbs.PBSRequest { - num := len(params) - - req := pbs.PBSRequest{ - Tid: "t-000", - AccountID: "1", - AdUnits: make([]pbs.AdUnit, num), - } - - for i := 0; i < num; i++ { - req.AdUnits[i] = pbs.AdUnit{ - Code: fmt.Sprintf("au-%03d", i), - Sizes: []openrtb2.Format{ - { - W: 300, - H: 250, - }, - }, - Bids: []pbs.Bids{ - { - BidderCode: "conversant", - BidID: fmt.Sprintf("b-%03d", i), - Params: json.RawMessage(params[i]), - }, - }, - } - } - - return &req -} - -// Convert a request to a video request by adding required properties - -func ConvertToVideoRequest(req *pbs.PBSRequest, videoParams ...string) (*pbs.PBSRequest, error) { - for i := 0; i < len(req.AdUnits); i++ { - video := pbs.PBSVideo{} - if i < len(videoParams) { - err := json.Unmarshal([]byte(videoParams[i]), &video) - if err != nil { - return nil, err - } - } - - if video.Mimes == nil { - video.Mimes = []string{"video/mp4"} - } - - req.AdUnits[i].Video = video - req.AdUnits[i].MediaTypes = []string{"video"} - } - - return req, nil -} - -// Convert a request to an app request by adding required properties -func ConvertToAppRequest(req *pbs.PBSRequest, appParams string) (*pbs.PBSRequest, error) { - app := new(openrtb2.App) - err := json.Unmarshal([]byte(appParams), &app) - if err == nil { - req.App = app - } - - return req, nil -} - -// Feed the request thru the prebid parser so user id and -// other private properties are defined - -func ParseRequest(req *pbs.PBSRequest) (*pbs.PBSRequest, error) { - body := new(bytes.Buffer) - _ = json.NewEncoder(body).Encode(req) - - // Need to pass the conversant user id thru uid cookie - - httpReq := httptest.NewRequest("POST", "/foo", body) - cookie := usersync.NewCookie() - _ = cookie.TrySync("conversant", ExpectedBuyerUID) - httpReq.Header.Set("Cookie", cookie.ToHTTPCookie(90*24*time.Hour).String()) - httpReq.Header.Add("Referer", "http://example.com") - cache, _ := dummycache.New() - hcc := config.HostCookie{} - - parsedReq, err := pbs.ParsePBSRequest(httpReq, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, cache, &hcc) - - return parsedReq, err -} - -// A helper to create a banner request - -func CreateBannerRequest(params ...string) (*pbs.PBSRequest, error) { - req := CreateRequest(params...) - req, err := ParseRequest(req) - return req, err -} - -// A helper to create a video request - -func CreateVideoRequest(params ...string) (*pbs.PBSRequest, error) { - req := CreateRequest(params...) - req, err := ConvertToVideoRequest(req) - if err != nil { - return nil, err - } - req, err = ParseRequest(req) - return req, err -} - -// Helper to create a test http server that receives and generate openrtb requests and responses - -func CreateServer(prices ...float64) (*httptest.Server, *openrtb2.BidRequest) { - var lastBidRequest openrtb2.BidRequest - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - body, err := ioutil.ReadAll(r.Body) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - var bidReq openrtb2.BidRequest - var price float64 - var bids []openrtb2.Bid - var bid openrtb2.Bid - - err = json.Unmarshal(body, &bidReq) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - - lastBidRequest = bidReq - - for i, imp := range bidReq.Imp { - if i < len(prices) { - price = prices[i] - } else { - price = 0 - } - - if price > 0 { - bid = openrtb2.Bid{ - ID: imp.ID, - ImpID: imp.ID, - Price: price, - NURL: ExpectedNURL, - AdM: ExpectedAdM, - CrID: ExpectedCrID, - } - - if imp.Banner != nil { - bid.W = *imp.Banner.W - bid.H = *imp.Banner.H - } else if imp.Video != nil { - bid.W = imp.Video.W - bid.H = imp.Video.H - } - } else { - bid = openrtb2.Bid{ - ID: imp.ID, - ImpID: imp.ID, - Price: 0, - } - } - - bids = append(bids, bid) - } - - if len(bids) == 0 { - w.WriteHeader(http.StatusNoContent) - } else { - js, _ := json.Marshal(openrtb2.BidResponse{ - ID: bidReq.ID, - SeatBid: []openrtb2.SeatBid{ - { - Bid: bids, - }, - }, - }) - - w.Header().Set("Content-Type", "application/json") - _, _ = w.Write(js) - } - }), - ) - - return server, &lastBidRequest -} - -// Helper to remove impressions with $0 bids - -func FilterZeroPrices(prices []float64, imps []openrtb2.Imp) ([]float64, []openrtb2.Imp) { - prices2 := make([]float64, 0) - imps2 := make([]openrtb2.Imp, 0) - - for i := range prices { - if prices[i] > 0 { - prices2 = append(prices2, prices[i]) - imps2 = append(imps2, imps[i]) - } - } - - return prices2, imps2 -} - -// Helpers to test equality - -func assertEqual(t *testing.T, actual interface{}, expected interface{}, msg string) { - if expected != actual { - msg = fmt.Sprintf("%s: act(%v) != exp(%v)", msg, actual, expected) - t.Fatal(msg) - } -} - -func assertNotEqual(t *testing.T, actual interface{}, expected interface{}, msg string) { - if expected == actual { - msg = fmt.Sprintf("%s: act(%v) == exp(%v)", msg, actual, expected) - t.Fatal(msg) - } -} - -func assertTrue(t *testing.T, val bool, msg string) { - if val == false { - msg = fmt.Sprintf("%s: is false but should be true", msg) - t.Fatal(msg) - } -} - -func assertFalse(t *testing.T, val bool, msg string) { - if val == true { - msg = fmt.Sprintf("%s: is true but should be false", msg) - t.Fatal(msg) - } -} diff --git a/adapters/deepintent/deepintent.go b/adapters/deepintent/deepintent.go index b5b0fd54c5d..0853bb8b405 100644 --- a/adapters/deepintent/deepintent.go +++ b/adapters/deepintent/deepintent.go @@ -20,10 +20,6 @@ type DeepintentAdapter struct { URI string } -type deepintentParams struct { - tagId string `json:"tagId"` -} - // Builder builds a new instance of the Deepintent adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { bidder := &DeepintentAdapter{ diff --git a/adapters/dmx/dmx_test.go b/adapters/dmx/dmx_test.go index 409290c110d..aa4a6f79053 100644 --- a/adapters/dmx/dmx_test.go +++ b/adapters/dmx/dmx_test.go @@ -13,10 +13,6 @@ import ( "github.com/prebid/prebid-server/adapters/adapterstest" ) -var ( - bidRequest string -) - func TestFetchParams(t *testing.T) { var w, h int = 300, 250 diff --git a/adapters/impactify/impactifytest/exemplary/sample_banner.json b/adapters/impactify/impactifytest/exemplary/sample_banner.json index f5d76c9e4c7..2eeba02b26e 100644 --- a/adapters/impactify/impactifytest/exemplary/sample_banner.json +++ b/adapters/impactify/impactifytest/exemplary/sample_banner.json @@ -14,6 +14,8 @@ } ] }, + "bidfloor": 1.00, + "bidfloorcur": "MXN", "ext":{ "bidder": { "appId": "impactify.io", @@ -22,7 +24,19 @@ } } } - ] + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "MXN": { + "USD": 0.05 + } + }, + "usepbsrates": false + } + } + } }, "httpCalls": [ @@ -45,6 +59,8 @@ } ] }, + "bidfloor": 0.05, + "bidfloorcur": "USD", "ext": { "impactify": { "appId": "impactify.io", @@ -53,7 +69,19 @@ } } } - ] + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "MXN": { + "USD": 0.05 + } + }, + "usepbsrates": false + } + } + } } }, "mockResponse": { diff --git a/adapters/impactify/impactifytest/supplemental/currency_rate_not_found.json b/adapters/impactify/impactifytest/supplemental/currency_rate_not_found.json new file mode 100644 index 00000000000..d52ccd31036 --- /dev/null +++ b/adapters/impactify/impactifytest/supplemental/currency_rate_not_found.json @@ -0,0 +1,45 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "bidfloor": 1.00, + "bidfloorcur": "JPY", + "ext":{ + "bidder": { + "appId": "impactify.io", + "format": "screen", + "style": "impact" + } + } + } + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "MXN": { + "USD": 0.05 + } + }, + "usepbsrates": false + } + } + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Currency conversion rate not found: 'JPY' => 'USD'", + "comparison": "literal" + } + ] +} diff --git a/adapters/infoawarebidder_test.go b/adapters/infoawarebidder_test.go index 375248137ad..62bcc08d7cb 100644 --- a/adapters/infoawarebidder_test.go +++ b/adapters/infoawarebidder_test.go @@ -178,7 +178,6 @@ func TestImpFiltering(t *testing.T) { } type mockBidder struct { - gotRequest *openrtb2.BidRequest } func (m *mockBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { diff --git a/adapters/ix/ix.go b/adapters/ix/ix.go index c79eda31040..1cfec69322d 100644 --- a/adapters/ix/ix.go +++ b/adapters/ix/ix.go @@ -1,259 +1,27 @@ package ix import ( - "bytes" - "context" "encoding/json" "fmt" - "io/ioutil" "net/http" "sort" "strings" - "github.com/mxmCherry/openrtb/v15/native1" - native1response "github.com/mxmCherry/openrtb/v15/native1/response" - "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/pbs" - "golang.org/x/net/context/ctxhttp" + + "github.com/mxmCherry/openrtb/v15/native1" + native1response "github.com/mxmCherry/openrtb/v15/native1/response" + "github.com/mxmCherry/openrtb/v15/openrtb2" ) type IxAdapter struct { - http *adapters.HTTPAdapter URI string maxRequests int } -func (a *IxAdapter) Name() string { - return string(openrtb_ext.BidderIx) -} - -func (a *IxAdapter) SkipNoCookies() bool { - return false -} - -type indexParams struct { - SiteID string `json:"siteId"` -} - -type ixBidResult struct { - Request *callOneObject - StatusCode int - ResponseBody string - Bid *pbs.PBSBid - Error error -} - -type callOneObject struct { - requestJSON bytes.Buffer - width int64 - height int64 - bidType string -} - -func (a *IxAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { - var prioritizedRequests, requests []callOneObject - - mediaTypes := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} - indexReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), mediaTypes) - if err != nil { - return nil, err - } - - indexReqImp := indexReq.Imp - for i, unit := range bidder.AdUnits { - // Supposedly fixes some segfaults - if len(indexReqImp) <= i { - break - } - - var params indexParams - err := json.Unmarshal(unit.Params, ¶ms) - if err != nil { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("unmarshal params '%s' failed: %v", unit.Params, err), - } - } - - if params.SiteID == "" { - return nil, &errortypes.BadInput{ - Message: "Missing siteId param", - } - } - - for sizeIndex, format := range unit.Sizes { - // Only grab this ad unit. Not supporting multi-media-type adunit yet. - thisImp := indexReqImp[i] - - thisImp.TagID = unit.Code - if thisImp.Banner != nil { - thisImp.Banner.Format = []openrtb2.Format{format} - thisImp.Banner.W = &format.W - thisImp.Banner.H = &format.H - } - indexReq.Imp = []openrtb2.Imp{thisImp} - // Index spec says "adunit path representing ad server inventory" but we don't have this - // ext is DFP div ID and KV pairs if avail - //indexReq.Imp[i].Ext = json.RawMessage("{}") - - if indexReq.Site != nil { - // Any objects pointed to by indexReq *must not be mutated*, or we will get race conditions. - siteCopy := *indexReq.Site - siteCopy.Publisher = &openrtb2.Publisher{ID: params.SiteID} - indexReq.Site = &siteCopy - } - - bidType := "" - if thisImp.Banner != nil { - bidType = string(openrtb_ext.BidTypeBanner) - } else if thisImp.Video != nil { - bidType = string(openrtb_ext.BidTypeVideo) - } - j, _ := json.Marshal(indexReq) - request := callOneObject{requestJSON: *bytes.NewBuffer(j), width: format.W, height: format.H, bidType: bidType} - - // prioritize slots over sizes - if sizeIndex == 0 { - prioritizedRequests = append(prioritizedRequests, request) - } else { - requests = append(requests, request) - } - } - } - - // cap the number of requests to maxRequests - requests = append(prioritizedRequests, requests...) - if len(requests) > a.maxRequests { - requests = requests[:a.maxRequests] - } - - if len(requests) == 0 { - return nil, &errortypes.BadInput{ - Message: "Invalid ad unit/imp/size", - } - } - - ch := make(chan ixBidResult) - for _, request := range requests { - go func(bidder *pbs.PBSBidder, request callOneObject) { - result, err := a.callOne(ctx, request.requestJSON) - result.Request = &request - result.Error = err - if result.Bid != nil { - result.Bid.BidderCode = bidder.BidderCode - result.Bid.BidID = bidder.LookupBidID(result.Bid.AdUnitCode) - result.Bid.Width = request.width - result.Bid.Height = request.height - result.Bid.CreativeMediaType = request.bidType - - if result.Bid.BidID == "" { - result.Error = &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unknown ad unit code '%s'", result.Bid.AdUnitCode), - } - result.Bid = nil - } - } - ch <- result - }(bidder, request) - } - - bids := make(pbs.PBSBidSlice, 0) - for i := 0; i < len(requests); i++ { - result := <-ch - if result.Bid != nil && result.Bid.Price != 0 { - bids = append(bids, result.Bid) - } - - if req.IsDebug { - debug := &pbs.BidderDebug{ - RequestURI: a.URI, - RequestBody: result.Request.requestJSON.String(), - StatusCode: result.StatusCode, - ResponseBody: result.ResponseBody, - } - bidder.Debug = append(bidder.Debug, debug) - } - if result.Error != nil { - err = result.Error - } - } - - if len(bids) == 0 { - return nil, err - } - return bids, nil -} - -func (a *IxAdapter) callOne(ctx context.Context, reqJSON bytes.Buffer) (ixBidResult, error) { - var result ixBidResult - - httpReq, _ := http.NewRequest("POST", a.URI, &reqJSON) - httpReq.Header.Add("Content-Type", "application/json;charset=utf-8") - httpReq.Header.Add("Accept", "application/json") - - ixResp, err := ctxhttp.Do(ctx, a.http.Client, httpReq) - if err != nil { - return result, err - } - - result.StatusCode = ixResp.StatusCode - - if ixResp.StatusCode == http.StatusNoContent { - return result, nil - } - - if ixResp.StatusCode == http.StatusBadRequest { - return result, &errortypes.BadInput{ - Message: fmt.Sprintf("HTTP status: %d", ixResp.StatusCode), - } - } - - if ixResp.StatusCode != http.StatusOK { - return result, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("HTTP status: %d", ixResp.StatusCode), - } - } - - defer ixResp.Body.Close() - body, err := ioutil.ReadAll(ixResp.Body) - if err != nil { - return result, err - } - result.ResponseBody = string(body) - - var bidResp openrtb2.BidResponse - err = json.Unmarshal(body, &bidResp) - if err != nil { - return result, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Error parsing response: %v", err), - } - } - - if len(bidResp.SeatBid) == 0 { - return result, nil - } - if len(bidResp.SeatBid[0].Bid) == 0 { - return result, nil - } - bid := bidResp.SeatBid[0].Bid[0] - - pbid := pbs.PBSBid{ - AdUnitCode: bid.ImpID, - Price: bid.Price, - Adm: bid.AdM, - Creative_id: bid.CrID, - Width: bid.W, - Height: bid.H, - DealId: bid.DealID, - } - - result.Bid = &pbid - return result, nil -} - func (a *IxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { nImp := len(request.Imp) if nImp > a.maxRequests { @@ -454,14 +222,6 @@ func (a *IxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalReque return bidderResponse, errs } -func NewIxLegacyAdapter(config *adapters.HTTPAdapterConfig, endpoint string) *IxAdapter { - return &IxAdapter{ - http: adapters.NewHTTPAdapter(config), - URI: endpoint, - maxRequests: 20, - } -} - // Builder builds a new instance of the Ix adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { bidder := &IxAdapter{ diff --git a/adapters/ix/ix_test.go b/adapters/ix/ix_test.go index d292273a92c..fc1d0f9a0a2 100644 --- a/adapters/ix/ix_test.go +++ b/adapters/ix/ix_test.go @@ -1,22 +1,15 @@ package ix import ( - "context" "encoding/json" - "fmt" - "io/ioutil" - "math/rand" - "net/http" - "net/http/httptest" "testing" - "time" - "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/pbs" + + "github.com/mxmCherry/openrtb/v15/openrtb2" ) const endpoint string = "http://host/endpoint" @@ -31,698 +24,6 @@ func TestJsonSamples(t *testing.T) { } } -// Tests for the legacy, non-openrtb code. -// They can be removed after the legacy interface is deprecated. - -func getAdUnit() pbs.PBSAdUnit { - return pbs.PBSAdUnit{ - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - BidID: "bidid", - Sizes: []openrtb2.Format{ - { - W: 10, - H: 12, - }, - }, - Params: json.RawMessage("{\"siteId\":\"12\"}"), - } -} - -func getVideoAdUnit() pbs.PBSAdUnit { - return pbs.PBSAdUnit{ - Code: "unitCodeVideo", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}, - BidID: "bididvideo", - Sizes: []openrtb2.Format{ - { - W: 100, - H: 75, - }, - }, - Video: pbs.PBSVideo{ - Mimes: []string{"video/mp4"}, - Minduration: 15, - Maxduration: 30, - Startdelay: 5, - Skippable: 0, - PlaybackMethod: 1, - Protocols: []int8{2, 3}, - }, - Params: json.RawMessage("{\"siteId\":\"12\"}"), - } -} - -func getOpenRTBBid(i openrtb2.Imp) openrtb2.Bid { - return openrtb2.Bid{ - ID: fmt.Sprintf("%d", rand.Intn(1000)), - ImpID: i.ID, - Price: 1.0, - AdM: "Content", - } -} - -func newAdapter(endpoint string) *IxAdapter { - return NewIxLegacyAdapter(adapters.DefaultHTTPAdapterConfig, endpoint) -} - -func dummyIXServer(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - body, err := ioutil.ReadAll(r.Body) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - var breq openrtb2.BidRequest - err = json.Unmarshal(body, &breq) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - impression := breq.Imp[0] - - resp := openrtb2.BidResponse{ - SeatBid: []openrtb2.SeatBid{ - { - Bid: []openrtb2.Bid{ - getOpenRTBBid(impression), - }, - }, - }, - } - - js, err := json.Marshal(resp) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - w.Header().Set("Content-Type", "application/json") - w.Write(js) -} - -func TestIxSkipNoCookies(t *testing.T) { - if newAdapter(endpoint).SkipNoCookies() { - t.Fatalf("SkipNoCookies must return false") - } -} - -func TestIxInvalidCall(t *testing.T) { - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{} - _, err := newAdapter(endpoint).Call(ctx, &pbReq, &pbBidder) - if err == nil { - t.Fatalf("No error received for invalid request") - } -} - -func TestIxInvalidCallReqAppNil(t *testing.T) { - ctx := context.TODO() - pbReq := pbs.PBSRequest{ - App: &openrtb2.App{}, - } - pbBidder := pbs.PBSBidder{} - - _, err := newAdapter(endpoint).Call(ctx, &pbReq, &pbBidder) - if err == nil { - t.Fatalf("No error received for invalid request") - } -} - -func TestIxInvalidCallMissingSiteID(t *testing.T) { - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - adUnit := getAdUnit() - adUnit.Params = json.RawMessage("{}") - - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - adUnit, - }, - } - _, err := newAdapter(endpoint).Call(ctx, &pbReq, &pbBidder) - if err == nil { - t.Fatalf("No error received for request with missing siteId") - } -} - -func TestIxTimeout(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - <-time.After(2 * time.Millisecond) - }), - ) - defer server.Close() - - ctx, cancel := context.WithTimeout(context.Background(), 0) - defer cancel() - - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - getAdUnit(), - }, - } - _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder) - if err == nil || err != context.DeadlineExceeded { - t.Fatalf("Invalid timeout error received") - } -} - -func TestIxTimeoutMultipleSlots(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - body, err := ioutil.ReadAll(r.Body) - - var breq openrtb2.BidRequest - err = json.Unmarshal(body, &breq) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - impression := breq.Imp[0] - - resp := openrtb2.BidResponse{ - SeatBid: []openrtb2.SeatBid{ - { - Bid: []openrtb2.Bid{ - getOpenRTBBid(impression), - }, - }, - }, - } - - js, err := json.Marshal(resp) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // cancel the request before 2nd impression is returned - // delay to let 1st impression return successfully - if impression.ID == "unitCode2" { - <-time.After(10 * time.Millisecond) - cancel() - <-r.Context().Done() - } - - w.Header().Set("Content-Type", "application/json") - w.Write(js) - }), - ) - defer server.Close() - - pbReq := pbs.PBSRequest{} - - adUnit1 := getAdUnit() - adUnit2 := getAdUnit() - adUnit2.Code = "unitCode2" - adUnit2.Sizes = []openrtb2.Format{ - { - W: 8, - H: 10, - }, - } - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - adUnit1, - adUnit2, - }, - } - bids, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder) - - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - - if len(bids) != 1 { - t.Fatalf("Should have received one bid") - } - - bid := findBidByAdUnitCode(bids, adUnit1.Code) - if adUnit1.Sizes[0].H != bid.Height || adUnit1.Sizes[0].W != bid.Width { - t.Fatalf("Received the wrong size") - } -} - -func TestIxInvalidJsonResponse(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, "Blah") - }), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - getAdUnit(), - }, - } - _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder) - if err == nil { - t.Fatalf("No error received for invalid request") - } -} - -func TestIxInvalidStatusCode(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Send 404 - http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) - }), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{IsDebug: true} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - getAdUnit(), - }, - } - _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder) - if err == nil { - t.Fatalf("No error received for invalid request") - } -} - -func TestIxBadRequest(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Send 400 - http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) - }), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - getAdUnit(), - }, - } - _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder) - if err == nil { - t.Fatalf("No error received for bad request") - } -} - -func TestIxNoContent(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Send 204 - http.Error(w, http.StatusText(http.StatusNoContent), http.StatusNoContent) - }), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - getAdUnit(), - }, - } - - bids, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder) - if err != nil || bids != nil { - t.Fatalf("Must return nil for no content") - } -} - -func TestIxInvalidCallMissingSize(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(dummyIXServer), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - adUnit := getAdUnit() - adUnit.Sizes = []openrtb2.Format{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - adUnit, - }, - } - if _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder); err == nil { - t.Fatalf("Should not have gotten an error for missing/invalid size: %v", err) - } -} - -func TestIxInvalidCallEmptyBidIDResponse(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(dummyIXServer), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - adUnit := getAdUnit() - adUnit.BidID = "" - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - adUnit, - }, - } - if _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder); err == nil { - t.Fatalf("Should have gotten an error for unknown adunit code") - } -} - -func TestIxMismatchUnitCode(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - body, err := ioutil.ReadAll(r.Body) - - var breq openrtb2.BidRequest - err = json.Unmarshal(body, &breq) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - resp := openrtb2.BidResponse{ - SeatBid: []openrtb2.SeatBid{ - { - Bid: []openrtb2.Bid{ - { - ID: fmt.Sprintf("%d", rand.Intn(1000)), - ImpID: "unitCode_bogus", - Price: 1.0, - AdM: "Content", - W: 10, - H: 12, - }, - }, - }, - }, - } - - js, err := json.Marshal(resp) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - w.Header().Set("Content-Type", "application/json") - w.Write(js) - }), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - getAdUnit(), - }, - } - if _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder); err == nil { - t.Fatalf("Should have gotten an error for unknown adunit code") - } -} - -func TestNoSeatBid(t *testing.T) { - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - body, err := ioutil.ReadAll(r.Body) - - var breq openrtb2.BidRequest - err = json.Unmarshal(body, &breq) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - resp := openrtb2.BidResponse{} - - js, err := json.Marshal(resp) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - w.Header().Set("Content-Type", "application/json") - w.Write(js) - }), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - getAdUnit(), - }, - } - if _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder); err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } -} - -func TestNoSeatBidBid(t *testing.T) { - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - body, err := ioutil.ReadAll(r.Body) - - var breq openrtb2.BidRequest - err = json.Unmarshal(body, &breq) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - resp := openrtb2.BidResponse{ - SeatBid: []openrtb2.SeatBid{ - {}, - }, - } - - js, err := json.Marshal(resp) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - w.Header().Set("Content-Type", "application/json") - w.Write(js) - }), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - getAdUnit(), - }, - } - if _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder); err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } -} - -func TestIxInvalidParam(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(dummyIXServer), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - adUnit := getAdUnit() - adUnit.Params = json.RawMessage("Bogus invalid input") - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - adUnit, - }, - } - if _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder); err == nil { - t.Fatalf("Should have gotten an error for unrecognized params") - } -} - -func TestIxSingleSlotSingleValidSize(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(dummyIXServer), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - getAdUnit(), - }, - } - bids, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder) - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - - if len(bids) != 1 { - t.Fatalf("Should have received one bid") - } -} - -func TestIxTwoSlotValidSize(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(dummyIXServer), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - adUnit1 := getAdUnit() - adUnit2 := getVideoAdUnit() - adUnit2.Params = json.RawMessage("{\"siteId\":\"1111\"}") - - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - adUnit1, - adUnit2, - }, - } - bids, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder) - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - - if len(bids) != 2 { - t.Fatalf("Should have received two bid") - } - - bid := findBidByAdUnitCode(bids, adUnit1.Code) - if adUnit1.Sizes[0].H != bid.Height || adUnit1.Sizes[0].W != bid.Width { - t.Fatalf("Received the wrong size") - } - - bid = findBidByAdUnitCode(bids, adUnit2.Code) - if adUnit2.Sizes[0].H != bid.Height || adUnit2.Sizes[0].W != bid.Width { - t.Fatalf("Received the wrong size") - } -} - -func TestIxTwoSlotMultiSizeOnlyValidIXSizeResponse(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(dummyIXServer), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - adUnit := getAdUnit() - adUnit.Sizes = append(adUnit.Sizes, openrtb2.Format{W: 20, H: 22}) - - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - adUnit, - }, - } - bids, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder) - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - - if len(bids) != 2 { - t.Fatalf("Should have received 2 bids") - } - - for _, size := range adUnit.Sizes { - if !bidResponseForSizeExist(bids, size.H, size.W) { - t.Fatalf("Missing bid for specified size %d and %d", size.W, size.H) - } - } -} - -func bidResponseForSizeExist(bids pbs.PBSBidSlice, h, w int64) bool { - for _, v := range bids { - if v.Height == h && v.Width == w { - return true - } - } - return false -} - -func findBidByAdUnitCode(bids pbs.PBSBidSlice, c string) *pbs.PBSBid { - for _, v := range bids { - if v.AdUnitCode == c { - return v - } - } - return &pbs.PBSBid{} -} - -func TestIxMaxRequests(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(dummyIXServer), - ) - defer server.Close() - - adapter := newAdapter(server.URL) - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - adUnits := []pbs.PBSAdUnit{} - - for i := 0; i < adapter.maxRequests+1; i++ { - adUnits = append(adUnits, getAdUnit()) - } - - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: adUnits, - } - - bids, err := adapter.Call(ctx, &pbReq, &pbBidder) - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - - if len(bids) != adapter.maxRequests { - t.Fatalf("Should have received %d bid", adapter.maxRequests) - } -} - func TestIxMakeBidsWithCategoryDuration(t *testing.T) { bidder := &IxAdapter{} diff --git a/adapters/legacy.go b/adapters/legacy.go deleted file mode 100644 index 8b2221fe0ca..00000000000 --- a/adapters/legacy.go +++ /dev/null @@ -1,97 +0,0 @@ -package adapters - -import ( - "context" - "crypto/tls" - "net/http" - "time" - - "github.com/prebid/prebid-server/pbs" - "github.com/prebid/prebid-server/server/ssl" -) - -// This file contains some deprecated, legacy types. -// -// These support the `/auction` endpoint, but will be replaced by `/openrtb2/auction`. -// New demand partners should ignore this file, and implement the Bidder interface. - -// Adapter is a deprecated interface which connects prebid-server to a demand partner. -// PBS is currently being rewritten to use Bidder, and this will be removed after. -// Their primary purpose is to produce bids in response to Auction requests. -type Adapter interface { - // Name must be identical to the BidderName. - Name() string - // Determines whether this adapter should get callouts if there is not a synched user ID. - SkipNoCookies() bool - // Call produces bids which should be considered, given the auction params. - // - // In practice, implementations almost always make one call to an external server here. - // However, that is not a requirement for satisfying this interface. - // - // An error here will cause all bids to be ignored. If the error was caused by bad user input, - // this should return a BadInputError. If it was caused by bad server behavior - // (e.g. 500, unexpected response format, etc), this should return a BadServerResponseError. - Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) -} - -// HTTPAdapterConfig groups options which control how HTTP requests are made by adapters. -type HTTPAdapterConfig struct { - // See IdleConnTimeout on https://golang.org/pkg/net/http/#Transport - IdleConnTimeout time.Duration - // See MaxIdleConns on https://golang.org/pkg/net/http/#Transport - MaxConns int - // See MaxIdleConnsPerHost on https://golang.org/pkg/net/http/#Transport - MaxConnsPerHost int -} - -type HTTPAdapter struct { - Client *http.Client -} - -// DefaultHTTPAdapterConfig is an HTTPAdapterConfig that chooses sensible default values. -var DefaultHTTPAdapterConfig = &HTTPAdapterConfig{ - MaxConns: 50, - MaxConnsPerHost: 10, - IdleConnTimeout: 60 * time.Second, -} - -// NewHTTPAdapter creates an HTTPAdapter which obeys the rules given by the config, and -// has all the available SSL certs available in the project. -func NewHTTPAdapter(c *HTTPAdapterConfig) *HTTPAdapter { - ts := &http.Transport{ - MaxIdleConns: c.MaxConns, - MaxIdleConnsPerHost: c.MaxConnsPerHost, - IdleConnTimeout: c.IdleConnTimeout, - TLSClientConfig: &tls.Config{RootCAs: ssl.GetRootCAPool()}, - } - - return &HTTPAdapter{ - Client: &http.Client{ - Transport: ts, - }, - } -} - -// used for callOne (possibly pull all of the shared code here) -type CallOneResult struct { - StatusCode int - ResponseBody string - Bid *pbs.PBSBid - Error error -} - -type MisconfiguredAdapter struct { - TheName string - Err error -} - -func (b *MisconfiguredAdapter) Name() string { - return b.TheName -} -func (b *MisconfiguredAdapter) SkipNoCookies() bool { - return false -} - -func (b *MisconfiguredAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { - return nil, b.Err -} diff --git a/adapters/nextmillennium/nextmillennium.go b/adapters/nextmillennium/nextmillennium.go index 9171b21c714..68ca4dc22f2 100644 --- a/adapters/nextmillennium/nextmillennium.go +++ b/adapters/nextmillennium/nextmillennium.go @@ -119,25 +119,26 @@ func (adapter *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalR return nil, []error{&errortypes.BadServerResponse{Message: msg}} } + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { msg = fmt.Sprintf("Bad server response: %d", err) return nil, []error{&errortypes.BadServerResponse{Message: msg}} } - if len(bidResp.SeatBid) != 1 { - var msg = fmt.Sprintf("Invalid SeatBids count: %d", len(bidResp.SeatBid)) - return nil, []error{&errortypes.BadServerResponse{Message: msg}} + + if len(bidResp.SeatBid) == 0 { + return nil, nil } - seatBid := bidResp.SeatBid[0] - bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid)) + bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) - for i := 0; i < len(seatBid.Bid); i++ { - bid := seatBid.Bid[i] - bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ - Bid: &bid, - BidType: openrtb_ext.BidTypeBanner, - }) + for _, sb := range bidResp.SeatBid { + for i := range sb.Bid { + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &sb.Bid[i], + BidType: openrtb_ext.BidTypeBanner, + }) + } } return bidResponse, nil } diff --git a/adapters/nextmillennium/nextmillenniumtest/supplemental/bad-response.json b/adapters/nextmillennium/nextmillenniumtest/supplemental/empty-seatbid.json similarity index 88% rename from adapters/nextmillennium/nextmillenniumtest/supplemental/bad-response.json rename to adapters/nextmillennium/nextmillenniumtest/supplemental/empty-seatbid.json index 64de88abc40..8c344d57214 100644 --- a/adapters/nextmillennium/nextmillenniumtest/supplemental/bad-response.json +++ b/adapters/nextmillennium/nextmillenniumtest/supplemental/empty-seatbid.json @@ -49,10 +49,5 @@ } } ], - "expectedMakeBidsErrors": [ - { - "value": "Invalid SeatBids count: 0", - "comparison": "literal" - } - ] + "expectedBidResponses": [] } diff --git a/adapters/openrtb_util.go b/adapters/openrtb_util.go index 6aa07c6b764..5587cac7ef2 100644 --- a/adapters/openrtb_util.go +++ b/adapters/openrtb_util.go @@ -2,173 +2,60 @@ package adapters import ( "encoding/json" - + "errors" + "fmt" "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/pbs" + "github.com/prebid/prebid-server/openrtb_ext" ) -func min(x, y int) int { - if x < y { - return x - } - return y -} - -func mediaTypeInSlice(t pbs.MediaType, list []pbs.MediaType) bool { - for _, b := range list { - if b == t { - return true - } +func ExtractAdapterReqBidderParams(bidRequest *openrtb2.BidRequest) (map[string]json.RawMessage, error) { + if bidRequest == nil { + return nil, errors.New("error bidRequest should not be nil") } - return false -} -func commonMediaTypes(l1 []pbs.MediaType, l2 []pbs.MediaType) []pbs.MediaType { - res := make([]pbs.MediaType, min(len(l1), len(l2))) - i := 0 - for _, b := range l1 { - if mediaTypeInSlice(b, l2) { - res[i] = b - i = i + 1 + reqExt := &openrtb_ext.ExtRequest{} + if len(bidRequest.Ext) > 0 { + err := json.Unmarshal(bidRequest.Ext, &reqExt) + if err != nil { + return nil, fmt.Errorf("error decoding Request.ext : %s", err.Error()) } } - return res[:i] -} -func makeBanner(unit pbs.PBSAdUnit) *openrtb2.Banner { - return &openrtb2.Banner{ - W: openrtb2.Int64Ptr(unit.Sizes[0].W), - H: openrtb2.Int64Ptr(unit.Sizes[0].H), - Format: copyFormats(unit.Sizes), // defensive copy because adapters may mutate Imps, and this is shared data - TopFrame: unit.TopFrame, + if reqExt.Prebid.BidderParams == nil { + return nil, nil } -} -func makeVideo(unit pbs.PBSAdUnit) *openrtb2.Video { - // empty mimes array is a sign of uninitialized Video object - if len(unit.Video.Mimes) < 1 { - return nil + var bidderParams map[string]json.RawMessage + err := json.Unmarshal(reqExt.Prebid.BidderParams, &bidderParams) + if err != nil { + return nil, err } - mimes := make([]string, len(unit.Video.Mimes)) - copy(mimes, unit.Video.Mimes) - pbm := make([]openrtb2.PlaybackMethod, 1) - //this will become int8 soon, so we only care about the first index in the array - pbm[0] = openrtb2.PlaybackMethod(unit.Video.PlaybackMethod) - protocols := make([]openrtb2.Protocol, 0, len(unit.Video.Protocols)) - for _, protocol := range unit.Video.Protocols { - protocols = append(protocols, openrtb2.Protocol(protocol)) - } - return &openrtb2.Video{ - MIMEs: mimes, - MinDuration: unit.Video.Minduration, - MaxDuration: unit.Video.Maxduration, - W: unit.Sizes[0].W, - H: unit.Sizes[0].H, - StartDelay: openrtb2.StartDelay(unit.Video.Startdelay).Ptr(), - PlaybackMethod: pbm, - Protocols: protocols, - } + return bidderParams, nil } -// adapters.MakeOpenRTBGeneric makes an openRTB request from the PBS-specific structs. -// -// Any objects pointed to by the returned BidRequest *must not be mutated*, or we will get race conditions. -// The only exception is the Imp property, whose objects will be created new by this method and can be mutated freely. -func MakeOpenRTBGeneric(req *pbs.PBSRequest, bidder *pbs.PBSBidder, bidderFamily string, allowedMediatypes []pbs.MediaType) (openrtb2.BidRequest, error) { - imps := make([]openrtb2.Imp, 0, len(bidder.AdUnits)*len(allowedMediatypes)) - for _, unit := range bidder.AdUnits { - if len(unit.Sizes) <= 0 { - continue - } - unitMediaTypes := commonMediaTypes(unit.MediaTypes, allowedMediatypes) - if len(unitMediaTypes) == 0 { - continue - } - - newImp := openrtb2.Imp{ - ID: unit.Code, - Secure: &req.Secure, - Instl: unit.Instl, - } - for _, mType := range unitMediaTypes { - switch mType { - case pbs.MEDIA_TYPE_BANNER: - newImp.Banner = makeBanner(unit) - case pbs.MEDIA_TYPE_VIDEO: - newImp.Video = makeVideo(unit) - // It's strange to error here... but preserves legacy behavior in legacy code. See #603. - if newImp.Video == nil { - return openrtb2.BidRequest{}, &errortypes.BadInput{ - Message: "Invalid AdUnit: VIDEO media type with no video data", - } - } - } - } - if newImp.Banner != nil || newImp.Video != nil { - imps = append(imps, newImp) - } +func ExtractReqExtBidderParams(bidReq *openrtb2.BidRequest) (map[string]map[string]json.RawMessage, error) { + if bidReq == nil { + return nil, errors.New("error bidRequest should not be nil") } - if len(imps) < 1 { - return openrtb2.BidRequest{}, &errortypes.BadInput{ - Message: "openRTB bids need at least one Imp", + reqExt := &openrtb_ext.ExtRequest{} + if len(bidReq.Ext) > 0 { + err := json.Unmarshal(bidReq.Ext, &reqExt) + if err != nil { + return nil, fmt.Errorf("error decoding Request.ext : %s", err.Error()) } } - if req.App != nil { - return openrtb2.BidRequest{ - ID: req.Tid, - Imp: imps, - App: req.App, - Device: req.Device, - User: req.User, - Source: &openrtb2.Source{ - TID: req.Tid, - }, - AT: 1, - TMax: req.TimeoutMillis, - Regs: req.Regs, - }, nil + if reqExt.Prebid.BidderParams == nil { + return nil, nil } - buyerUID, _, _ := req.Cookie.GetUID(bidderFamily) - id, _, _ := req.Cookie.GetUID("adnxs") - - var userExt json.RawMessage - if req.User != nil { - userExt = req.User.Ext + var bidderParams map[string]map[string]json.RawMessage + err := json.Unmarshal(reqExt.Prebid.BidderParams, &bidderParams) + if err != nil { + return nil, err } - return openrtb2.BidRequest{ - ID: req.Tid, - Imp: imps, - Site: &openrtb2.Site{ - Domain: req.Domain, - Page: req.Url, - }, - Device: req.Device, - User: &openrtb2.User{ - BuyerUID: buyerUID, - ID: id, - Ext: userExt, - }, - Source: &openrtb2.Source{ - FD: 1, // upstream, aka header - TID: req.Tid, - }, - AT: 1, - TMax: req.TimeoutMillis, - Regs: req.Regs, - }, nil -} - -func copyFormats(sizes []openrtb2.Format) []openrtb2.Format { - sizesCopy := make([]openrtb2.Format, len(sizes)) - for i := 0; i < len(sizes); i++ { - sizesCopy[i] = sizes[i] - sizesCopy[i].Ext = append([]byte(nil), sizes[i].Ext...) - } - return sizesCopy + return bidderParams, nil } diff --git a/adapters/openrtb_util_test.go b/adapters/openrtb_util_test.go index 035f4d9b679..e09149a7fc3 100644 --- a/adapters/openrtb_util_test.go +++ b/adapters/openrtb_util_test.go @@ -1,543 +1,117 @@ package adapters import ( - "testing" - "encoding/json" - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/pbs" - "github.com/prebid/prebid-server/usersync" - "github.com/stretchr/testify/assert" + "reflect" + "testing" ) -func TestCommonMediaTypes(t *testing.T) { - mt1 := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER} - mt2 := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} - common := commonMediaTypes(mt1, mt2) - assert.Equal(t, len(common), 1) - assert.Equal(t, common[0], pbs.MEDIA_TYPE_BANNER) - - common2 := commonMediaTypes(mt2, mt1) - assert.Equal(t, len(common2), 1) - assert.Equal(t, common2[0], pbs.MEDIA_TYPE_BANNER) - - mt3 := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} - mt4 := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} - common3 := commonMediaTypes(mt3, mt4) - assert.Equal(t, len(common3), 2) - - mt5 := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER} - mt6 := []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO} - common4 := commonMediaTypes(mt5, mt6) - assert.Equal(t, len(common4), 0) -} - -func TestOpenRTB(t *testing.T) { - - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 10, - H: 12, - }, - }, - Instl: 1, - }, - }, - } - resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) - - assert.Equal(t, err, nil) - assert.Equal(t, resp.Imp[0].ID, "unitCode") - assert.EqualValues(t, *resp.Imp[0].Banner.W, 10) - assert.EqualValues(t, *resp.Imp[0].Banner.H, 12) - assert.EqualValues(t, resp.Imp[0].Instl, 1) - - assert.Nil(t, resp.User.Ext) - assert.Nil(t, resp.Regs) -} - -func TestOpenRTBVideo(t *testing.T) { - - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}, - Sizes: []openrtb2.Format{ - { - W: 10, - H: 12, - }, - }, - Video: pbs.PBSVideo{ - Mimes: []string{"video/mp4"}, - Minduration: 15, - Maxduration: 30, - Startdelay: 5, - Skippable: 0, - PlaybackMethod: 1, - }, - }, - }, - } - resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}) - - assert.Equal(t, err, nil) - assert.Equal(t, resp.Imp[0].ID, "unitCode") - assert.EqualValues(t, resp.Imp[0].Video.MaxDuration, 30) - assert.EqualValues(t, resp.Imp[0].Video.MinDuration, 15) - assert.EqualValues(t, *resp.Imp[0].Video.StartDelay, openrtb2.StartDelay(5)) - assert.EqualValues(t, resp.Imp[0].Video.PlaybackMethod, []openrtb2.PlaybackMethod{openrtb2.PlaybackMethod(1)}) - assert.EqualValues(t, resp.Imp[0].Video.MIMEs, []string{"video/mp4"}) -} - -func TestOpenRTBVideoNoVideoData(t *testing.T) { - - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}, - Sizes: []openrtb2.Format{ - { - W: 10, - H: 12, - }, - }, - }, - }, - } - _, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}) - - assert.NotEqual(t, err, nil) - -} - -func TestOpenRTBVideoFilteredOut(t *testing.T) { - - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}, - Sizes: []openrtb2.Format{ - { - W: 10, - H: 12, - }, - }, - Video: pbs.PBSVideo{ - Mimes: []string{"video/mp4"}, - Minduration: 15, - Maxduration: 30, - Startdelay: 5, - Skippable: 0, - PlaybackMethod: 1, - }, - }, - { - Code: "unitCode2", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 10, - H: 12, - }, - }, - }, - }, - } - resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) - assert.Equal(t, err, nil) - for i := 0; i < len(resp.Imp); i++ { - if resp.Imp[i].Video != nil { - t.Errorf("No video impressions should exist.") - } - } -} - -func TestOpenRTBMultiMediaImp(t *testing.T) { - - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO, pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 10, - H: 12, - }, - }, - Video: pbs.PBSVideo{ - Mimes: []string{"video/mp4"}, - Minduration: 15, - Maxduration: 30, - Startdelay: 5, - Skippable: 0, - PlaybackMethod: 1, - }, - }, - }, - } - resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO, pbs.MEDIA_TYPE_BANNER}) - assert.Equal(t, err, nil) - assert.Equal(t, len(resp.Imp), 1) - assert.Equal(t, resp.Imp[0].ID, "unitCode") - assert.EqualValues(t, *resp.Imp[0].Banner.W, 10) - assert.EqualValues(t, resp.Imp[0].Video.W, 10) - assert.EqualValues(t, resp.Imp[0].Video.MaxDuration, 30) - assert.EqualValues(t, resp.Imp[0].Video.MinDuration, 15) -} - -func TestOpenRTBMultiMediaImpFiltered(t *testing.T) { - - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO, pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 10, - H: 12, - }, - }, - Video: pbs.PBSVideo{ - Mimes: []string{"video/mp4"}, - Minduration: 15, - Maxduration: 30, - Startdelay: 5, - Skippable: 0, - PlaybackMethod: 1, - }, - }, - }, - } - resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) - assert.Equal(t, err, nil) - assert.Equal(t, len(resp.Imp), 1) - assert.Equal(t, resp.Imp[0].ID, "unitCode") - assert.EqualValues(t, *resp.Imp[0].Banner.W, 10) - assert.EqualValues(t, resp.Imp[0].Video, (*openrtb2.Video)(nil)) -} - -func TestOpenRTBNoSize(t *testing.T) { - - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - }, - }, - } - _, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) - if err == nil { - t.Errorf("Bids without impressions should not be allowed.") - } -} - -func TestOpenRTBMobile(t *testing.T) { - pbReq := pbs.PBSRequest{ - AccountID: "test_account_id", - Tid: "test_tid", - CacheMarkup: 1, - SortBids: 1, - MaxKeyLength: 20, - Secure: 1, - TimeoutMillis: 1000, - App: &openrtb2.App{ - Bundle: "AppNexus.PrebidMobileDemo", - Publisher: &openrtb2.Publisher{ - ID: "1995257847363113", - }, - }, - Device: &openrtb2.Device{ - UA: "test_ua", - IP: "test_ip", - Make: "test_make", - Model: "test_model", - IFA: "test_ifa", - }, - User: &openrtb2.User{ - BuyerUID: "test_buyeruid", - }, +func TestExtractAdapterReqBidderParams(t *testing.T) { + type args struct { + bidRequest *openrtb2.BidRequest } - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 300, - H: 250, - }, - }, + tests := []struct { + name string + args args + want map[string]json.RawMessage + wantErr bool + }{ + { + name: "extract bidder params from nil req", + args: args{ + bidRequest: nil, }, + want: nil, + wantErr: true, }, - } - resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) - assert.Equal(t, err, nil) - assert.Equal(t, resp.Imp[0].ID, "unitCode") - assert.EqualValues(t, *resp.Imp[0].Banner.W, 300) - assert.EqualValues(t, *resp.Imp[0].Banner.H, 250) - - assert.EqualValues(t, resp.App.Bundle, "AppNexus.PrebidMobileDemo") - assert.EqualValues(t, resp.App.Publisher.ID, "1995257847363113") - assert.EqualValues(t, resp.User.BuyerUID, "test_buyeruid") - - assert.EqualValues(t, resp.Device.UA, "test_ua") - assert.EqualValues(t, resp.Device.IP, "test_ip") - assert.EqualValues(t, resp.Device.Make, "test_make") - assert.EqualValues(t, resp.Device.Model, "test_model") - assert.EqualValues(t, resp.Device.IFA, "test_ifa") -} - -func TestOpenRTBEmptyUser(t *testing.T) { - pbReq := pbs.PBSRequest{ - User: &openrtb2.User{}, - } - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode2", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 10, - H: 12, - }, - }, + { + name: "extract bidder params from nil req.Ext", + args: args{ + bidRequest: &openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{}}`)}, }, + want: nil, + wantErr: false, }, + { + name: "extract bidder params from req.Ext for input request in adapter code", + args: args{ + bidRequest: &openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"bidderparams": {"profile": 1234, "version": 1}}}`)}, + }, + want: map[string]json.RawMessage{"profile": json.RawMessage(`1234`), "version": json.RawMessage(`1`)}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ExtractAdapterReqBidderParams(tt.args.bidRequest) + if (err != nil) != tt.wantErr { + t.Errorf("ExtractReqExtBidderParams() error = %v, wantErr = %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ExtractReqExtBidderParams() got = %v, want = %v", got, tt.want) + } + }) } - resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) - assert.Equal(t, err, nil) - assert.EqualValues(t, resp.User, &openrtb2.User{}) } -func TestOpenRTBUserWithCookie(t *testing.T) { - pbsCookie := usersync.NewCookie() - pbsCookie.TrySync("test", "abcde") - pbReq := pbs.PBSRequest{ - User: &openrtb2.User{}, +func TestExtractReqExtBidderParams(t *testing.T) { + type args struct { + request *openrtb2.BidRequest } - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 300, - H: 250, - }, - }, + tests := []struct { + name string + args args + want map[string]map[string]json.RawMessage + wantErr bool + }{ + { + name: "extract bidder params from nil req", + args: args{ + request: nil, }, + want: nil, + wantErr: true, }, - } - pbReq.Cookie = pbsCookie - resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) - assert.Equal(t, err, nil) - assert.EqualValues(t, resp.User.BuyerUID, "abcde") -} - -func TestSizesCopy(t *testing.T) { - formats := []openrtb2.Format{ { - W: 10, + name: "extract bidder params from nil req.Ext.prebid", + args: args{ + request: &openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{}}`)}, + }, + want: nil, + wantErr: false, }, { - Ext: []byte{0x5}, - }, - } - clone := copyFormats(formats) - - if len(clone) != 2 { - t.Error("The copy should have 2 elements") - } - if clone[0].W != 10 { - t.Error("The Format's width should be preserved.") - } - if len(clone[1].Ext) != 1 || clone[1].Ext[0] != 0x5 { - t.Error("The Format's Ext should be preserved.") - } - if &formats[0] == &clone[0] || &formats[1] == &clone[1] { - t.Error("The Format elements should not point to the same instance") - } - if &formats[0] == &clone[0] || &formats[1] == &clone[1] { - t.Error("The Format elements should not point to the same instance") - } - if &formats[1].Ext[0] == &clone[1].Ext[0] { - t.Error("The Format.Ext property should point to two different instances") - } -} - -func TestMakeVideo(t *testing.T) { - adUnit := pbs.PBSAdUnit{ - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}, - Sizes: []openrtb2.Format{ - { - W: 10, - H: 12, + name: "extract bidder params from nil req.Ext", + args: args{ + request: &openrtb2.BidRequest{Ext: nil}, }, + want: nil, + wantErr: false, }, - Video: pbs.PBSVideo{ - Mimes: []string{"video/mp4"}, - Minduration: 15, - Maxduration: 30, - Startdelay: 5, - Skippable: 0, - PlaybackMethod: 1, - Protocols: []int8{1, 2, 5, 6}, - }, - } - video := makeVideo(adUnit) - assert.EqualValues(t, video.MinDuration, 15) - assert.EqualValues(t, video.MaxDuration, 30) - assert.EqualValues(t, *video.StartDelay, openrtb2.StartDelay(5)) - assert.EqualValues(t, len(video.PlaybackMethod), 1) - assert.EqualValues(t, len(video.Protocols), 4) -} - -func TestGDPR(t *testing.T) { - - rawUserExt := json.RawMessage(`{"consent": "12345"}`) - userExt, _ := json.Marshal(rawUserExt) - - rawRegsExt := json.RawMessage(`{"gdpr": 1}`) - regsExt, _ := json.Marshal(rawRegsExt) - - pbReq := pbs.PBSRequest{ - User: &openrtb2.User{ - Ext: userExt, - }, - Regs: &openrtb2.Regs{ - Ext: regsExt, - }, - } - - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 10, - H: 12, - }, - }, - Instl: 1, + { + name: "extract bidder params from req.Ext for input request before adapter code", + args: args{ + request: &openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"bidderparams": {"pubmatic": {"profile": 1234, "version": 1}, "appnexus": {"key1": 123, "key2": {"innerKey1":"innerValue1"} } }}}`)}, }, - }, - } - resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) - - assert.Equal(t, err, nil) - assert.Equal(t, resp.Imp[0].ID, "unitCode") - assert.EqualValues(t, *resp.Imp[0].Banner.W, 10) - assert.EqualValues(t, *resp.Imp[0].Banner.H, 12) - assert.EqualValues(t, resp.Imp[0].Instl, 1) - - assert.EqualValues(t, resp.User.Ext, userExt) - assert.EqualValues(t, resp.Regs.Ext, regsExt) -} - -func TestGDPRMobile(t *testing.T) { - rawUserExt := json.RawMessage(`{"consent": "12345"}`) - userExt, _ := json.Marshal(rawUserExt) - - rawRegsExt := json.RawMessage(`{"gdpr": 1}`) - regsExt, _ := json.Marshal(rawRegsExt) - - pbReq := pbs.PBSRequest{ - AccountID: "test_account_id", - Tid: "test_tid", - CacheMarkup: 1, - SortBids: 1, - MaxKeyLength: 20, - Secure: 1, - TimeoutMillis: 1000, - App: &openrtb2.App{ - Bundle: "AppNexus.PrebidMobileDemo", - Publisher: &openrtb2.Publisher{ - ID: "1995257847363113", + want: map[string]map[string]json.RawMessage{ + "pubmatic": {"profile": json.RawMessage(`1234`), "version": json.RawMessage(`1`)}, + "appnexus": {"key1": json.RawMessage(`123`), "key2": json.RawMessage(`{"innerKey1":"innerValue1"}`)}, }, - }, - Device: &openrtb2.Device{ - UA: "test_ua", - IP: "test_ip", - Make: "test_make", - Model: "test_model", - IFA: "test_ifa", - }, - User: &openrtb2.User{ - BuyerUID: "test_buyeruid", - Ext: userExt, - }, - Regs: &openrtb2.Regs{ - Ext: regsExt, + wantErr: false, }, } - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 300, - H: 250, - }, - }, - }, - }, + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ExtractReqExtBidderParams(tt.args.request) + if (err != nil) != tt.wantErr { + t.Errorf("ExtractReqExtBidderParams() error = %v, wantErr = %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ExtractReqExtBidderParams() got = %v, want = %v", got, tt.want) + } + }) } - resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) - assert.Equal(t, err, nil) - assert.Equal(t, resp.Imp[0].ID, "unitCode") - assert.EqualValues(t, *resp.Imp[0].Banner.W, 300) - assert.EqualValues(t, *resp.Imp[0].Banner.H, 250) - - assert.EqualValues(t, resp.App.Bundle, "AppNexus.PrebidMobileDemo") - assert.EqualValues(t, resp.App.Publisher.ID, "1995257847363113") - assert.EqualValues(t, resp.User.BuyerUID, "test_buyeruid") - - assert.EqualValues(t, resp.Device.UA, "test_ua") - assert.EqualValues(t, resp.Device.IP, "test_ip") - assert.EqualValues(t, resp.Device.Make, "test_make") - assert.EqualValues(t, resp.Device.Model, "test_model") - assert.EqualValues(t, resp.Device.IFA, "test_ifa") - - assert.EqualValues(t, resp.User.Ext, userExt) - assert.EqualValues(t, resp.Regs.Ext, regsExt) } diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go index c2e9fffa0fe..799d3364f76 100644 --- a/adapters/pubmatic/pubmatic.go +++ b/adapters/pubmatic/pubmatic.go @@ -1,11 +1,8 @@ package pubmatic import ( - "bytes" - "context" "encoding/json" "fmt" - "io/ioutil" "net/http" "strconv" "strings" @@ -16,46 +13,28 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/pbs" - "golang.org/x/net/context/ctxhttp" ) const MAX_IMPRESSIONS_PUBMATIC = 30 type PubmaticAdapter struct { - http *adapters.HTTPAdapter - URI string + URI string } -// used for cookies and such -func (a *PubmaticAdapter) Name() string { - return "pubmatic" -} - -func (a *PubmaticAdapter) SkipNoCookies() bool { - return false +type pubmaticBidExt struct { + BidType *int `json:"BidType,omitempty"` + VideoCreativeInfo *pubmaticBidExtVideo `json:"video,omitempty"` } -// Below is bidder specific parameters for pubmatic adaptor, -// PublisherId and adSlot are mandatory parameters, others are optional parameters -// Keywords is bid specific parameter, -// WrapExt needs to be sent once per bid request -type pubmaticParams struct { - PublisherId string `json:"publisherId"` - AdSlot string `json:"adSlot"` - WrapExt json.RawMessage `json:"wrapper,omitempty"` - Keywords map[string]string `json:"keywords,omitempty"` +type pubmaticWrapperExt struct { + ProfileID int `json:"profile,omitempty"` + VersionID int `json:"version,omitempty"` } type pubmaticBidExtVideo struct { Duration *int `json:"duration,omitempty"` } -type pubmaticBidExt struct { - BidType *int `json:"BidType,omitempty"` - VideoCreativeInfo *pubmaticBidExtVideo `json:"video,omitempty"` -} - type ExtImpBidderPubmatic struct { adapters.ExtImpBidder Data *ExtData `json:"data,omitempty"` @@ -72,16 +51,6 @@ type ExtAdServer struct { } const ( - INVALID_PARAMS = "Invalid BidParam" - MISSING_PUBID = "Missing PubID" - MISSING_ADSLOT = "Missing AdSlot" - INVALID_WRAPEXT = "Invalid WrapperExt" - INVALID_ADSIZE = "Invalid AdSize" - INVALID_WIDTH = "Invalid Width" - INVALID_HEIGHT = "Invalid Height" - INVALID_MEDIATYPE = "Invalid MediaType" - INVALID_ADSLOT = "Invalid AdSlot" - dctrKeyName = "key_val" pmZoneIDKeyName = "pmZoneId" pmZoneIDKeyNameOld = "pmZoneID" @@ -89,264 +58,54 @@ const ( AdServerGAM = "gam" ) -func PrepareLogMessage(tID, pubId, adUnitId, bidID, details string, args ...interface{}) string { - return fmt.Sprintf("[PUBMATIC] ReqID [%s] PubID [%s] AdUnit [%s] BidID [%s] %s \n", - tID, pubId, adUnitId, bidID, details) -} +func (a *PubmaticAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + errs := make([]error, 0, len(request.Imp)) -func (a *PubmaticAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { - mediaTypes := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} - pbReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), mediaTypes) + pubID := "" + var wrapperExt *pubmaticWrapperExt + extractWrapperExtFromImp := true + extractPubIDFromImp := true + wrapperExt, err := extractPubmaticWrapperExtFromRequest(request) if err != nil { - logf("[PUBMATIC] Failed to make ortb request for request id [%s] \n", pbReq.ID) - return nil, err + return nil, []error{err} } - - var errState []string - adSlotFlag := false - pubId := "" - wrapExt := "" - if len(bidder.AdUnits) > MAX_IMPRESSIONS_PUBMATIC { - logf("[PUBMATIC] First %d impressions will be considered from request tid %s\n", - MAX_IMPRESSIONS_PUBMATIC, pbReq.ID) + if wrapperExt != nil && wrapperExt.ProfileID != 0 && wrapperExt.VersionID != 0 { + extractWrapperExtFromImp = false } - for i, unit := range bidder.AdUnits { - var params pubmaticParams - err := json.Unmarshal(unit.Params, ¶ms) - if err != nil { - errState = append(errState, fmt.Sprintf("BidID:%s;Error:%s;param:%s", unit.BidID, INVALID_PARAMS, unit.Params)) - logf(PrepareLogMessage(pbReq.ID, params.PublisherId, unit.Code, unit.BidID, - fmt.Sprintf("Ignored bid: invalid JSON [%s] err [%s]", unit.Params, err.Error()))) - continue - } - - if params.PublisherId == "" { - errState = append(errState, fmt.Sprintf("BidID:%s;Error:%s;param:%s", unit.BidID, MISSING_PUBID, unit.Params)) - logf(PrepareLogMessage(pbReq.ID, params.PublisherId, unit.Code, unit.BidID, - fmt.Sprintf("Ignored bid: Publisher Id missing"))) - continue - } - pubId = params.PublisherId + for i := 0; i < len(request.Imp); i++ { + wrapperExtFromImp, pubIDFromImp, err := parseImpressionObject(&request.Imp[i], extractWrapperExtFromImp, extractPubIDFromImp) - if params.AdSlot == "" { - errState = append(errState, fmt.Sprintf("BidID:%s;Error:%s;param:%s", unit.BidID, MISSING_ADSLOT, unit.Params)) - logf(PrepareLogMessage(pbReq.ID, params.PublisherId, unit.Code, unit.BidID, - fmt.Sprintf("Ignored bid: adSlot missing"))) + // If the parsing is failed, remove imp and add the error. + if err != nil { + errs = append(errs, err) + request.Imp = append(request.Imp[:i], request.Imp[i+1:]...) + i-- continue } - // Parse Wrapper Extension i.e. ProfileID and VersionID only once per request - if wrapExt == "" && len(params.WrapExt) != 0 { - var wrapExtMap map[string]int - err := json.Unmarshal([]byte(params.WrapExt), &wrapExtMap) - if err != nil { - errState = append(errState, fmt.Sprintf("BidID:%s;Error:%s;param:%s", unit.BidID, INVALID_WRAPEXT, unit.Params)) - logf(PrepareLogMessage(pbReq.ID, params.PublisherId, unit.Code, unit.BidID, - fmt.Sprintf("Ignored bid: Wrapper Extension Invalid"))) - continue - } - wrapExt = string(params.WrapExt) - } - - adSlotStr := strings.TrimSpace(params.AdSlot) - adSlot := strings.Split(adSlotStr, "@") - if len(adSlot) == 2 && adSlot[0] != "" && adSlot[1] != "" { - // Fixes some segfaults. Since this is legacy code, I'm not looking into it too deeply - if len(pbReq.Imp) <= i { - break - } - if pbReq.Imp[i].Banner != nil { - adSize := strings.Split(strings.ToLower(strings.TrimSpace(adSlot[1])), "x") - if len(adSize) == 2 { - width, err := strconv.Atoi(strings.TrimSpace(adSize[0])) - if err != nil { - errState = append(errState, fmt.Sprintf("BidID:%s;Error:%s;param:%s", unit.BidID, INVALID_WIDTH, unit.Params)) - logf(PrepareLogMessage(pbReq.ID, params.PublisherId, unit.Code, unit.BidID, - fmt.Sprintf("Ignored bid: invalid adSlot width [%s]", adSize[0]))) - continue - } - - heightStr := strings.Split(strings.TrimSpace(adSize[1]), ":") - height, err := strconv.Atoi(strings.TrimSpace(heightStr[0])) - if err != nil { - errState = append(errState, fmt.Sprintf("BidID:%s;Error:%s;param:%s", unit.BidID, INVALID_HEIGHT, unit.Params)) - logf(PrepareLogMessage(pbReq.ID, params.PublisherId, unit.Code, unit.BidID, - fmt.Sprintf("Ignored bid: invalid adSlot height [%s]", heightStr[0]))) - continue - } - - pbReq.Imp[i].TagID = strings.TrimSpace(adSlot[0]) - pbReq.Imp[i].Banner.W = openrtb2.Int64Ptr(int64(width)) - pbReq.Imp[i].Banner.H = openrtb2.Int64Ptr(int64(height)) - - if len(params.Keywords) != 0 { - kvstr := prepareImpressionExt(params.Keywords) - pbReq.Imp[i].Ext = json.RawMessage([]byte(kvstr)) - } else { - pbReq.Imp[i].Ext = nil - } - - adSlotFlag = true - } else { - errState = append(errState, fmt.Sprintf("BidID:%s;Error:%s;param:%s", unit.BidID, INVALID_ADSIZE, unit.Params)) - logf(PrepareLogMessage(pbReq.ID, params.PublisherId, unit.Code, unit.BidID, - fmt.Sprintf("Ignored bid: invalid adSize [%s]", adSize))) - continue + if extractWrapperExtFromImp { + if wrapperExtFromImp != nil { + if wrapperExt == nil { + wrapperExt = &pubmaticWrapperExt{} } - } else { - errState = append(errState, fmt.Sprintf("BidID:%s;Error:%s;param:%s", unit.BidID, INVALID_MEDIATYPE, unit.Params)) - logf(PrepareLogMessage(pbReq.ID, params.PublisherId, unit.Code, unit.BidID, - fmt.Sprintf("Ignored bid: invalid Media Type"))) - continue - } - } else { - errState = append(errState, fmt.Sprintf("BidID:%s;Error:%s;param:%s", unit.BidID, INVALID_ADSLOT, unit.Params)) - logf(PrepareLogMessage(pbReq.ID, params.PublisherId, unit.Code, unit.BidID, - fmt.Sprintf("Ignored bid: invalid adSlot [%s]", params.AdSlot))) - continue - } - - if pbReq.Site != nil { - siteCopy := *pbReq.Site - siteCopy.Publisher = &openrtb2.Publisher{ID: params.PublisherId, Domain: req.Domain} - pbReq.Site = &siteCopy - } - if pbReq.App != nil { - appCopy := *pbReq.App - appCopy.Publisher = &openrtb2.Publisher{ID: params.PublisherId, Domain: req.Domain} - pbReq.App = &appCopy - } - } - - if !(adSlotFlag) { - return nil, &errortypes.BadInput{ - Message: "Incorrect adSlot / Publisher params, Error list: [" + strings.Join(errState, ",") + "]", - } - } - - if wrapExt != "" { - rawExt := fmt.Sprintf("{\"wrapper\": %s}", wrapExt) - pbReq.Ext = json.RawMessage(rawExt) - } - - reqJSON, err := json.Marshal(pbReq) - - debug := &pbs.BidderDebug{ - RequestURI: a.URI, - } - - if req.IsDebug { - debug.RequestBody = string(reqJSON) - bidder.Debug = append(bidder.Debug, debug) - } - - userId, _, _ := req.Cookie.GetUID(a.Name()) - httpReq, err := http.NewRequest("POST", a.URI, bytes.NewBuffer(reqJSON)) - httpReq.Header.Add("Content-Type", "application/json;charset=utf-8") - httpReq.Header.Add("Accept", "application/json") - httpReq.AddCookie(&http.Cookie{ - Name: "KADUSERCOOKIE", - Value: userId, - }) - - pbResp, err := ctxhttp.Do(ctx, a.http.Client, httpReq) - if err != nil { - return nil, err - } - - debug.StatusCode = pbResp.StatusCode - - if pbResp.StatusCode == http.StatusNoContent { - return nil, nil - } - - if pbResp.StatusCode == http.StatusBadRequest { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("HTTP status: %d", pbResp.StatusCode), - } - } - - if pbResp.StatusCode != http.StatusOK { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("HTTP status: %d", pbResp.StatusCode), - } - } - - defer pbResp.Body.Close() - body, err := ioutil.ReadAll(pbResp.Body) - if err != nil { - return nil, err - } - - if req.IsDebug { - debug.ResponseBody = string(body) - } - - var bidResp openrtb2.BidResponse - err = json.Unmarshal(body, &bidResp) - if err != nil { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("HTTP status: %d", pbResp.StatusCode), - } - } - - bids := make(pbs.PBSBidSlice, 0) - - numBids := 0 - for _, sb := range bidResp.SeatBid { - for _, bid := range sb.Bid { - numBids++ - - bidID := bidder.LookupBidID(bid.ImpID) - if bidID == "" { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unknown ad unit code '%s'", bid.ImpID), + if wrapperExt.ProfileID == 0 { + wrapperExt.ProfileID = wrapperExtFromImp.ProfileID + } + if wrapperExt.VersionID == 0 { + wrapperExt.VersionID = wrapperExtFromImp.VersionID } - } - - pbid := pbs.PBSBid{ - BidID: bidID, - AdUnitCode: bid.ImpID, - BidderCode: bidder.BidderCode, - Price: bid.Price, - Adm: bid.AdM, - Creative_id: bid.CrID, - Width: bid.W, - Height: bid.H, - DealId: bid.DealID, - } - var bidExt pubmaticBidExt - mediaType := openrtb_ext.BidTypeBanner - if err := json.Unmarshal(bid.Ext, &bidExt); err == nil { - mediaType = getBidType(&bidExt) + if wrapperExt != nil && wrapperExt.ProfileID != 0 && wrapperExt.VersionID != 0 { + extractWrapperExtFromImp = false + } } - pbid.CreativeMediaType = string(mediaType) - - bids = append(bids, &pbid) - logf("[PUBMATIC] Returned Bid for PubID [%s] AdUnit [%s] BidID [%s] Size [%dx%d] Price [%f] \n", - pubId, pbid.AdUnitCode, pbid.BidID, pbid.Width, pbid.Height, pbid.Price) } - } - - return bids, nil -} -func (a *PubmaticAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - errs := make([]error, 0, len(request.Imp)) - - wrapExt := "" - pubID := "" - - for i := 0; i < len(request.Imp); i++ { - err := parseImpressionObject(&request.Imp[i], &wrapExt, &pubID) - // If the parsing is failed, remove imp and add the error. - if err != nil { - errs = append(errs, err) - request.Imp = append(request.Imp[:i], request.Imp[i+1:]...) - i-- + if extractPubIDFromImp && pubIDFromImp != "" { + pubID = pubIDFromImp + extractPubIDFromImp = false } } @@ -355,9 +114,14 @@ func (a *PubmaticAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ad return nil, errs } - if wrapExt != "" { - rawExt := fmt.Sprintf("{\"wrapper\": %s}", wrapExt) - request.Ext = json.RawMessage(rawExt) + if wrapperExt != nil { + reqExt := make(map[string]interface{}) + reqExt["wrapper"] = wrapperExt + rawExt, err := json.Marshal(reqExt) + if err != nil { + return nil, []error{err} + } + request.Ext = rawExt } if request.Site != nil { @@ -460,10 +224,13 @@ func assignBannerWidthAndHeight(banner *openrtb2.Banner, w, h int64) *openrtb2.B } // parseImpressionObject parse the imp to get it ready to send to pubmatic -func parseImpressionObject(imp *openrtb2.Imp, wrapExt *string, pubID *string) error { +func parseImpressionObject(imp *openrtb2.Imp, extractWrapperExtFromImp, extractPubIDFromImp bool) (*pubmaticWrapperExt, string, error) { + var wrapExt *pubmaticWrapperExt + var pubID string + // PubMatic supports banner and video impressions. if imp.Banner == nil && imp.Video == nil { - return fmt.Errorf("Invalid MediaType. PubMatic only supports Banner and Video. Ignoring ImpID=%s", imp.ID) + return wrapExt, pubID, fmt.Errorf("Invalid MediaType. PubMatic only supports Banner and Video. Ignoring ImpID=%s", imp.ID) } if imp.Audio != nil { @@ -472,36 +239,34 @@ func parseImpressionObject(imp *openrtb2.Imp, wrapExt *string, pubID *string) er var bidderExt ExtImpBidderPubmatic if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { - return err + return wrapExt, pubID, err } var pubmaticExt openrtb_ext.ExtImpPubmatic if err := json.Unmarshal(bidderExt.Bidder, &pubmaticExt); err != nil { - return err + return wrapExt, pubID, err } - if *pubID == "" { - *pubID = strings.TrimSpace(pubmaticExt.PublisherId) + if extractPubIDFromImp { + pubID = strings.TrimSpace(pubmaticExt.PublisherId) } // Parse Wrapper Extension only once per request - if *wrapExt == "" && len(pubmaticExt.WrapExt) != 0 { - var wrapExtMap map[string]int - err := json.Unmarshal([]byte(pubmaticExt.WrapExt), &wrapExtMap) + if extractWrapperExtFromImp && len(pubmaticExt.WrapExt) != 0 { + err := json.Unmarshal([]byte(pubmaticExt.WrapExt), &wrapExt) if err != nil { - return fmt.Errorf("Error in Wrapper Parameters = %v for ImpID = %v WrapperExt = %v", err.Error(), imp.ID, string(pubmaticExt.WrapExt)) + return wrapExt, pubID, fmt.Errorf("Error in Wrapper Parameters = %v for ImpID = %v WrapperExt = %v", err.Error(), imp.ID, string(pubmaticExt.WrapExt)) } - *wrapExt = string(pubmaticExt.WrapExt) } if err := validateAdSlot(strings.TrimSpace(pubmaticExt.AdSlot), imp); err != nil { - return err + return wrapExt, pubID, err } if imp.Banner != nil { bannerCopy, err := assignBannerSize(imp.Banner) if err != nil { - return err + return wrapExt, pubID, err } imp.Banner = bannerCopy } @@ -534,8 +299,26 @@ func parseImpressionObject(imp *openrtb2.Imp, wrapExt *string, pubID *string) er } } - return nil + return wrapExt, pubID, nil +} + +// extractPubmaticWrapperExtFromRequest parse the imp to get it ready to send to pubmatic +func extractPubmaticWrapperExtFromRequest(request *openrtb2.BidRequest) (*pubmaticWrapperExt, error) { + var wrpExt pubmaticWrapperExt + reqExtBidderParams, err := adapters.ExtractAdapterReqBidderParams(request) + if err != nil { + return nil, err + } + //get request ext bidder params + if wrapperObj, present := reqExtBidderParams["wrapper"]; present && len(wrapperObj) != 0 { + err = json.Unmarshal(wrapperObj, &wrpExt) + if err != nil { + return nil, err + } + return &wrpExt, nil + } + return nil, nil } func addKeywordsToExt(keywords []*openrtb_ext.ExtImpPubmaticKeyVal, extMap map[string]interface{}) { @@ -553,22 +336,6 @@ func addKeywordsToExt(keywords []*openrtb_ext.ExtImpPubmaticKeyVal, extMap map[s } } -func prepareImpressionExt(keywords map[string]string) string { - - eachKv := make([]string, 0, len(keywords)) - for key, val := range keywords { - if len(val) == 0 { - logf("No values present for key = %s", key) - continue - } else { - eachKv = append(eachKv, fmt.Sprintf("\"%s\":\"%s\"", key, val)) - } - } - - kvStr := "{" + strings.Join(eachKv, ",") + "}" - return kvStr -} - func (a *PubmaticAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil @@ -646,15 +413,6 @@ func logf(msg string, args ...interface{}) { } } -func NewPubmaticLegacyAdapter(config *adapters.HTTPAdapterConfig, uri string) *PubmaticAdapter { - a := adapters.NewHTTPAdapter(config) - - return &PubmaticAdapter{ - http: a, - URI: uri, - } -} - // Builder builds a new instance of the Pubmatic adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { bidder := &PubmaticAdapter{ diff --git a/adapters/pubmatic/pubmatic_test.go b/adapters/pubmatic/pubmatic_test.go index 2e8a6804850..ac7dbdb711f 100644 --- a/adapters/pubmatic/pubmatic_test.go +++ b/adapters/pubmatic/pubmatic_test.go @@ -1,25 +1,11 @@ package pubmatic import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io/ioutil" - "math/rand" - "net/http" - "net/http/httptest" "testing" - "time" - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/cache/dummycache" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/pbs" - "github.com/prebid/prebid-server/usersync" ) func TestJsonSamples(t *testing.T) { @@ -33,655 +19,8 @@ func TestJsonSamples(t *testing.T) { adapterstest.RunJSONBidderTest(t, "pubmatictest", bidder) } -// ---------------------------------------------------------------------------- -// Code below this line tests the legacy, non-openrtb code flow. It can be deleted after we -// clean up the existing code and make everything openrtb2. - -func CompareStringValue(val1 string, val2 string, t *testing.T) { - if val1 != val2 { - t.Fatalf(fmt.Sprintf("Expected = %s , Actual = %s", val2, val1)) - } -} - -func DummyPubMaticServer(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - body, err := ioutil.ReadAll(r.Body) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - var breq openrtb2.BidRequest - err = json.Unmarshal(body, &breq) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - resp := openrtb2.BidResponse{ - ID: breq.ID, - BidID: "bidResponse_ID", - Cur: "USD", - SeatBid: []openrtb2.SeatBid{ - { - Seat: "pubmatic", - Bid: make([]openrtb2.Bid, 0), - }, - }, - } - rand.Seed(int64(time.Now().UnixNano())) - var bids []openrtb2.Bid - - for i, imp := range breq.Imp { - bids = append(bids, openrtb2.Bid{ - ID: fmt.Sprintf("SeatID_%d", i), - ImpID: imp.ID, - Price: float64(int(rand.Float64()*1000)) / 100, - AdID: fmt.Sprintf("adID-%d", i), - AdM: "AdContent", - CrID: fmt.Sprintf("creative-%d", i), - W: *imp.Banner.W, - H: *imp.Banner.H, - DealID: fmt.Sprintf("DealID_%d", i), - }) - } - resp.SeatBid[0].Bid = bids - - js, err := json.Marshal(resp) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - w.Header().Set("Content-Type", "application/json") - w.Write(js) -} - -func TestPubmaticInvalidCall(t *testing.T) { - - an := NewPubmaticLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "blah") - - ctx := context.Background() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{} - _, err := an.Call(ctx, &pbReq, &pbBidder) - if err == nil { - t.Fatalf("No error received for invalid request") - } -} - -func TestPubmaticTimeout(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - <-time.After(2 * time.Millisecond) - }), - ) - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewPubmaticLegacyAdapter(&conf, server.URL) - ctx, cancel := context.WithTimeout(context.Background(), 0) - defer cancel() - - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 120, - H: 240, - }, - }, - Params: json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x240\"}"), - }, - }, - } - _, err := an.Call(ctx, &pbReq, &pbBidder) - if err == nil || err != context.DeadlineExceeded { - t.Fatalf("No timeout received for timed out request: %v", err) - } -} - -func TestPubmaticInvalidJson(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, "Blah") - }), - ) - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewPubmaticLegacyAdapter(&conf, server.URL) - ctx := context.Background() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 120, - H: 240, - }, - }, - Params: json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x240\"}"), - }, - }, - } - _, err := an.Call(ctx, &pbReq, &pbBidder) - if err == nil { - t.Fatalf("No error received for invalid request") - } -} - -func TestPubmaticInvalidStatusCode(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Send 404 - http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) - }), - ) - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewPubmaticLegacyAdapter(&conf, server.URL) - ctx := context.Background() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 120, - H: 240, - }, - }, - Params: json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x240\"}"), - }, - }, - } - _, err := an.Call(ctx, &pbReq, &pbBidder) - if err == nil { - t.Fatalf("No error received for invalid request") - } -} - -func TestPubmaticInvalidInputParameters(t *testing.T) { - - server := httptest.NewServer(http.HandlerFunc(DummyPubMaticServer)) - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewPubmaticLegacyAdapter(&conf, server.URL) - ctx := context.Background() - - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - BidID: "bidid", - Sizes: []openrtb2.Format{ - { - W: 120, - H: 240, - }, - }, - }, - }, - } - - pbReq.IsDebug = true - inValidPubmaticParams := []json.RawMessage{ - // Invalid Request JSON - json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x240\""), - // Missing adSlot in AdUnits.Params - json.RawMessage("{\"publisherId\": \"10\"}"), - // Missing publisher ID - json.RawMessage("{\"adSlot\": \"slot@120x240\"}"), - // Missing slot name in AdUnits.Params - json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"@120x240\"}"), - // Invalid adSize in AdUnits.Params - json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120-240\"}"), - // Missing impression width and height in AdUnits.Params - json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@\"}"), - // Missing height in AdUnits.Params - json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120\"}"), - // Missing width in AdUnits.Params - json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@x120\"}"), - // Incorrect width param in AdUnits.Params - json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@valx120\"}"), - // Incorrect height param in AdUnits.Params - json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120xval\"}"), - // Empty slot name in AdUnits.Params, - json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \" @120x240\"}"), - // Empty width in AdUnits.Params - json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@ x240\"}"), - // Empty height in AdUnits.Params - json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x \"}"), - // Empty height in AdUnits.Params - json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \" @120x \"}"), - // Invalid Keywords - json.RawMessage(`{"publisherId": "640", "adSlot": "slot1@336x280","keywords":{"pmZoneId":1},"wrapper":{"version":2,"profile":595}}`), - // Invalid Wrapper ext - json.RawMessage(`{"publisherId": "640", "adSlot": "slot1@336x280","keywords":{"pmZoneId":"Zone1,Zone2"},"wrapper":{"version":"2","profile":595}}`), - } - - for _, param := range inValidPubmaticParams { - pbBidder.AdUnits[0].Params = param - _, err := an.Call(ctx, &pbReq, &pbBidder) - if err == nil { - t.Fatalf("Should get errors for params = %v", string(param)) - } - } - -} - -func TestPubmaticBasicResponse_MandatoryParams(t *testing.T) { - - server := httptest.NewServer(http.HandlerFunc(DummyPubMaticServer)) - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewPubmaticLegacyAdapter(&conf, server.URL) - ctx := context.Background() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - BidID: "bidid", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 336, - H: 280, - }, - }, - Params: json.RawMessage("{\"publisherId\": \"640\", \"adSlot\": \"slot1@336x280\"}"), - }, - }, - } - pbReq.IsDebug = true - bids, err := an.Call(ctx, &pbReq, &pbBidder) - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - if len(bids) != 1 { - t.Fatalf("Should have received one bid") - } -} - -func TestPubmaticBasicResponse_AllParams(t *testing.T) { - - server := httptest.NewServer(http.HandlerFunc(DummyPubMaticServer)) - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewPubmaticLegacyAdapter(&conf, server.URL) - ctx := context.Background() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - BidID: "bidid", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 336, - H: 280, - }, - }, - Params: json.RawMessage(`{"publisherId": "640", - "adSlot": "slot1@336x280", - "keywords":{ - "pmZoneId": "Zone1,Zone2" - }, - "wrapper": - {"version":2, - "profile":595} - }`), - }, - }, - } - pbReq.IsDebug = true - bids, err := an.Call(ctx, &pbReq, &pbBidder) - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - if len(bids) != 1 { - t.Fatalf("Should have received one bid") - } -} - -func TestPubmaticMultiImpressionResponse(t *testing.T) { - - server := httptest.NewServer(http.HandlerFunc(DummyPubMaticServer)) - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewPubmaticLegacyAdapter(&conf, server.URL) - - ctx := context.Background() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode1", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - BidID: "bidid", - Sizes: []openrtb2.Format{ - { - W: 336, - H: 280, - }, - }, - Params: json.RawMessage("{\"publisherId\": \"640\", \"adSlot\": \"slot1@336x280\"}"), - }, - { - Code: "unitCode1", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - BidID: "bidid", - Sizes: []openrtb2.Format{ - { - W: 800, - H: 200, - }, - }, - Params: json.RawMessage("{\"publisherId\": \"640\", \"adSlot\": \"slot1@800x200\"}"), - }, - }, - } - bids, err := an.Call(ctx, &pbReq, &pbBidder) - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - if len(bids) != 2 { - t.Fatalf("Should have received two bids") - } -} - -func TestPubmaticMultiAdUnitResponse(t *testing.T) { - - server := httptest.NewServer(http.HandlerFunc(DummyPubMaticServer)) - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewPubmaticLegacyAdapter(&conf, server.URL) - - ctx := context.Background() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode1", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - BidID: "bidid", - Sizes: []openrtb2.Format{ - { - W: 336, - H: 280, - }, - }, - Params: json.RawMessage("{\"publisherId\": \"640\", \"adSlot\": \"slot1@336x280\"}"), - }, - { - Code: "unitCode2", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - BidID: "bidid", - Sizes: []openrtb2.Format{ - { - W: 800, - H: 200, - }, - }, - Params: json.RawMessage("{\"publisherId\": \"640\", \"adSlot\": \"slot1@800x200\"}"), - }, - }, - } - bids, err := an.Call(ctx, &pbReq, &pbBidder) - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - if len(bids) != 2 { - t.Fatalf("Should have received one bid") - } - -} - -func TestPubmaticMobileResponse(t *testing.T) { - - server := httptest.NewServer(http.HandlerFunc(DummyPubMaticServer)) - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewPubmaticLegacyAdapter(&conf, server.URL) - - ctx := context.Background() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - BidID: "bidid", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 336, - H: 280, - }, - }, - Params: json.RawMessage("{\"publisherId\": \"640\", \"adSlot\": \"slot1@336x280\"}"), - }, - }, - } - - pbReq.App = &openrtb2.App{ - ID: "com.test", - Name: "testApp", - } - - bids, err := an.Call(ctx, &pbReq, &pbBidder) - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - if len(bids) != 1 { - t.Fatalf("Should have received one bid") - } -} -func TestPubmaticInvalidLookupBidIDParameter(t *testing.T) { - - server := httptest.NewServer(http.HandlerFunc(DummyPubMaticServer)) - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewPubmaticLegacyAdapter(&conf, server.URL) - - ctx := context.Background() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 120, - H: 240, - }, - }, - }, - }, - } - - pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x240\"}") - _, err := an.Call(ctx, &pbReq, &pbBidder) - - CompareStringValue(err.Error(), "Unknown ad unit code 'unitCode'", t) -} - -func TestPubmaticAdSlotParams(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(DummyPubMaticServer)) - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewPubmaticLegacyAdapter(&conf, server.URL) - - ctx := context.Background() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - BidID: "bidid", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 120, - H: 240, - }, - }, - }, - }, - } - pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \" slot@120x240\"}") - bids, err := an.Call(ctx, &pbReq, &pbBidder) - if err != nil && len(bids) != 1 { - t.Fatalf("Should not return err") - } - - pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot @120x240\"}") - bids, err = an.Call(ctx, &pbReq, &pbBidder) - if err != nil && len(bids) != 1 { - t.Fatalf("Should not return err") - } - - pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x240 \"}") - bids, err = an.Call(ctx, &pbReq, &pbBidder) - if err != nil && len(bids) != 1 { - t.Fatalf("Should not return err") - } - - pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@ 120x240\"}") - bids, err = an.Call(ctx, &pbReq, &pbBidder) - if err != nil && len(bids) != 1 { - t.Fatalf("Should not return err") - } - - pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@220 x240\"}") - bids, err = an.Call(ctx, &pbReq, &pbBidder) - if err != nil && len(bids) != 1 { - t.Fatalf("Should not return err") - } - - pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x 240\"}") - bids, err = an.Call(ctx, &pbReq, &pbBidder) - if err != nil && len(bids) != 1 { - t.Fatalf("Should not return err") - } - - pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x240:1\"}") - bids, err = an.Call(ctx, &pbReq, &pbBidder) - if err != nil && len(bids) != 1 { - t.Fatalf("Should not return err") - } - - pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x 240:1\"}") - bids, err = an.Call(ctx, &pbReq, &pbBidder) - if err != nil && len(bids) != 1 { - t.Fatalf("Should not return err") - } - - pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x240 :1\"}") - bids, err = an.Call(ctx, &pbReq, &pbBidder) - if err != nil && len(bids) != 1 { - t.Fatalf("Should not return err") - } - - pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x240: 1\"}") - bids, err = an.Call(ctx, &pbReq, &pbBidder) - if err != nil && len(bids) != 1 { - t.Fatalf("Should not return err") - } -} - -func TestPubmaticSampleRequest(t *testing.T) { - - server := httptest.NewServer(http.HandlerFunc(DummyPubMaticServer)) - defer server.Close() - - pbReq := pbs.PBSRequest{ - AdUnits: make([]pbs.AdUnit, 1), - } - pbReq.AdUnits[0] = pbs.AdUnit{ - Code: "adUnit_1", - Sizes: []openrtb2.Format{ - { - W: 100, - H: 120, - }, - }, - Bids: []pbs.Bids{ - { - BidderCode: "pubmatic", - BidID: "BidID", - Params: json.RawMessage("{\"publisherId\": \"640\", \"adSlot\": \"slot1@100x120\"}"), - }, - }, - } - - pbReq.IsDebug = true - - body := new(bytes.Buffer) - err := json.NewEncoder(body).Encode(pbReq) - if err != nil { - t.Fatalf("Error when serializing request") - } - - httpReq := httptest.NewRequest("POST", server.URL, body) - httpReq.Header.Add("Referer", "http://test.com/sports") - pc := usersync.ParseCookieFromRequest(httpReq, &config.HostCookie{}) - pc.TrySync("pubmatic", "12345") - fakewriter := httptest.NewRecorder() - - pc.SetCookieOnResponse(fakewriter, false, &config.HostCookie{Domain: ""}, 90*24*time.Hour) - httpReq.Header.Add("Cookie", fakewriter.Header().Get("Set-Cookie")) - - cacheClient, _ := dummycache.New() - hcs := config.HostCookie{} - - _, err = pbs.ParsePBSRequest(httpReq, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, cacheClient, &hcs) - if err != nil { - t.Fatalf("Error when parsing request: %v", err) - } -} - func TestGetBidTypeVideo(t *testing.T) { - pubmaticExt := new(pubmaticBidExt) + pubmaticExt := &pubmaticBidExt{} pubmaticExt.BidType = new(int) *pubmaticExt.BidType = 1 actualBidTypeValue := getBidType(pubmaticExt) @@ -691,8 +30,8 @@ func TestGetBidTypeVideo(t *testing.T) { } func TestGetBidTypeForMissingBidTypeExt(t *testing.T) { - pubmaticExt := pubmaticBidExt{} - actualBidTypeValue := getBidType(&pubmaticExt) + pubmaticExt := &pubmaticBidExt{} + actualBidTypeValue := getBidType(pubmaticExt) // banner is the default bid type when no bidType key is present in the bid.ext if actualBidTypeValue != "banner" { t.Errorf("Expected Bid Type value was: banner, actual value is: %v", actualBidTypeValue) @@ -700,7 +39,7 @@ func TestGetBidTypeForMissingBidTypeExt(t *testing.T) { } func TestGetBidTypeBanner(t *testing.T) { - pubmaticExt := new(pubmaticBidExt) + pubmaticExt := &pubmaticBidExt{} pubmaticExt.BidType = new(int) *pubmaticExt.BidType = 0 actualBidTypeValue := getBidType(pubmaticExt) @@ -710,7 +49,7 @@ func TestGetBidTypeBanner(t *testing.T) { } func TestGetBidTypeNative(t *testing.T) { - pubmaticExt := new(pubmaticBidExt) + pubmaticExt := &pubmaticBidExt{} pubmaticExt.BidType = new(int) *pubmaticExt.BidType = 2 actualBidTypeValue := getBidType(pubmaticExt) @@ -720,7 +59,7 @@ func TestGetBidTypeNative(t *testing.T) { } func TestGetBidTypeForUnsupportedCode(t *testing.T) { - pubmaticExt := new(pubmaticBidExt) + pubmaticExt := &pubmaticBidExt{} pubmaticExt.BidType = new(int) *pubmaticExt.BidType = 99 actualBidTypeValue := getBidType(pubmaticExt) diff --git a/adapters/pubmatic/pubmatictest/supplemental/nilReqExt.json b/adapters/pubmatic/pubmatictest/supplemental/nilReqExt.json new file mode 100644 index 00000000000..06fc80ae20e --- /dev/null +++ b/adapters/pubmatic/pubmatictest/supplemental/nilReqExt.json @@ -0,0 +1,160 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "adSlot": "AdTag_Div1@300x250", + "publisherId": " 999 ", + "keywords": [ + { + "key": "pmZoneID", + "value": [ + "Zone1", + "Zone2" + ] + }, + { + "key": "preference", + "value": [ + "sports", + "movies" + ] + } + ], + "wrapper": { + "version": 1, + "profile": 5123 + } + } + } + } + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36" + }, + "ext": {}, + "site": { + "id": "siteID", + "publisher": { + "id": "1234" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://hbopenbid.pubmatic.com/translator?source=prebid-server", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "AdTag_Div1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "h": 250, + "w": 300 + }, + "ext": { + "pmZoneId": "Zone1,Zone2", + "preference": "sports,movies" + } + } + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36" + }, + "site": { + "id": "siteID", + "publisher": { + "id": "999" + } + }, + "ext": { + "wrapper": { + "profile": 5123, + "version": 1 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "pubmatic.com" + ], + "crid": "29681110", + "h": 250, + "w": 300, + "dealid": "test deal", + "ext": { + "dspid": 6, + "deal_channel": 1 + } + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "pubmatic.com" + ], + "crid": "29681110", + "w": 300, + "h": 250, + "dealid": "test deal", + "ext": { + "dspid": 6, + "deal_channel": 1 + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/pubmatic/pubmatictest/supplemental/reqBidderParams.json b/adapters/pubmatic/pubmatictest/supplemental/reqBidderParams.json new file mode 100644 index 00000000000..5d4a1819f37 --- /dev/null +++ b/adapters/pubmatic/pubmatictest/supplemental/reqBidderParams.json @@ -0,0 +1,169 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "adSlot": "AdTag_Div1@300x250", + "publisherId": " 999 ", + "keywords": [ + { + "key": "pmZoneID", + "value": [ + "Zone1", + "Zone2" + ] + }, + { + "key": "preference", + "value": [ + "sports", + "movies" + ] + } + ], + "wrapper": { + "version": 1, + "profile": 5123 + } + } + } + } + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36" + }, + "ext": { + "prebid": { + "bidderparams": { + "wrapper": { + "profile": 1234, + "version": 2 + } + } + } + }, + "site": { + "id": "siteID", + "publisher": { + "id": "1234" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://hbopenbid.pubmatic.com/translator?source=prebid-server", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "AdTag_Div1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "h": 250, + "w": 300 + }, + "ext": { + "pmZoneId": "Zone1,Zone2", + "preference": "sports,movies" + } + } + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36" + }, + "site": { + "id": "siteID", + "publisher": { + "id": "999" + } + }, + "ext": { + "wrapper": { + "profile": 1234, + "version": 2 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "pubmatic.com" + ], + "crid": "29681110", + "h": 250, + "w": 300, + "dealid": "test deal", + "ext": { + "dspid": 6, + "deal_channel": 1 + } + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "pubmatic.com" + ], + "crid": "29681110", + "w": 300, + "h": 250, + "dealid": "test deal", + "ext": { + "dspid": 6, + "deal_channel": 1 + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/pulsepoint/pulsepoint.go b/adapters/pulsepoint/pulsepoint.go index 6b6b4305607..f756d5dd31a 100644 --- a/adapters/pulsepoint/pulsepoint.go +++ b/adapters/pulsepoint/pulsepoint.go @@ -1,27 +1,21 @@ package pulsepoint import ( - "bytes" - "context" "encoding/json" "fmt" - "io/ioutil" "net/http" "strconv" - "strings" - "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/pbs" - "golang.org/x/net/context/ctxhttp" + + "github.com/mxmCherry/openrtb/v15/openrtb2" ) type PulsePointAdapter struct { - http *adapters.HTTPAdapter - URI string + URI string } // Builds an instance of PulsePointAdapter @@ -168,192 +162,3 @@ func getBidType(imp openrtb2.Imp) openrtb_ext.BidType { } return "" } - -///////////////////////////////// -// Legacy implementation: Start -///////////////////////////////// - -func NewPulsePointLegacyAdapter(config *adapters.HTTPAdapterConfig, uri string) *PulsePointAdapter { - a := adapters.NewHTTPAdapter(config) - - return &PulsePointAdapter{ - http: a, - URI: uri, - } -} - -// used for cookies and such -func (a *PulsePointAdapter) Name() string { - return "pulsepoint" -} - -// parameters for pulsepoint adapter. -type PulsepointParams struct { - PublisherId int `json:"cp"` - TagId int `json:"ct"` - AdSize string `json:"cf"` -} - -func (a *PulsePointAdapter) SkipNoCookies() bool { - return false -} - -func (a *PulsePointAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { - mediaTypes := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER} - ppReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), mediaTypes) - - if err != nil { - return nil, err - } - - for i, unit := range bidder.AdUnits { - var params PulsepointParams - err := json.Unmarshal(unit.Params, ¶ms) - if err != nil { - return nil, &errortypes.BadInput{ - Message: err.Error(), - } - } - if params.PublisherId == 0 { - return nil, &errortypes.BadInput{ - Message: "Missing PublisherId param cp", - } - } - if params.TagId == 0 { - return nil, &errortypes.BadInput{ - Message: "Missing TagId param ct", - } - } - if params.AdSize == "" { - return nil, &errortypes.BadInput{ - Message: "Missing AdSize param cf", - } - } - // Fixes some segfaults. Since this is legacy code, I'm not looking into it too deeply - if len(ppReq.Imp) <= i { - break - } - ppReq.Imp[i].TagID = strconv.Itoa(params.TagId) - publisher := &openrtb2.Publisher{ID: strconv.Itoa(params.PublisherId)} - if ppReq.Site != nil { - siteCopy := *ppReq.Site - siteCopy.Publisher = publisher - ppReq.Site = &siteCopy - } else { - appCopy := *ppReq.App - appCopy.Publisher = publisher - ppReq.App = &appCopy - } - if ppReq.Imp[i].Banner != nil { - var size = strings.Split(strings.ToLower(params.AdSize), "x") - if len(size) == 2 { - width, err := strconv.Atoi(size[0]) - if err == nil { - ppReq.Imp[i].Banner.W = openrtb2.Int64Ptr(int64(width)) - } else { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("Invalid Width param %s", size[0]), - } - } - height, err := strconv.Atoi(size[1]) - if err == nil { - ppReq.Imp[i].Banner.H = openrtb2.Int64Ptr(int64(height)) - } else { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("Invalid Height param %s", size[1]), - } - } - } else { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("Invalid AdSize param %s", params.AdSize), - } - } - } - } - reqJSON, err := json.Marshal(ppReq) - debug := &pbs.BidderDebug{ - RequestURI: a.URI, - } - - if req.IsDebug { - debug.RequestBody = string(reqJSON) - bidder.Debug = append(bidder.Debug, debug) - } - - httpReq, err := http.NewRequest("POST", a.URI, bytes.NewBuffer(reqJSON)) - httpReq.Header.Add("Content-Type", "application/json;charset=utf-8") - httpReq.Header.Add("Accept", "application/json") - - ppResp, err := ctxhttp.Do(ctx, a.http.Client, httpReq) - if err != nil { - return nil, err - } - - debug.StatusCode = ppResp.StatusCode - - if ppResp.StatusCode == http.StatusNoContent { - return nil, nil - } - - if ppResp.StatusCode == http.StatusBadRequest { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("HTTP status: %d", ppResp.StatusCode), - } - } - - if ppResp.StatusCode != http.StatusOK { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("HTTP status: %d", ppResp.StatusCode), - } - } - - defer ppResp.Body.Close() - body, err := ioutil.ReadAll(ppResp.Body) - if err != nil { - return nil, err - } - - if req.IsDebug { - debug.ResponseBody = string(body) - } - - var bidResp openrtb2.BidResponse - err = json.Unmarshal(body, &bidResp) - if err != nil { - return nil, &errortypes.BadServerResponse{ - Message: err.Error(), - } - } - - bids := make(pbs.PBSBidSlice, 0) - - for _, sb := range bidResp.SeatBid { - for _, bid := range sb.Bid { - bidID := bidder.LookupBidID(bid.ImpID) - if bidID == "" { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unknown ad unit code '%s'", bid.ImpID), - } - } - - pbid := pbs.PBSBid{ - BidID: bidID, - AdUnitCode: bid.ImpID, - BidderCode: bidder.BidderCode, - Price: bid.Price, - Adm: bid.AdM, - Creative_id: bid.CrID, - Width: bid.W, - Height: bid.H, - CreativeMediaType: string(openrtb_ext.BidTypeBanner), - } - bids = append(bids, &pbid) - } - } - - return bids, nil -} - -///////////////////////////////// -// Legacy implementation: End -///////////////////////////////// diff --git a/adapters/pulsepoint/pulsepoint_test.go b/adapters/pulsepoint/pulsepoint_test.go index a4e20b04859..8929898522a 100644 --- a/adapters/pulsepoint/pulsepoint_test.go +++ b/adapters/pulsepoint/pulsepoint_test.go @@ -1,26 +1,11 @@ package pulsepoint import ( - "encoding/json" - "net/http" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/openrtb_ext" - - "bytes" - "context" - "fmt" - "io/ioutil" - "net/http/httptest" - "time" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/cache/dummycache" "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/pbs" - "github.com/prebid/prebid-server/usersync" + "github.com/prebid/prebid-server/openrtb_ext" ) func TestJsonSamples(t *testing.T) { @@ -33,280 +18,3 @@ func TestJsonSamples(t *testing.T) { adapterstest.RunJSONBidderTest(t, "pulsepointtest", bidder) } - -///////////////////////////////// -// Legacy implementation: Start -///////////////////////////////// - -/** - * Verify adapter names are setup correctly. - */ -func TestPulsePointAdapterNames(t *testing.T) { - adapter := NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "http://localhost/bid") - adapterstest.VerifyStringValue(adapter.Name(), "pulsepoint", t) -} - -/** - * Test required parameters not sent - */ -func TestPulsePointRequiredBidParameters(t *testing.T) { - adapter := NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "http://localhost/bid") - ctx := context.TODO() - req := SampleRequest(1, t) - bidder := req.Bidders[0] - // remove "ct" param and verify error message. - bidder.AdUnits[0].Params = json.RawMessage("{\"cp\": 2001, \"cf\": \"728X90\"}") - _, errTag := adapter.Call(ctx, req, bidder) - adapterstest.VerifyStringValue(errTag.Error(), "Missing TagId param ct", t) - // remove "cp" param and verify error message. - bidder.AdUnits[0].Params = json.RawMessage("{\"ct\": 1001, \"cf\": \"728X90\"}") - _, errPub := adapter.Call(ctx, req, bidder) - adapterstest.VerifyStringValue(errPub.Error(), "Missing PublisherId param cp", t) - // remove "cf" param and verify error message. - bidder.AdUnits[0].Params = json.RawMessage("{\"cp\": 2001, \"ct\": 1001}") - _, errSize := adapter.Call(ctx, req, bidder) - adapterstest.VerifyStringValue(errSize.Error(), "Missing AdSize param cf", t) - // invalid width parameter value for cf - bidder.AdUnits[0].Params = json.RawMessage("{\"ct\": 1001, \"cp\": 2001, \"cf\": \"aXb\"}") - _, errWidth := adapter.Call(ctx, req, bidder) - adapterstest.VerifyStringValue(errWidth.Error(), "Invalid Width param a", t) - // invalid parameter values for cf - bidder.AdUnits[0].Params = json.RawMessage("{\"ct\": 1001, \"cp\": 2001, \"cf\": \"12Xb\"}") - _, errHeight := adapter.Call(ctx, req, bidder) - adapterstest.VerifyStringValue(errHeight.Error(), "Invalid Height param b", t) - // invalid parameter values for cf - bidder.AdUnits[0].Params = json.RawMessage("{\"ct\": 1001, \"cp\": 2001, \"cf\": \"12-20\"}") - _, errAdSizeValue := adapter.Call(ctx, req, bidder) - adapterstest.VerifyStringValue(errAdSizeValue.Error(), "Invalid AdSize param 12-20", t) -} - -/** - * Verify the openrtb request sent to Pulsepoint endpoint. - * Ensure the ct, cp, cf params are transformed and sent alright. - */ -func TestPulsePointOpenRTBRequest(t *testing.T) { - service := CreateService(adapterstest.BidOnTags("")) - server := service.Server - ctx := context.TODO() - req := SampleRequest(1, t) - bidder := req.Bidders[0] - adapter := NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - adapter.Call(ctx, req, bidder) - adapterstest.VerifyIntValue(len(service.LastBidRequest.Imp), 1, t) - adapterstest.VerifyStringValue(service.LastBidRequest.Imp[0].TagID, "1001", t) - adapterstest.VerifyStringValue(service.LastBidRequest.Site.Publisher.ID, "2001", t) - adapterstest.VerifyBannerSize(service.LastBidRequest.Imp[0].Banner, 728, 90, t) -} - -/** - * Verify bidding behavior. - */ -func TestPulsePointBiddingBehavior(t *testing.T) { - // setup server endpoint to return bid. - server := CreateService(adapterstest.BidOnTags("1001")).Server - ctx := context.TODO() - req := SampleRequest(1, t) - bidder := req.Bidders[0] - adapter := NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - bids, _ := adapter.Call(ctx, req, bidder) - // number of bids should be 1 - adapterstest.VerifyIntValue(len(bids), 1, t) - adapterstest.VerifyStringValue(bids[0].AdUnitCode, "div-adunit-1", t) - adapterstest.VerifyStringValue(bids[0].BidderCode, "pulsepoint", t) - adapterstest.VerifyStringValue(bids[0].Adm, "
This is an Ad
", t) - adapterstest.VerifyStringValue(bids[0].Creative_id, "Cr-234", t) - adapterstest.VerifyIntValue(int(bids[0].Width), 728, t) - adapterstest.VerifyIntValue(int(bids[0].Height), 90, t) - adapterstest.VerifyIntValue(int(bids[0].Price*100), 210, t) - adapterstest.VerifyStringValue(bids[0].CreativeMediaType, string(openrtb_ext.BidTypeBanner), t) -} - -/** - * Verify bidding behavior on multiple impressions, some impressions make a bid - */ -func TestPulsePointMultiImpPartialBidding(t *testing.T) { - // setup server endpoint to return bid. - service := CreateService(adapterstest.BidOnTags("1001")) - server := service.Server - ctx := context.TODO() - req := SampleRequest(2, t) - bidder := req.Bidders[0] - adapter := NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - bids, _ := adapter.Call(ctx, req, bidder) - // two impressions sent. - // number of bids should be 1 - adapterstest.VerifyIntValue(len(service.LastBidRequest.Imp), 2, t) - adapterstest.VerifyIntValue(len(bids), 1, t) - adapterstest.VerifyStringValue(bids[0].AdUnitCode, "div-adunit-1", t) -} - -/** - * Verify bidding behavior on multiple impressions, all impressions passed back. - */ -func TestPulsePointMultiImpPassback(t *testing.T) { - // setup server endpoint to return bid. - service := CreateService(adapterstest.BidOnTags("")) - server := service.Server - ctx := context.TODO() - req := SampleRequest(2, t) - bidder := req.Bidders[0] - adapter := NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - bids, _ := adapter.Call(ctx, req, bidder) - // two impressions sent. - // number of bids should be 1 - adapterstest.VerifyIntValue(len(service.LastBidRequest.Imp), 2, t) - adapterstest.VerifyIntValue(len(bids), 0, t) -} - -/** - * Verify bidding behavior on multiple impressions, all impressions passed back. - */ -func TestPulsePointMultiImpAllBid(t *testing.T) { - // setup server endpoint to return bid. - service := CreateService(adapterstest.BidOnTags("1001,1002")) - server := service.Server - ctx := context.TODO() - req := SampleRequest(2, t) - bidder := req.Bidders[0] - adapter := NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - bids, _ := adapter.Call(ctx, req, bidder) - // two impressions sent. - // number of bids should be 1 - adapterstest.VerifyIntValue(len(service.LastBidRequest.Imp), 2, t) - adapterstest.VerifyIntValue(len(bids), 2, t) - adapterstest.VerifyStringValue(bids[0].AdUnitCode, "div-adunit-1", t) - adapterstest.VerifyStringValue(bids[1].AdUnitCode, "div-adunit-2", t) -} - -/** - * Verify bidding behavior on mobile app requests - */ -func TestMobileAppRequest(t *testing.T) { - // setup server endpoint to return bid. - service := CreateService(adapterstest.BidOnTags("1001")) - server := service.Server - ctx := context.TODO() - req := SampleRequest(1, t) - req.App = &openrtb2.App{ - ID: "com.facebook.katana", - Name: "facebook", - } - bidder := req.Bidders[0] - adapter := NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - bids, _ := adapter.Call(ctx, req, bidder) - // one mobile app impression sent. - // verify appropriate fields are sent to pulsepoint endpoint. - adapterstest.VerifyIntValue(len(service.LastBidRequest.Imp), 1, t) - adapterstest.VerifyStringValue(service.LastBidRequest.App.ID, "com.facebook.katana", t) - adapterstest.VerifyIntValue(len(bids), 1, t) - adapterstest.VerifyStringValue(bids[0].AdUnitCode, "div-adunit-1", t) -} - -/** - * Produces a sample PBSRequest, for the impressions given. - */ -func SampleRequest(numberOfImpressions int, t *testing.T) *pbs.PBSRequest { - // create a request object - req := pbs.PBSRequest{ - AdUnits: make([]pbs.AdUnit, 2), - } - req.AccountID = "1" - tagId := 1001 - for i := 0; i < numberOfImpressions; i++ { - req.AdUnits[i] = pbs.AdUnit{ - Code: fmt.Sprintf("div-adunit-%d", i+1), - Sizes: []openrtb2.Format{ - { - W: 10, - H: 12, - }, - }, - Bids: []pbs.Bids{ - { - BidderCode: "pulsepoint", - BidID: fmt.Sprintf("Bid-%d", i+1), - Params: json.RawMessage(fmt.Sprintf("{\"ct\": %d, \"cp\": 2001, \"cf\": \"728X90\"}", tagId+i)), - }, - }, - } - } - // serialize the request to json - body := new(bytes.Buffer) - err := json.NewEncoder(body).Encode(req) - if err != nil { - t.Fatalf("Error when serializing request") - } - // setup a http request - httpReq := httptest.NewRequest("POST", CreateService(adapterstest.BidOnTags("")).Server.URL, body) - httpReq.Header.Add("Referer", "http://news.pub/topnews") - pc := usersync.ParseCookieFromRequest(httpReq, &config.HostCookie{}) - pc.TrySync("pulsepoint", "pulsepointUser123") - fakewriter := httptest.NewRecorder() - - pc.SetCookieOnResponse(fakewriter, false, &config.HostCookie{Domain: ""}, 90*24*time.Hour) - httpReq.Header.Add("Cookie", fakewriter.Header().Get("Set-Cookie")) - // parse the http request - cacheClient, _ := dummycache.New() - hcs := config.HostCookie{} - - parsedReq, err := pbs.ParsePBSRequest(httpReq, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, cacheClient, &hcs) - if err != nil { - t.Fatalf("Error when parsing request: %v", err) - } - return parsedReq -} - -/** - * Represents a mock ORTB endpoint of PulsePoint. Would return a bid - * for TagId 1001 and passback for 1002 as the default behavior. - */ -func CreateService(tagsToBid map[string]bool) adapterstest.OrtbMockService { - service := adapterstest.OrtbMockService{} - var lastBidRequest openrtb2.BidRequest - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - body, err := ioutil.ReadAll(r.Body) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - var breq openrtb2.BidRequest - err = json.Unmarshal(body, &breq) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - lastBidRequest = breq - var bids []openrtb2.Bid - for i, imp := range breq.Imp { - if tagsToBid[imp.TagID] { - bids = append(bids, adapterstest.SampleBid(imp.Banner.W, imp.Banner.H, imp.ID, i+1)) - } - } - // no bids were produced, pulsepoint service returns 204 - if len(bids) == 0 { - w.WriteHeader(204) - } else { - // serialize the bids to openrtb2.BidResponse - js, _ := json.Marshal(openrtb2.BidResponse{ - SeatBid: []openrtb2.SeatBid{ - { - Bid: bids, - }, - }, - }) - w.Header().Set("Content-Type", "application/json") - w.Write(js) - } - })) - service.Server = server - service.LastBidRequest = &lastBidRequest - return service -} - -///////////////////////////////// -// Legacy implementation: End -///////////////////////////////// diff --git a/adapters/richaudience/params_test.go b/adapters/richaudience/params_test.go new file mode 100644 index 00000000000..038936f3cbf --- /dev/null +++ b/adapters/richaudience/params_test.go @@ -0,0 +1,49 @@ +package richaudience + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range validParams { + if err := validator.Validate(openrtb_ext.BidderRichaudience, json.RawMessage(p)); err != nil { + t.Errorf("Schema rejected valid params: %s", p) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderRichaudience, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} + +var validParams = []string{ + `{"pid":"hash", "supplyType":"site"}`, + `{"pid":"hash", "supplyType":"site", "test": true}`, +} + +var invalidParams = []string{ + `{"pid": 42}`, + `{"pid": "", "supplyType":0}`, + `{"pid": 11, "supplyType":"site"}`, + `{"pid": "hash", "supplyType":11}`, + `{"pid": "hash"}`, + `{"supplyType":"site"}`, + `{}`, +} diff --git a/adapters/richaudience/richaudience.go b/adapters/richaudience/richaudience.go new file mode 100644 index 00000000000..333df56408b --- /dev/null +++ b/adapters/richaudience/richaudience.go @@ -0,0 +1,202 @@ +package richaudience + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type adapter struct { + endpoint string +} + +// Builder builds a new instance of the RichAudience adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + raiHeaders := http.Header{} + + setHeaders(&raiHeaders) + + isUrlSecure := getIsUrlSecure(request) + + resImps, err := setImp(request, isUrlSecure) + if err != nil { + return nil, []error{err} + } + + request.Imp = resImps + + if err = validateDevice(request); err != nil { + return nil, []error{err} + } + + req, err := json.Marshal(request) + if err != nil { + return nil, []error{err} + } + + requestData := &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + Body: req, + Headers: raiHeaders, + } + + return []*adapters.RequestData{requestData}, nil +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if responseData.StatusCode == http.StatusNoContent { + return nil, nil + } + + if responseData.StatusCode == http.StatusBadRequest { + err := &errortypes.BadInput{ + Message: "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.", + } + return nil, []error{err} + } + + if responseData.StatusCode != http.StatusOK { + err := &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info.", responseData.StatusCode), + } + return nil, []error{err} + } + + var bidResp openrtb2.BidResponse + if err := json.Unmarshal(responseData.Body, &bidResp); err != nil { + + return nil, []error{&errortypes.BadServerResponse{ + Message: err.Error(), + }} + } + + var response openrtb2.BidResponse + if err := json.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + bidResponse.Currency = response.Cur + for _, seatBid := range response.SeatBid { + for i := range seatBid.Bid { + b := &adapters.TypedBid{ + Bid: &seatBid.Bid[i], + BidType: openrtb_ext.BidTypeBanner, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + + return bidResponse, nil +} + +func setHeaders(raiHeaders *http.Header) { + raiHeaders.Set("Content-Type", "application/json;charset=utf-8") + raiHeaders.Set("Accept", "application/json") + raiHeaders.Add("X-Openrtb-Version", "2.5") +} + +func setImp(request *openrtb2.BidRequest, isUrlSecure bool) (resImps []openrtb2.Imp, err error) { + for _, imp := range request.Imp { + var secure = int8(0) + raiExt, errImp := parseImpExt(&imp) + if errImp != nil { + return nil, errImp + } + + if raiExt != nil { + if raiExt.Pid != "" { + imp.TagID = raiExt.Pid + } + + if raiExt.Test { + request.Test = int8(1) + } + + if raiExt.BidFloorCur != "" { + imp.BidFloorCur = raiExt.BidFloorCur + } else if imp.BidFloorCur == "" { + imp.BidFloorCur = "USD" + } + } + if isUrlSecure { + secure = int8(1) + } + + imp.Secure = &secure + + if imp.Banner.W == nil && imp.Banner.H == nil { + if len(imp.Banner.Format) == 0 { + err = &errortypes.BadInput{ + Message: "request.Banner.Format is required", + } + return nil, err + } + } + + resImps = append(resImps, imp) + + } + return resImps, nil +} + +func getIsUrlSecure(request *openrtb2.BidRequest) (isUrlSecure bool) { + if request.Site != nil { + if request.Site.Page != "" { + pageURL, err := url.Parse(request.Site.Page) + if err == nil { + if request.Site.Domain == "" { + request.Site.Domain = pageURL.Host + } + isUrlSecure = pageURL.Scheme == "https" + } + } + } + return +} + +func validateDevice(request *openrtb2.BidRequest) (err error) { + + if request.Device != nil && request.Device.IP == "" && request.Device.IPv6 == "" { + err = &errortypes.BadInput{ + Message: "request.Device.IP is required", + } + return err + } + return err +} + +func parseImpExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpRichaudience, error) { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + err = &errortypes.BadInput{ + Message: fmt.Sprintf("not found parameters ext in ImpID : %s", imp.ID), + } + return nil, err + } + + var richaudienceExt openrtb_ext.ExtImpRichaudience + if err := json.Unmarshal(bidderExt.Bidder, &richaudienceExt); err != nil { + err = &errortypes.BadInput{ + Message: fmt.Sprintf("invalid parameters ext in ImpID: %s", imp.ID), + } + return nil, err + } + + return &richaudienceExt, nil +} diff --git a/adapters/richaudience/richaudience_test.go b/adapters/richaudience/richaudience_test.go new file mode 100644 index 00000000000..b1655a4d1d6 --- /dev/null +++ b/adapters/richaudience/richaudience_test.go @@ -0,0 +1,161 @@ +package richaudience + +import ( + "net/http" + "testing" + + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +type richaudienceRequest struct { + ID string `json:"id,omitempty"` + Imp []openrtb2.Imp `json:"imp,omitempty"` + User richaudienceUser `json:"user,omitempty"` + Device richaudienceDevice `json:"device,omitempty"` + Site richaudienceSite `json:"site,omitempty"` + Test int8 `json:"test,omitempty"` +} + +type richaudienceUser struct { + BuyerUID string `json:"buyeruid,omitempty"` + Ext richaudienceUserExt `json:"ext,omitempty"` +} + +type richaudienceUserExt struct { + Eids []openrtb_ext.ExtUserEid `json:"eids,omitempty"` + Consent string `json:"consent,omitempty"` +} + +type richaudienceDevice struct { + IP string `json:"ip,omitempty"` + IPv6 string `json:"ipv6,omitempty"` + Lmt int8 `json:"lmt,omitempty"` + DNT int8 `json:"dnt,omitempty"` + UA string `json:"ua,omitempty"` +} + +type richaudienceSite struct { + Domain string `json:"domain,omitempty"` + Page string `json:"page,omitempty"` +} + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderRichaudience, config.Adapter{ + Endpoint: "http://ortb.richaudience.com/ortb/?bidder=pbs", + }) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "richaudiencetest", bidder) +} + +func TestGetBuilder(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderRichaudience, config.Adapter{ + Endpoint: "http://ortb.richaudience.com/ortb/?bidder=pbs"}) + + if buildErr != nil { + t.Errorf("error %s", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "richaudience", bidder) +} + +func TestGetSite(t *testing.T) { + raBidRequest := &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Domain: "www.test.com", + }, + } + + richaudienceRequestTest := &richaudienceRequest{ + Site: richaudienceSite{ + Domain: "www.test.com", + }, + } + + getIsUrlSecure(raBidRequest) + + if raBidRequest.Site.Domain != richaudienceRequestTest.Site.Domain { + t.Errorf("error %s", richaudienceRequestTest.Site.Domain) + } + + raBidRequest = &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Page: "http://www.test.com/test", + Domain: "", + }, + } + + richaudienceRequestTest = &richaudienceRequest{ + Site: richaudienceSite{ + Domain: "", + }, + } + + getIsUrlSecure(raBidRequest) +} + +func TestGetDevice(t *testing.T) { + + raBidRequest := &openrtb2.BidRequest{ + Device: &openrtb2.Device{ + IP: "11.222.33.44", + UA: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36", + }, + } + + richaudienceRequestTest := &richaudienceRequest{ + Device: richaudienceDevice{ + IP: "11.222.33.44", + Lmt: 0, + DNT: 0, + UA: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36", + }, + } + + validateDevice(raBidRequest) + + if raBidRequest.Device.IP != richaudienceRequestTest.Device.IP { + t.Errorf("error %s", richaudienceRequestTest.Device.IP) + } + + if richaudienceRequestTest.Device.Lmt == 1 { + t.Errorf("error %v", richaudienceRequestTest.Device.Lmt) + } + + if richaudienceRequestTest.Device.DNT == 1 { + t.Errorf("error %v", richaudienceRequestTest.Device.DNT) + } + + if raBidRequest.Device.UA != richaudienceRequestTest.Device.UA { + t.Errorf("error %s", richaudienceRequestTest.Device.UA) + } +} + +func TestResponseEmpty(t *testing.T) { + httpResp := &adapters.ResponseData{ + StatusCode: http.StatusNoContent, + } + bidder := new(adapter) + bidResponse, errs := bidder.MakeBids(nil, nil, httpResp) + + assert.Nil(t, bidResponse, "Expected Nil") + assert.Empty(t, errs, "Errors: %d", len(errs)) +} + +func TestEmptyConfig(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderRichaudience, config.Adapter{ + Endpoint: ``, + ExtraAdapterInfo: ``, + }) + + assert.NoError(t, buildErr) + assert.Empty(t, bidder) +} diff --git a/adapters/richaudience/richaudiencetest/exemplary/single-banner-app.json b/adapters/richaudience/richaudiencetest/exemplary/single-banner-app.json new file mode 100644 index 00000000000..430e1a27f02 --- /dev/null +++ b/adapters/richaudience/richaudiencetest/exemplary/single-banner-app.json @@ -0,0 +1,122 @@ +{ + "mockBidRequest": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 0, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true + } + } + } + ], + "device": { + "ip": "11.222.33.44", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": {} + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ortb.richaudience.com/ortb/?bidder=pbs", + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": ["2.5"] + }, + "body": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 1, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "tagid" : "OsNsyeF68q", + "secure": 0, + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true + } + } + } + ], + "device": { + "ip": "11.222.33.44", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": { + + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "ff935bea-4661-40bf-95b7-80c354cf0cdc", + "seatbid": [{ + "bid": [{ + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "crid": "999999", + "adm": "", + "adomain": ["richaudience.com"], + "h": 250, + "w": 300 + }] + }] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "adm": "", + "adomain": ["richaudience.com"], + "crid": "999999", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] + } \ No newline at end of file diff --git a/adapters/richaudience/richaudiencetest/exemplary/single-banner-defaultCurrency.json b/adapters/richaudience/richaudiencetest/exemplary/single-banner-defaultCurrency.json new file mode 100644 index 00000000000..a6ebe54e95b --- /dev/null +++ b/adapters/richaudience/richaudiencetest/exemplary/single-banner-defaultCurrency.json @@ -0,0 +1,129 @@ +{ + "mockBidRequest": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 0, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": {} + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ortb.richaudience.com/ortb/?bidder=pbs", + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": ["2.5"] + }, + "body": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 1, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "tagid" : "OsNsyeF68q", + "secure": 1, + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": { + + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "ff935bea-4661-40bf-95b7-80c354cf0cdc", + "seatbid": [{ + "bid": [{ + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "crid": "999999", + "adm": "", + "adomain": ["richaudience.com"], + "h": 250, + "w": 300 + }] + }] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "adm": "", + "adomain": ["richaudience.com"], + "crid": "999999", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] + } \ No newline at end of file diff --git a/adapters/richaudience/richaudiencetest/exemplary/single-banner-deviceConfig.json b/adapters/richaudience/richaudiencetest/exemplary/single-banner-deviceConfig.json new file mode 100644 index 00000000000..8b21864c63f --- /dev/null +++ b/adapters/richaudience/richaudiencetest/exemplary/single-banner-deviceConfig.json @@ -0,0 +1,132 @@ +{ + "mockBidRequest": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 0, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "dnt": 1, + "lmt": 1, + "ip": "11.222.33.44" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": {} + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ortb.richaudience.com/ortb/?bidder=pbs", + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": ["2.5"] + }, + "body": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 1, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "tagid" : "OsNsyeF68q", + "secure": 1, + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "dnt": 1, + "lmt": 1, + "ip": "11.222.33.44" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": { + + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "ff935bea-4661-40bf-95b7-80c354cf0cdc", + "seatbid": [{ + "bid": [{ + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "crid": "999999", + "adm": "", + "adomain": ["richaudience.com"], + "h": 250, + "w": 300 + }] + }] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "adm": "", + "adomain": ["richaudience.com"], + "crid": "999999", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] + } \ No newline at end of file diff --git a/adapters/richaudience/richaudiencetest/exemplary/single-banner-extUser.json b/adapters/richaudience/richaudiencetest/exemplary/single-banner-extUser.json new file mode 100644 index 00000000000..72f5387dcf4 --- /dev/null +++ b/adapters/richaudience/richaudiencetest/exemplary/single-banner-extUser.json @@ -0,0 +1,162 @@ +{ + "mockBidRequest": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 0, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": { + "eids": [ + { + "source": "id5-sync.com", + "uids": [ + { + "id": "ID5-ZHMOC5mEw0TZiThiUevdyq0gjh7Egh3A4p4i9XGP-w!ID5*NAbWTGXXH8AqlxI7DB9w3qTju41wihkerqwFIZs_FPgAABOqmcXN5sfliX2kQ4Ku", + "atype": 1, + "ext": { + "linkType": 2, + "abTestingControlGroup": false + } + } + ] + } + ], + "consent": "CPItQrlPItQrlAKAfAENBhCsAP_AAHLAAAiQIBtf_X__bX9j" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ortb.richaudience.com/ortb/?bidder=pbs", + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": ["2.5"] + }, + "body": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 1, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "tagid" : "OsNsyeF68q", + "secure": 1, + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": { + "consent": "CPItQrlPItQrlAKAfAENBhCsAP_AAHLAAAiQIBtf_X__bX9j", + "eids": [ + { + "source": "id5-sync.com", + "uids": [ + { + "id": "ID5-ZHMOC5mEw0TZiThiUevdyq0gjh7Egh3A4p4i9XGP-w!ID5*NAbWTGXXH8AqlxI7DB9w3qTju41wihkerqwFIZs_FPgAABOqmcXN5sfliX2kQ4Ku", + "atype": 1, + "ext": { + "linkType": 2, + "abTestingControlGroup": false + } + } + ] + } + ] + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "ff935bea-4661-40bf-95b7-80c354cf0cdc", + "seatbid": [{ + "bid": [{ + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "crid": "999999", + "adm": "", + "adomain": ["richaudience.com"], + "h": 250, + "w": 300 + }] + }] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "adm": "", + "adomain": ["richaudience.com"], + "crid": "999999", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] + } \ No newline at end of file diff --git a/adapters/richaudience/richaudiencetest/exemplary/single-banner-floorPrice.json b/adapters/richaudience/richaudiencetest/exemplary/single-banner-floorPrice.json new file mode 100644 index 00000000000..3dcc0f94db9 --- /dev/null +++ b/adapters/richaudience/richaudiencetest/exemplary/single-banner-floorPrice.json @@ -0,0 +1,132 @@ +{ + "mockBidRequest": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 0, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true, + "BidFloor": 1.5 + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": {} + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ortb.richaudience.com/ortb/?bidder=pbs", + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": ["2.5"] + }, + "body": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 1, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "tagid" : "OsNsyeF68q", + "secure": 1, + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true, + "BidFloor": 1.5 + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": { + + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "ff935bea-4661-40bf-95b7-80c354cf0cdc", + "seatbid": [{ + "bid": [{ + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "crid": "999999", + "adm": "", + "adomain": ["richaudience.com"], + "h": 250, + "w": 300 + }] + }] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "adm": "", + "adomain": ["richaudience.com"], + "crid": "999999", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] + } \ No newline at end of file diff --git a/adapters/richaudience/richaudiencetest/exemplary/single-banner-iPv6.json b/adapters/richaudience/richaudiencetest/exemplary/single-banner-iPv6.json new file mode 100644 index 00000000000..f2162ed598b --- /dev/null +++ b/adapters/richaudience/richaudiencetest/exemplary/single-banner-iPv6.json @@ -0,0 +1,130 @@ +{ + "mockBidRequest": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 0, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ipv6": "2607:fb90:f27:4512:d800:cb23:a603:e245", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": {} + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ortb.richaudience.com/ortb/?bidder=pbs", + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": ["2.5"] + }, + "body": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 1, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "tagid" : "OsNsyeF68q", + "secure": 1, + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ipv6": "2607:fb90:f27:4512:d800:cb23:a603:e245", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": { + + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "ff935bea-4661-40bf-95b7-80c354cf0cdc", + "seatbid": [{ + "bid": [{ + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "crid": "999999", + "adm": "", + "adomain": ["richaudience.com"], + "h": 250, + "w": 300 + }] + }] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "adm": "", + "adomain": ["richaudience.com"], + "crid": "999999", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] + } \ No newline at end of file diff --git a/adapters/richaudience/richaudiencetest/exemplary/single-banner-nosecure.json b/adapters/richaudience/richaudiencetest/exemplary/single-banner-nosecure.json new file mode 100644 index 00000000000..ffd92960718 --- /dev/null +++ b/adapters/richaudience/richaudiencetest/exemplary/single-banner-nosecure.json @@ -0,0 +1,130 @@ +{ + "mockBidRequest": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 0, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "http://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": {} + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ortb.richaudience.com/ortb/?bidder=pbs", + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": ["2.5"] + }, + "body": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 1, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "tagid" : "OsNsyeF68q", + "secure": 0, + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "http://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": { + + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "ff935bea-4661-40bf-95b7-80c354cf0cdc", + "seatbid": [{ + "bid": [{ + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "crid": "999999", + "adm": "", + "adomain": ["richaudience.com"], + "h": 250, + "w": 300 + }] + }] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "adm": "", + "adomain": ["richaudience.com"], + "crid": "999999", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] + } \ No newline at end of file diff --git a/adapters/richaudience/richaudiencetest/exemplary/single-banner-setCurrency.json b/adapters/richaudience/richaudiencetest/exemplary/single-banner-setCurrency.json new file mode 100644 index 00000000000..1d970605444 --- /dev/null +++ b/adapters/richaudience/richaudiencetest/exemplary/single-banner-setCurrency.json @@ -0,0 +1,131 @@ +{ + "mockBidRequest": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 0, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true, + "bidfloorcur": "EUR" + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": {} + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ortb.richaudience.com/ortb/?bidder=pbs", + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": ["2.5"] + }, + "body": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 1, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "tagid" : "OsNsyeF68q", + "secure": 1, + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "EUR", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true, + "bidfloorcur": "EUR" + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": { + + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "ff935bea-4661-40bf-95b7-80c354cf0cdc", + "seatbid": [{ + "bid": [{ + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "crid": "999999", + "adm": "", + "adomain": ["richaudience.com"], + "h": 250, + "w": 300 + }] + }] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "adm": "", + "adomain": ["richaudience.com"], + "crid": "999999", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] + } \ No newline at end of file diff --git a/adapters/richaudience/richaudiencetest/exemplary/single-banner-sitePage.json b/adapters/richaudience/richaudiencetest/exemplary/single-banner-sitePage.json new file mode 100644 index 00000000000..a1634235060 --- /dev/null +++ b/adapters/richaudience/richaudiencetest/exemplary/single-banner-sitePage.json @@ -0,0 +1,130 @@ +{ + "mockBidRequest": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 0, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": {} + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ortb.richaudience.com/ortb/?bidder=pbs", + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": ["2.5"] + }, + "body": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 1, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "tagid" : "OsNsyeF68q", + "secure": 1, + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": { + + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "ff935bea-4661-40bf-95b7-80c354cf0cdc", + "seatbid": [{ + "bid": [{ + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "crid": "999999", + "adm": "", + "adomain": ["richaudience.com"], + "h": 250, + "w": 300 + }] + }] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "adm": "", + "adomain": ["richaudience.com"], + "crid": "999999", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] + } \ No newline at end of file diff --git a/adapters/richaudience/richaudiencetest/exemplary/single-banner.json b/adapters/richaudience/richaudiencetest/exemplary/single-banner.json new file mode 100644 index 00000000000..a1634235060 --- /dev/null +++ b/adapters/richaudience/richaudiencetest/exemplary/single-banner.json @@ -0,0 +1,130 @@ +{ + "mockBidRequest": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 0, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": {} + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ortb.richaudience.com/ortb/?bidder=pbs", + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": ["2.5"] + }, + "body": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 1, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "tagid" : "OsNsyeF68q", + "secure": 1, + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": { + + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "ff935bea-4661-40bf-95b7-80c354cf0cdc", + "seatbid": [{ + "bid": [{ + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "crid": "999999", + "adm": "", + "adomain": ["richaudience.com"], + "h": 250, + "w": 300 + }] + }] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "adm": "", + "adomain": ["richaudience.com"], + "crid": "999999", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] + } \ No newline at end of file diff --git a/adapters/richaudience/richaudiencetest/supplemental/formatNotFound.json b/adapters/richaudience/richaudiencetest/supplemental/formatNotFound.json new file mode 100644 index 00000000000..01fbd4184c5 --- /dev/null +++ b/adapters/richaudience/richaudiencetest/supplemental/formatNotFound.json @@ -0,0 +1,39 @@ +{ + "mockBidRequest": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 0, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format" : [] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "", + "ext": {} + } + }, + + "expectedMakeRequestsErrors": [{ + "value": "request.Banner.Format is required", + "comparison": "literal" + }] + } \ No newline at end of file diff --git a/adapters/richaudience/richaudiencetest/supplemental/invalidParams.json b/adapters/richaudience/richaudiencetest/supplemental/invalidParams.json new file mode 100644 index 00000000000..2c727621101 --- /dev/null +++ b/adapters/richaudience/richaudiencetest/supplemental/invalidParams.json @@ -0,0 +1,38 @@ +{ + "mockBidRequest": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 0, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": {} + } + }, + + "expectedMakeRequestsErrors": [{ + "value": "invalid parameters ext in ImpID: div-gpt-ad-1460505748561-0", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/richaudience/richaudiencetest/supplemental/noIPDevice.json b/adapters/richaudience/richaudiencetest/supplemental/noIPDevice.json new file mode 100644 index 00000000000..72148a7b376 --- /dev/null +++ b/adapters/richaudience/richaudiencetest/supplemental/noIPDevice.json @@ -0,0 +1,42 @@ +{ + "mockBidRequest": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 0, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "", + "ext": {} + } + }, + + "expectedMakeRequestsErrors": [{ + "value": "request.Device.IP is required", + "comparison": "literal" + }] + } \ No newline at end of file diff --git a/adapters/richaudience/richaudiencetest/supplemental/notFoundParams.json b/adapters/richaudience/richaudiencetest/supplemental/notFoundParams.json new file mode 100644 index 00000000000..7b1cac83c54 --- /dev/null +++ b/adapters/richaudience/richaudiencetest/supplemental/notFoundParams.json @@ -0,0 +1,36 @@ +{ + "mockBidRequest": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 0, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD" + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": {} + } + }, + + "expectedMakeRequestsErrors": [{ + "value": "not found parameters ext in ImpID : div-gpt-ad-1460505748561-0", + "comparison": "literal" + }] + } \ No newline at end of file diff --git a/adapters/richaudience/richaudiencetest/supplemental/responseBlank.json b/adapters/richaudience/richaudiencetest/supplemental/responseBlank.json new file mode 100644 index 00000000000..d8f53b9f027 --- /dev/null +++ b/adapters/richaudience/richaudiencetest/supplemental/responseBlank.json @@ -0,0 +1,90 @@ +{ + "mockBidRequest": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": false + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": {} + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ortb.richaudience.com/ortb/?bidder=pbs", + "body": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "tagid" : "OsNsyeF68q", + "secure": 1, + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": false + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": { + + } + } + } + }, + "mockResponse": { + "status": 204, + "body": { + + } + } + } + ] + } \ No newline at end of file diff --git a/adapters/richaudience/richaudiencetest/supplemental/statusCode400.json b/adapters/richaudience/richaudiencetest/supplemental/statusCode400.json new file mode 100644 index 00000000000..8d67723c856 --- /dev/null +++ b/adapters/richaudience/richaudiencetest/supplemental/statusCode400.json @@ -0,0 +1,93 @@ +{ + "mockBidRequest": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": false + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": {} + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ortb.richaudience.com/ortb/?bidder=pbs", + "body": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "tagid" : "OsNsyeF68q", + "secure": 1, + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": false + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": { + + } + } + } + }, + "mockResponse": { + "status": 400 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.", + "comparison": "literal" + } + ] + } \ No newline at end of file diff --git a/adapters/richaudience/richaudiencetest/supplemental/statusCodeError.json b/adapters/richaudience/richaudiencetest/supplemental/statusCodeError.json new file mode 100644 index 00000000000..5fd05c12e6a --- /dev/null +++ b/adapters/richaudience/richaudiencetest/supplemental/statusCodeError.json @@ -0,0 +1,93 @@ +{ + "mockBidRequest": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": false + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": {} + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ortb.richaudience.com/ortb/?bidder=pbs", + "body": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "secure": 1, + "tagid" : "OsNsyeF68q", + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": false + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": { + + } + } + } + }, + "mockResponse": { + "status": 502 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 502. Run with request.debug = 1 for more info.", + "comparison": "literal" + } + ] + } \ No newline at end of file diff --git a/adapters/richaudience/richaudiencetest/supplemental/unexpectedStatusCode.json b/adapters/richaudience/richaudiencetest/supplemental/unexpectedStatusCode.json new file mode 100644 index 00000000000..ba66f0b6c28 --- /dev/null +++ b/adapters/richaudience/richaudiencetest/supplemental/unexpectedStatusCode.json @@ -0,0 +1,93 @@ +{ + "mockBidRequest": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": false + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": {} + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ortb.richaudience.com/ortb/?bidder=pbs", + "body": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "tagid" : "OsNsyeF68q", + "secure": 1, + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": false + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": { + + } + } + } + }, + "mockResponse": { + "status": 800 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 800. Run with request.debug = 1 for more info.", + "comparison": "literal" + } + ] + } \ No newline at end of file diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index 80c62df16a1..72b61628a74 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -1,51 +1,50 @@ package rubicon import ( - "bytes" - "context" "encoding/json" "fmt" - "github.com/buger/jsonparser" - "io/ioutil" "net/http" "net/url" "strconv" "strings" - "github.com/golang/glog" - "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/pbs" - "golang.org/x/net/context/ctxhttp" + + "github.com/buger/jsonparser" + "github.com/mxmCherry/openrtb/v15/openrtb2" ) const badvLimitSize = 50 type RubiconAdapter struct { - http *adapters.HTTPAdapter URI string XAPIUsername string XAPIPassword string } -func (a *RubiconAdapter) Name() string { - return "rubicon" +type rubiconContext struct { + Data json.RawMessage `json:"data"` } -func (a *RubiconAdapter) SkipNoCookies() bool { - return false +type rubiconData struct { + AdServer rubiconAdServer `json:"adserver"` + PbAdSlot string `json:"pbadslot"` } -type rubiconParams struct { - AccountId int `json:"accountId"` - SiteId int `json:"siteId"` - ZoneId int `json:"zoneId"` - Inventory json.RawMessage `json:"inventory,omitempty"` - Visitor json.RawMessage `json:"visitor,omitempty"` - Video rubiconVideoParams `json:"video"` +type rubiconAdServer struct { + Name string `json:"name"` + AdSlot string `json:"adslot"` +} + +type rubiconExtImpBidder struct { + Prebid *openrtb_ext.ExtImpPrebid `json:"prebid"` + Bidder json.RawMessage `json:"bidder"` + Gpid string `json:"gpid"` + Data json.RawMessage `json:"data"` + Context rubiconContext `json:"context"` } type bidRequestExt struct { @@ -134,15 +133,6 @@ type rubiconBannerExt struct { } // ***** Video Extension ***** -type rubiconVideoParams struct { - Language string `json:"language,omitempty"` - PlayerHeight int `json:"playerHeight,omitempty"` - PlayerWidth int `json:"playerWidth,omitempty"` - VideoSizeID int `json:"size_id,omitempty"` - Skip int `json:"skip,omitempty"` - SkipDelay int `json:"skipdelay,omitempty"` -} - type rubiconVideoExt struct { Skip int `json:"skip,omitempty"` SkipDelay int `json:"skipdelay,omitempty"` @@ -154,19 +144,6 @@ type rubiconVideoExtRP struct { SizeID int `json:"size_id,omitempty"` } -type rubiconTargetingExt struct { - RP rubiconTargetingExtRP `json:"rp"` -} - -type rubiconTargetingExtRP struct { - Targeting []rubiconTargetingObj `json:"targeting"` -} - -type rubiconTargetingObj struct { - Key string `json:"key"` - Values []string `json:"values"` -} - type rubiconDeviceExtRP struct { PixelRatio float64 `json:"pixelratio"` } @@ -175,10 +152,6 @@ type rubiconDeviceExt struct { RP rubiconDeviceExtRP `json:"rp"` } -type rubiconUser struct { - Language string `json:"language"` -} - type rubiconBidResponse struct { openrtb2.BidResponse SeatBid []rubiconSeatBid `json:"seatbid,omitempty"` @@ -303,7 +276,7 @@ type mappedRubiconUidsParam struct { liverampIdl string } -//MAS algorithm +// MAS algorithm func findPrimary(alt []int) (int, []int) { min, pos, primary := 0, 0, 0 for i, size := range alt { @@ -353,273 +326,6 @@ func parseRubiconSizes(sizes []openrtb2.Format) (primary int, alt []int, err err return } -func (a *RubiconAdapter) callOne(ctx context.Context, reqJSON bytes.Buffer) (result adapters.CallOneResult, err error) { - httpReq, err := http.NewRequest("POST", a.URI, &reqJSON) - httpReq.Header.Add("Content-Type", "application/json;charset=utf-8") - httpReq.Header.Add("Accept", "application/json") - httpReq.Header.Add("User-Agent", "prebid-server/1.0") - httpReq.SetBasicAuth(a.XAPIUsername, a.XAPIPassword) - - rubiResp, e := ctxhttp.Do(ctx, a.http.Client, httpReq) - if e != nil { - err = e - return - } - - defer rubiResp.Body.Close() - body, _ := ioutil.ReadAll(rubiResp.Body) - result.ResponseBody = string(body) - - result.StatusCode = rubiResp.StatusCode - - if rubiResp.StatusCode == 204 { - return - } - - if rubiResp.StatusCode == http.StatusBadRequest { - err = &errortypes.BadInput{ - Message: fmt.Sprintf("HTTP status %d; body: %s", rubiResp.StatusCode, result.ResponseBody), - } - } - - if rubiResp.StatusCode != http.StatusOK { - err = &errortypes.BadServerResponse{ - Message: fmt.Sprintf("HTTP status %d; body: %s", rubiResp.StatusCode, result.ResponseBody), - } - return - } - - var bidResp openrtb2.BidResponse - err = json.Unmarshal(body, &bidResp) - if err != nil { - err = &errortypes.BadServerResponse{ - Message: err.Error(), - } - return - } - if len(bidResp.SeatBid) == 0 { - return - } - if len(bidResp.SeatBid[0].Bid) == 0 { - return - } - bid := bidResp.SeatBid[0].Bid[0] - - result.Bid = &pbs.PBSBid{ - AdUnitCode: bid.ImpID, - Price: bid.Price, - Adm: bid.AdM, - Creative_id: bid.CrID, - // for video, the width and height are undefined as there's no corresponding return value from XAPI - Width: bid.W, - Height: bid.H, - DealId: bid.DealID, - } - - // Pull out any server-side determined targeting - var rpExtTrg rubiconTargetingExt - - if err := json.Unmarshal([]byte(bid.Ext), &rpExtTrg); err == nil { - // Converting string => array(string) to string => string - targeting := make(map[string]string) - - // Only pick off the first for now - for _, target := range rpExtTrg.RP.Targeting { - targeting[target.Key] = target.Values[0] - } - - result.Bid.AdServerTargeting = targeting - } - - return -} - -type callOneObject struct { - requestJson bytes.Buffer - mediaType pbs.MediaType -} - -func (a *RubiconAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { - callOneObjects := make([]callOneObject, 0, len(bidder.AdUnits)) - supportedMediaTypes := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} - - rubiReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), supportedMediaTypes) - if err != nil { - return nil, err - } - - rubiReqImpCopy := rubiReq.Imp - - for i, unit := range bidder.AdUnits { - // Fixes some segfaults. Since this is legacy code, I'm not looking into it too deeply - if len(rubiReqImpCopy) <= i { - break - } - // Only grab this ad unit - // Not supporting multi-media-type add-unit yet - thisImp := rubiReqImpCopy[i] - - // Amend it with RP-specific information - var params rubiconParams - err = json.Unmarshal(unit.Params, ¶ms) - if err != nil { - return nil, &errortypes.BadInput{ - Message: err.Error(), - } - } - - var mint, mintVersion string - mint = "prebid" - mintVersion = req.SDK.Source + "_" + req.SDK.Platform + "_" + req.SDK.Version - track := rubiconImpExtRPTrack{Mint: mint, MintVersion: mintVersion} - - impExt := rubiconImpExt{RP: rubiconImpExtRP{ - ZoneID: params.ZoneId, - Target: params.Inventory, - Track: track, - }} - thisImp.Ext, err = json.Marshal(&impExt) - if err != nil { - continue - } - - // Copy the $.user object and amend with $.user.ext.rp.target - // Copy avoids race condition since it points to ref & shared with other adapters - userCopy := *rubiReq.User - userExt := rubiconUserExt{RP: rubiconUserExtRP{Target: params.Visitor}} - userCopy.Ext, err = json.Marshal(&userExt) - // Assign back our copy - rubiReq.User = &userCopy - - deviceCopy := *rubiReq.Device - deviceExt := rubiconDeviceExt{RP: rubiconDeviceExtRP{PixelRatio: rubiReq.Device.PxRatio}} - deviceCopy.Ext, err = json.Marshal(&deviceExt) - rubiReq.Device = &deviceCopy - - if thisImp.Video != nil { - - videoSizeId := params.Video.VideoSizeID - if videoSizeId == 0 { - resolvedSizeId, err := resolveVideoSizeId(thisImp.Video.Placement, thisImp.Instl, thisImp.ID) - if err == nil { - videoSizeId = resolvedSizeId - } else { - continue - } - } - - videoExt := rubiconVideoExt{Skip: params.Video.Skip, SkipDelay: params.Video.SkipDelay, RP: rubiconVideoExtRP{SizeID: videoSizeId}} - thisImp.Video.Ext, err = json.Marshal(&videoExt) - } else { - primarySizeID, altSizeIDs, err := parseRubiconSizes(unit.Sizes) - if err != nil { - continue - } - bannerExt := rubiconBannerExt{RP: rubiconBannerExtRP{SizeID: primarySizeID, AltSizeIDs: altSizeIDs, MIME: "text/html"}} - thisImp.Banner.Ext, err = json.Marshal(&bannerExt) - } - - siteExt := rubiconSiteExt{RP: rubiconSiteExtRP{SiteID: params.SiteId}} - pubExt := rubiconPubExt{RP: rubiconPubExtRP{AccountID: params.AccountId}} - var rubiconUser rubiconUser - err = json.Unmarshal(req.PBSUser, &rubiconUser) - - if rubiReq.Site != nil { - siteCopy := *rubiReq.Site - siteCopy.Ext, err = json.Marshal(&siteExt) - siteCopy.Publisher = &openrtb2.Publisher{} - siteCopy.Publisher.Ext, err = json.Marshal(&pubExt) - siteCopy.Content = &openrtb2.Content{} - siteCopy.Content.Language = rubiconUser.Language - rubiReq.Site = &siteCopy - } else { - site := &openrtb2.Site{} - site.Content = &openrtb2.Content{} - site.Content.Language = rubiconUser.Language - rubiReq.Site = site - } - - if rubiReq.App != nil { - appCopy := *rubiReq.App - appCopy.Ext, err = json.Marshal(&siteExt) - appCopy.Publisher = &openrtb2.Publisher{} - appCopy.Publisher.Ext, err = json.Marshal(&pubExt) - rubiReq.App = &appCopy - } - - rubiReq.Imp = []openrtb2.Imp{thisImp} - - var reqBuffer bytes.Buffer - err = json.NewEncoder(&reqBuffer).Encode(rubiReq) - if err != nil { - return nil, err - } - callOneObjects = append(callOneObjects, callOneObject{reqBuffer, unit.MediaTypes[0]}) - } - if len(callOneObjects) == 0 { - return nil, &errortypes.BadInput{ - Message: "Invalid ad unit/imp", - } - } - - ch := make(chan adapters.CallOneResult) - for _, obj := range callOneObjects { - go func(bidder *pbs.PBSBidder, reqJSON bytes.Buffer, mediaType pbs.MediaType) { - result, err := a.callOne(ctx, reqJSON) - result.Error = err - if result.Bid != nil { - result.Bid.BidderCode = bidder.BidderCode - result.Bid.BidID = bidder.LookupBidID(result.Bid.AdUnitCode) - if result.Bid.BidID == "" { - result.Error = &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unknown ad unit code '%s'", result.Bid.AdUnitCode), - } - result.Bid = nil - } else { - // no need to check whether mediaTypes is nil or length of zero, pbs.ParsePBSRequest will cover - // these cases. - // for media types other than banner and video, pbs.ParseMediaType will throw error. - // we may want to create a map/switch cases to support more media types in the future. - if mediaType == pbs.MEDIA_TYPE_VIDEO { - result.Bid.CreativeMediaType = string(openrtb_ext.BidTypeVideo) - } else { - result.Bid.CreativeMediaType = string(openrtb_ext.BidTypeBanner) - } - } - } - ch <- result - }(bidder, obj.requestJson, obj.mediaType) - } - - bids := make(pbs.PBSBidSlice, 0) - for i := 0; i < len(callOneObjects); i++ { - result := <-ch - if result.Bid != nil && result.Bid.Price != 0 { - bids = append(bids, result.Bid) - } - if req.IsDebug { - debug := &pbs.BidderDebug{ - RequestURI: a.URI, - RequestBody: callOneObjects[i].requestJson.String(), - StatusCode: result.StatusCode, - ResponseBody: result.ResponseBody, - } - bidder.Debug = append(bidder.Debug, debug) - } - if result.Error != nil { - if glog.V(2) { - glog.Infof("Error from rubicon adapter: %v", result.Error) - } - err = result.Error - } - } - - if len(bids) == 0 { - return nil, err - } - return bids, nil -} - func resolveVideoSizeId(placement openrtb2.VideoPlacementType, instl int8, impId string) (sizeID int, err error) { if placement != 0 { if placement == 1 { @@ -665,19 +371,6 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func NewRubiconLegacyAdapter(httpConfig *adapters.HTTPAdapterConfig, uri string, xuser string, xpass string, tracker string) *RubiconAdapter { - a := adapters.NewHTTPAdapter(httpConfig) - - uri = appendTrackerToUrl(uri, tracker) - - return &RubiconAdapter{ - http: a, - URI: uri, - XAPIUsername: xuser, - XAPIPassword: xpass, - } -} - func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { numRequests := len(request.Imp) errs := make([]error, 0, len(request.Imp)) @@ -693,7 +386,7 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada rubiconRequest := *request for _, imp := range requestImpCopy { - var bidderExt adapters.ExtImpBidder + var bidderExt rubiconExtImpBidder if err = json.Unmarshal(imp.Ext, &bidderExt); err != nil { errs = append(errs, &errortypes.BadInput{ Message: err.Error(), @@ -709,12 +402,7 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada continue } - target, err := updateImpRpTargetWithFpdAttributes(rubiconExt, imp, request.Site, request.App) - if err != nil { - errs = append(errs, err) - continue - } - adSlot, err := getAdSlot(imp) + target, err := updateImpRpTargetWithFpdAttributes(bidderExt, rubiconExt, imp, request.Site, request.App) if err != nil { errs = append(errs, err) continue @@ -726,8 +414,9 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada Target: target, Track: rubiconImpExtRPTrack{Mint: "", MintVersion: ""}, }, - GPID: adSlot, + GPID: bidderExt.Gpid, } + imp.Ext, err = json.Marshal(&impExt) if err != nil { errs = append(errs, err) @@ -918,8 +607,9 @@ func resolveBidFloor(bidFloor float64, bidFloorCur string, reqInfo *adapters.Ext return bidFloor, nil } -func updateImpRpTargetWithFpdAttributes(extImp openrtb_ext.ExtImpRubicon, imp openrtb2.Imp, - site *openrtb2.Site, app *openrtb2.App) (json.RawMessage, error) { +func updateImpRpTargetWithFpdAttributes(extImp rubiconExtImpBidder, extImpRubicon openrtb_ext.ExtImpRubicon, + imp openrtb2.Imp, site *openrtb2.Site, app *openrtb2.App) (json.RawMessage, error) { + existingTarget, _, _, err := jsonparser.Get(imp.Ext, "rp", "target") if isNotKeyPathError(err) { return nil, err @@ -928,7 +618,7 @@ func updateImpRpTargetWithFpdAttributes(extImp openrtb_ext.ExtImpRubicon, imp op if err != nil { return nil, err } - err = populateFirstPartyDataAttributes(extImp.Inventory, target) + err = populateFirstPartyDataAttributes(extImpRubicon.Inventory, target) if err != nil { return nil, err } @@ -974,28 +664,41 @@ func updateImpRpTargetWithFpdAttributes(extImp openrtb_ext.ExtImpRubicon, imp op } } - impExtContextAttributes, _, _, err := jsonparser.Get(imp.Ext, "context", "data") + if len(extImp.Context.Data) > 0 { + err = populateFirstPartyDataAttributes(extImp.Context.Data, target) + } else if len(extImp.Data) > 0 { + err = populateFirstPartyDataAttributes(extImp.Data, target) + } if isNotKeyPathError(err) { return nil, err } - if len(impExtContextAttributes) > 0 { - err = populateFirstPartyDataAttributes(impExtContextAttributes, target) + var data rubiconData + if len(extImp.Data) > 0 { + err := json.Unmarshal(extImp.Data, &data) if err != nil { return nil, err } - } else if impExtDataAttributes, _, _, err := jsonparser.Get(imp.Ext, "data"); err == nil && len(impExtDataAttributes) > 0 { - err = populateFirstPartyDataAttributes(impExtDataAttributes, target) + } + var contextData rubiconData + if len(extImp.Context.Data) > 0 { + err := json.Unmarshal(extImp.Context.Data, &contextData) if err != nil { return nil, err } } - if isNotKeyPathError(err) { - return nil, err + + if data.PbAdSlot != "" { + target["pbadslot"] = data.PbAdSlot + } else { + dfpAdUnitCode := extractDfpAdUnitCode(data, contextData) + if dfpAdUnitCode != "" { + target["dfp_ad_unit_code"] = dfpAdUnitCode + } } - if len(extImp.Keywords) > 0 { - addStringArrayAttribute(extImp.Keywords, target, "keywords") + if len(extImpRubicon.Keywords) > 0 { + addStringArrayAttribute(extImpRubicon.Keywords, target, "keywords") } updatedTarget, err := json.Marshal(target) if err != nil { @@ -1004,6 +707,16 @@ func updateImpRpTargetWithFpdAttributes(extImp openrtb_ext.ExtImpRubicon, imp op return updatedTarget, nil } +func extractDfpAdUnitCode(data rubiconData, contextData rubiconData) string { + if contextData.AdServer.Name == "gam" && contextData.AdServer.AdSlot != "" { + return contextData.AdServer.AdSlot + } else if data.AdServer.Name == "gam" && data.AdServer.AdSlot != "" { + return data.AdServer.AdSlot + } + + return "" +} + func isNotKeyPathError(err error) bool { return err != nil && err != jsonparser.KeyPathNotFoundError } @@ -1016,62 +729,6 @@ func addStringArrayAttribute(attribute []string, target map[string]interface{}, target[attributeName] = attribute } -func getAdSlot(imp openrtb2.Imp) (string, error) { - var adSlot string - var parsingError error - jsonparser.EachKey(imp.Ext, func(idx int, value []byte, vt jsonparser.ValueType, err error) { - switch idx { - case 0: - adServerContextName, err := jsonparser.GetString(value, "name") - if isNotKeyPathError(err) { - parsingError = err - return - } - if adServerContextName == "gam" { - contextAdSlot, err := jsonparser.GetString(value, "adslot") - if isNotKeyPathError(err) { - parsingError = err - return - } - adSlot = contextAdSlot - } - } - }, []string{"context", "data", "adserver"}) - - if parsingError != nil { - return "", parsingError - } - - if adSlot != "" { - return adSlot, nil - } - - jsonparser.EachKey(imp.Ext, func(idx int, value []byte, vt jsonparser.ValueType, err error) { - switch idx { - case 0: - adServerDataName, err := jsonparser.GetString(value, "name") - if isNotKeyPathError(err) { - parsingError = err - return - } - if adServerDataName == "gam" { - dataAdSlot, err := jsonparser.GetString(value, "adslot") - if isNotKeyPathError(err) { - parsingError = err - return - } - adSlot = dataAdSlot - } - } - }, []string{"data", "adserver"}) - - if parsingError != nil { - return "", parsingError - } - - return adSlot, nil -} - func updateUserRpTargetWithFpdAttributes(visitor json.RawMessage, user openrtb2.User) (json.RawMessage, error) { existingTarget, _, _, err := jsonparser.Get(user.Ext, "rp", "target") if isNotKeyPathError(err) { @@ -1177,6 +834,7 @@ func rawJSONToMap(message json.RawMessage) (map[string]interface{}, error) { return mapFromRawJSON(message) } + func mapFromRawJSON(message json.RawMessage) (map[string]interface{}, error) { targetAsMap := make(map[string]interface{}) err := json.Unmarshal(message, &targetAsMap) diff --git a/adapters/rubicon/rubicon_test.go b/adapters/rubicon/rubicon_test.go index 9bfa04fa78f..83c9e99a216 100644 --- a/adapters/rubicon/rubicon_test.go +++ b/adapters/rubicon/rubicon_test.go @@ -1,27 +1,17 @@ package rubicon import ( - "bytes" - "context" "encoding/json" "errors" - "fmt" - "io/ioutil" "net/http" - "net/http/httptest" "strconv" - "strings" "testing" - "time" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/cache/dummycache" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/pbs" - "github.com/prebid/prebid-server/usersync" "github.com/buger/jsonparser" "github.com/mxmCherry/openrtb/v15/openrtb2" @@ -48,291 +38,17 @@ type rubiSetNetworkIdTestScenario struct { isNetworkIdSet bool } -type rubiTagInfo struct { - code string - zoneID int - bid float64 - content string - adServerTargeting map[string]string - mediaType string -} - type rubiBidInfo struct { - domain string - page string - accountID int - siteID int - tags []rubiTagInfo - deviceIP string - deviceUA string - buyerUID string - xapiuser string - xapipass string - delay time.Duration - visitorTargeting string - inventoryTargeting string - sdkVersion string - sdkPlatform string - sdkSource string - devicePxRatio float64 + domain string + page string + deviceIP string + deviceUA string + buyerUID string + devicePxRatio float64 } var rubidata rubiBidInfo -func DummyRubiconServer(w http.ResponseWriter, r *http.Request) { - defer func() { - err := r.Body.Close() - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }() - - body, err := ioutil.ReadAll(r.Body) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - var breq openrtb2.BidRequest - err = json.Unmarshal(body, &breq) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - if len(breq.Imp) > 1 { - http.Error(w, "Rubicon adapter only supports one Imp per request", http.StatusInternalServerError) - return - } - imp := breq.Imp[0] - var rix rubiconImpExt - err = json.Unmarshal(imp.Ext, &rix) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - impTargetingString, _ := json.Marshal(&rix.RP.Target) - if string(impTargetingString) != rubidata.inventoryTargeting { - http.Error(w, fmt.Sprintf("Inventory FPD targeting '%s' doesn't match '%s'", string(impTargetingString), rubidata.inventoryTargeting), http.StatusInternalServerError) - return - } - if rix.RP.Track.Mint != "prebid" { - http.Error(w, fmt.Sprintf("Track mint '%s' doesn't match '%s'", rix.RP.Track.Mint, "prebid"), http.StatusInternalServerError) - return - } - mintVersionString := rubidata.sdkSource + "_" + rubidata.sdkPlatform + "_" + rubidata.sdkVersion - if rix.RP.Track.MintVersion != mintVersionString { - http.Error(w, fmt.Sprintf("Track mint version '%s' doesn't match '%s'", rix.RP.Track.MintVersion, mintVersionString), http.StatusInternalServerError) - return - } - - ix := -1 - - for i, tag := range rubidata.tags { - if rix.RP.ZoneID == tag.zoneID { - ix = i - } - } - if ix == -1 { - http.Error(w, fmt.Sprintf("Zone %d not found", rix.RP.ZoneID), http.StatusInternalServerError) - return - } - - resp := openrtb2.BidResponse{ - ID: "test-response-id", - BidID: "test-bid-id", - Cur: "USD", - SeatBid: []openrtb2.SeatBid{ - { - Seat: "RUBICON", - Bid: make([]openrtb2.Bid, 2), - }, - }, - } - - if imp.Banner != nil { - var bix rubiconBannerExt - err = json.Unmarshal(imp.Banner.Ext, &bix) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - if bix.RP.SizeID != 15 { // 300x250 - http.Error(w, fmt.Sprintf("Primary size ID isn't 15"), http.StatusInternalServerError) - return - } - if len(bix.RP.AltSizeIDs) != 1 || bix.RP.AltSizeIDs[0] != 10 { // 300x600 - http.Error(w, fmt.Sprintf("Alt size ID isn't 10"), http.StatusInternalServerError) - return - } - if bix.RP.MIME != "text/html" { - http.Error(w, fmt.Sprintf("MIME isn't text/html"), http.StatusInternalServerError) - return - } - } - - if imp.Video != nil { - var vix rubiconVideoExt - err = json.Unmarshal(imp.Video.Ext, &vix) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - if len(imp.Video.MIMEs) == 0 { - http.Error(w, fmt.Sprintf("Empty imp.video.mimes array"), http.StatusInternalServerError) - return - } - if len(imp.Video.Protocols) == 0 { - http.Error(w, fmt.Sprintf("Empty imp.video.protocols array"), http.StatusInternalServerError) - return - } - for _, protocol := range imp.Video.Protocols { - if protocol < 1 || protocol > 8 { - http.Error(w, fmt.Sprintf("Invalid video protocol %d", protocol), http.StatusInternalServerError) - return - } - } - } - - targeting := "{\"rp\":{\"targeting\":[{\"key\":\"key1\",\"values\":[\"value1\"]},{\"key\":\"key2\",\"values\":[\"value2\"]}]}}" - rawTargeting := json.RawMessage(targeting) - - resp.SeatBid[0].Bid[0] = openrtb2.Bid{ - ID: "random-id", - ImpID: imp.ID, - Price: rubidata.tags[ix].bid, - AdM: rubidata.tags[ix].content, - Ext: rawTargeting, - } - - if breq.Site == nil { - http.Error(w, fmt.Sprintf("No site object sent"), http.StatusInternalServerError) - return - } - if breq.Site.Domain != rubidata.domain { - http.Error(w, fmt.Sprintf("Domain '%s' doesn't match '%s", breq.Site.Domain, rubidata.domain), http.StatusInternalServerError) - return - } - if breq.Site.Page != rubidata.page { - http.Error(w, fmt.Sprintf("Page '%s' doesn't match '%s", breq.Site.Page, rubidata.page), http.StatusInternalServerError) - return - } - var rsx rubiconSiteExt - err = json.Unmarshal(breq.Site.Ext, &rsx) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - if rsx.RP.SiteID != rubidata.siteID { - http.Error(w, fmt.Sprintf("SiteID '%d' doesn't match '%d", rsx.RP.SiteID, rubidata.siteID), http.StatusInternalServerError) - return - } - if breq.Site.Publisher == nil { - http.Error(w, fmt.Sprintf("No site.publisher object sent"), http.StatusInternalServerError) - return - } - var rpx rubiconPubExt - err = json.Unmarshal(breq.Site.Publisher.Ext, &rpx) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - if rpx.RP.AccountID != rubidata.accountID { - http.Error(w, fmt.Sprintf("AccountID '%d' doesn't match '%d'", rpx.RP.AccountID, rubidata.accountID), http.StatusInternalServerError) - return - } - if breq.Device.UA != rubidata.deviceUA { - http.Error(w, fmt.Sprintf("UA '%s' doesn't match '%s'", breq.Device.UA, rubidata.deviceUA), http.StatusInternalServerError) - return - } - if breq.Device.IP != rubidata.deviceIP { - http.Error(w, fmt.Sprintf("IP '%s' doesn't match '%s'", breq.Device.IP, rubidata.deviceIP), http.StatusInternalServerError) - return - } - if breq.Device.PxRatio != rubidata.devicePxRatio { - http.Error(w, fmt.Sprintf("Pixel ratio '%f' doesn't match '%f'", breq.Device.PxRatio, rubidata.devicePxRatio), http.StatusInternalServerError) - return - } - if breq.User.BuyerUID != rubidata.buyerUID { - http.Error(w, fmt.Sprintf("User ID '%s' doesn't match '%s'", breq.User.BuyerUID, rubidata.buyerUID), http.StatusInternalServerError) - return - } - - var rux rubiconUserExt - err = json.Unmarshal(breq.User.Ext, &rux) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - userTargetingString, _ := json.Marshal(&rux.RP.Target) - if string(userTargetingString) != rubidata.visitorTargeting { - http.Error(w, fmt.Sprintf("User FPD targeting '%s' doesn't match '%s'", string(userTargetingString), rubidata.visitorTargeting), http.StatusInternalServerError) - return - } - - if rubidata.delay > 0 { - <-time.After(rubidata.delay) - } - - js, err := json.Marshal(resp) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - w.Header().Set("Content-Type", "application/json") - w.Write(js) -} - -func TestRubiconBasicResponse(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(DummyRubiconServer)) - defer server.Close() - - an, ctx, pbReq := CreatePrebidRequest(server, t) - - bids, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - assert.Nil(t, err, "Should not have gotten an error: %v", err) - assert.Equal(t, 2, len(bids), "Received %d bids instead of 3", len(bids)) - - for _, bid := range bids { - matched := false - for _, tag := range rubidata.tags { - if bid.AdUnitCode == tag.code { - matched = true - - assert.Equal(t, "rubicon", bid.BidderCode, "Incorrect BidderCode '%s'", bid.BidderCode) - - assert.Equal(t, tag.bid, bid.Price, "Incorrect bid price '%.2f' expected '%.2f'", bid.Price, tag.bid) - - assert.Equal(t, tag.content, bid.Adm, "Incorrect bid markup '%s' expected '%s'", bid.Adm, tag.content) - - assert.Equal(t, bid.AdServerTargeting, tag.adServerTargeting, - "Incorrect targeting '%+v' expected '%+v'", bid.AdServerTargeting, tag.adServerTargeting) - - assert.Equal(t, tag.mediaType, bid.CreativeMediaType, "Incorrect media type '%s' expected '%s'", bid.CreativeMediaType, tag.mediaType) - } - } - assert.True(t, matched, "Received bid for unknown ad unit '%s'", bid.AdUnitCode) - } - - // same test but with request timing out - rubidata.delay = 20 * time.Millisecond - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) - defer cancel() - - bids, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - assert.NotNil(t, err, "Should have gotten a timeout error: %v", err) -} - -func TestRubiconUserSyncInfo(t *testing.T) { - conf := *adapters.DefaultHTTPAdapterConfig - an := NewRubiconLegacyAdapter(&conf, "uri", "xuser", "xpass", "pbs-test-tracker") - - assert.Equal(t, "rubicon", an.Name(), "Name '%s' != 'rubicon'", an.Name()) - - assert.False(t, an.SkipNoCookies(), "SkipNoCookies should be false") -} - func getTestSizes() map[int]openrtb2.Format { return map[int]openrtb2.Format{ 15: {W: 300, H: 250}, @@ -703,368 +419,6 @@ func (m mockCurrencyConversion) GetRates() *map[string]map[string]float64 { return args.Get(0).(*map[string]map[string]float64) } -func TestNoContentResponse(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNoContent) - })) - defer server.Close() - - an, ctx, pbReq := CreatePrebidRequest(server, t) - bids, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - - assert.Empty(t, bids, "Length of bids should be 0 instead of: %v", len(bids)) - - assert.Equal(t, 204, pbReq.Bidders[0].Debug[0].StatusCode, - "StatusCode should be 204 instead of: %v", pbReq.Bidders[0].Debug[0].StatusCode) - - assert.Nil(t, err, "Should not have gotten an error: %v", err) -} - -func TestNotFoundResponse(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotFound) - })) - defer server.Close() - - an, ctx, pbReq := CreatePrebidRequest(server, t) - _, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - - assert.Equal(t, 404, pbReq.Bidders[0].Debug[0].StatusCode, - "StatusCode should be 404 instead of: %v", pbReq.Bidders[0].Debug[0].StatusCode) - - assert.NotNil(t, err, "Should have gotten an error: %v", err) - - assert.True(t, strings.HasPrefix(err.Error(), "HTTP status 404"), - "Should start with 'HTTP status' instead of: %v", err.Error()) -} - -func TestWrongFormatResponse(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("This is text.")) - })) - defer server.Close() - - an, ctx, pbReq := CreatePrebidRequest(server, t) - _, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - - assert.Equal(t, 200, pbReq.Bidders[0].Debug[0].StatusCode, - "StatusCode should be 200 instead of: %v", pbReq.Bidders[0].Debug[0].StatusCode) - - assert.NotNil(t, err, "Should have gotten an error: %v", err) - - assert.True(t, strings.HasPrefix(err.Error(), "invalid character"), - "Should start with 'invalid character' instead of: %v", err) -} - -func TestZeroSeatBidResponse(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - resp := openrtb2.BidResponse{ - ID: "test-response-id", - BidID: "test-bid-id", - Cur: "USD", - SeatBid: []openrtb2.SeatBid{}, - } - js, _ := json.Marshal(resp) - w.Write(js) - })) - defer server.Close() - - an, ctx, pbReq := CreatePrebidRequest(server, t) - bids, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - - assert.Empty(t, bids, "Length of bids should be 0 instead of: %v", len(bids)) - - assert.Nil(t, err, "Should not have gotten an error: %v", err) -} - -func TestEmptyBidResponse(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - resp := openrtb2.BidResponse{ - ID: "test-response-id", - BidID: "test-bid-id", - Cur: "USD", - SeatBid: []openrtb2.SeatBid{ - { - Seat: "RUBICON", - Bid: make([]openrtb2.Bid, 0), - }, - }, - } - js, _ := json.Marshal(resp) - w.Write(js) - })) - defer server.Close() - - an, ctx, pbReq := CreatePrebidRequest(server, t) - bids, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - - assert.Empty(t, bids, "Length of bids should be 0 instead of: %v", len(bids)) - - assert.Nil(t, err, "Should not have gotten an error: %v", err) -} - -func TestWrongBidIdResponse(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - resp := openrtb2.BidResponse{ - ID: "test-response-id", - BidID: "test-bid-id", - Cur: "USD", - SeatBid: []openrtb2.SeatBid{ - { - Seat: "RUBICON", - Bid: make([]openrtb2.Bid, 2), - }, - }, - } - resp.SeatBid[0].Bid[0] = openrtb2.Bid{ - ID: "random-id", - ImpID: "zma", - Price: 1.67, - AdM: "zma", - Ext: json.RawMessage("{\"rp\":{\"targeting\":[{\"key\":\"key1\",\"values\":[\"value1\"]},{\"key\":\"key2\",\"values\":[\"value2\"]}]}}"), - } - js, _ := json.Marshal(resp) - w.Write(js) - })) - defer server.Close() - - an, ctx, pbReq := CreatePrebidRequest(server, t) - bids, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - - assert.Empty(t, bids, "Length of bids should be 0 instead of: %v", len(bids)) - - assert.NotNil(t, err, "Should not have gotten an error: %v", err) - - assert.True(t, strings.HasPrefix(err.Error(), "Unknown ad unit code"), - "Should start with 'Unknown ad unit code' instead of: %v", err) -} - -func TestZeroPriceBidResponse(t *testing.T) { - - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - resp := openrtb2.BidResponse{ - ID: "test-response-id", - BidID: "test-bid-id", - Cur: "USD", - SeatBid: []openrtb2.SeatBid{ - { - Seat: "RUBICON", - Bid: make([]openrtb2.Bid, 1), - }, - }, - } - resp.SeatBid[0].Bid[0] = openrtb2.Bid{ - ID: "test-bid-id", - ImpID: "first-tag", - Price: 0, - AdM: "zma", - Ext: json.RawMessage("{\"rp\":{\"targeting\":[{\"key\":\"key1\",\"values\":[\"value1\"]},{\"key\":\"key2\",\"values\":[\"value2\"]}]}}"), - } - js, _ := json.Marshal(resp) - w.Write(js) - })) - defer server.Close() - - an, ctx, pbReq := CreatePrebidRequest(server, t) - b, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - - assert.Nil(t, b, "\n\n\n0 price bids are being included %d, err : %v", len(b), err) -} - -func TestDifferentRequest(t *testing.T) { - SIZE_ID := getTestSizes() - server := httptest.NewServer(http.HandlerFunc(DummyRubiconServer)) - defer server.Close() - - an, ctx, pbReq := CreatePrebidRequest(server, t) - - // test app not nil - pbReq.App = &openrtb2.App{ - ID: "com.test", - Name: "testApp", - } - - _, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - assert.NotNil(t, err, "Should have gotten an error: %v", err) - - // set app back to normal - pbReq.App = nil - - // test video media type - pbReq.Bidders[0].AdUnits[0].MediaTypes = []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO} - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - assert.NotNil(t, err, "Should have gotten an error: %v", err) - - // set media back to normal - pbReq.Bidders[0].AdUnits[0].MediaTypes = []pbs.MediaType{pbs.MEDIA_TYPE_BANNER} - - // test wrong params - pbReq.Bidders[0].AdUnits[0].Params = json.RawMessage(fmt.Sprintf("{\"zoneId\": %s, \"siteId\": %d, \"visitor\": %s, \"inventory\": %s}", "zma", rubidata.siteID, rubidata.visitorTargeting, rubidata.inventoryTargeting)) - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - assert.NotNil(t, err, "Should have gotten an error: %v", err) - - // set params back to normal - pbReq.Bidders[0].AdUnits[0].Params = json.RawMessage(fmt.Sprintf("{\"zoneId\": %d, \"siteId\": %d, \"accountId\": %d, \"visitor\": %s, \"inventory\": %s}", 8394, rubidata.siteID, rubidata.accountID, rubidata.visitorTargeting, rubidata.inventoryTargeting)) - - // test invalid size - pbReq.Bidders[0].AdUnits[0].Sizes = []openrtb2.Format{ - { - W: 2222, - H: 333, - }, - } - pbReq.Bidders[0].AdUnits[1].Sizes = []openrtb2.Format{ - { - W: 222, - H: 3333, - }, - { - W: 350, - H: 270, - }, - } - pbReq.Bidders[0].AdUnits = pbReq.Bidders[0].AdUnits[:len(pbReq.Bidders[0].AdUnits)-1] - b, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - assert.NotNil(t, err, "Should have gotten an error: %v", err) - - pbReq.Bidders[0].AdUnits[1].Sizes = []openrtb2.Format{ - { - W: 222, - H: 3333, - }, - SIZE_ID[10], - SIZE_ID[15], - } - b, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - assert.Nil(t, err, "Should have not gotten an error: %v", err) - - assert.Equal(t, 1, len(b), - "Filtering bids based on ad unit sizes failed. Got %d bids instead of 1, error = %v", len(b), err) -} - -func CreatePrebidRequest(server *httptest.Server, t *testing.T) (an *RubiconAdapter, ctx context.Context, pbReq *pbs.PBSRequest) { - SIZE_ID := getTestSizes() - rubidata = rubiBidInfo{ - domain: "nytimes.com", - page: "https://www.nytimes.com/2017/05/04/movies/guardians-of-the-galaxy-2-review-chris-pratt.html?hpw&rref=movies&action=click&pgtype=Homepage&module=well-region®ion=bottom-well&WT.nav=bottom-well&_r=0", - accountID: 7891, - siteID: 283282, - tags: make([]rubiTagInfo, 3), - deviceIP: "25.91.96.36", - deviceUA: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.1 Safari/603.1.30", - buyerUID: "need-an-actual-rp-id", - visitorTargeting: "[\"v1\",\"v2\"]", - inventoryTargeting: "[\"i1\",\"i2\"]", - sdkVersion: "2.0.0", - sdkPlatform: "iOS", - sdkSource: "some-sdk", - devicePxRatio: 4.0, - } - - targeting := make(map[string]string, 2) - targeting["key1"] = "value1" - targeting["key2"] = "value2" - - rubidata.tags[0] = rubiTagInfo{ - code: "first-tag", - zoneID: 8394, - bid: 1.67, - adServerTargeting: targeting, - mediaType: "banner", - } - rubidata.tags[1] = rubiTagInfo{ - code: "second-tag", - zoneID: 8395, - bid: 3.22, - adServerTargeting: targeting, - mediaType: "banner", - } - rubidata.tags[2] = rubiTagInfo{ - code: "video-tag", - zoneID: 7780, - bid: 23.12, - adServerTargeting: targeting, - mediaType: "video", - } - - conf := *adapters.DefaultHTTPAdapterConfig - an = NewRubiconLegacyAdapter(&conf, "uri", rubidata.xapiuser, rubidata.xapipass, "pbs-test-tracker") - an.URI = server.URL - - pbin := pbs.PBSRequest{ - AdUnits: make([]pbs.AdUnit, 3), - Device: &openrtb2.Device{PxRatio: rubidata.devicePxRatio}, - SDK: &pbs.SDK{Source: rubidata.sdkSource, Platform: rubidata.sdkPlatform, Version: rubidata.sdkVersion}, - } - - for i, tag := range rubidata.tags { - pbin.AdUnits[i] = pbs.AdUnit{ - Code: tag.code, - MediaTypes: []string{tag.mediaType}, - Sizes: []openrtb2.Format{ - SIZE_ID[10], - SIZE_ID[15], - }, - Bids: []pbs.Bids{ - { - BidderCode: "rubicon", - BidID: fmt.Sprintf("random-id-from-pbjs-%d", i), - Params: json.RawMessage(fmt.Sprintf("{\"zoneId\": %d, \"siteId\": %d, \"accountId\": %d, \"visitor\": %s, \"inventory\": %s}", tag.zoneID, rubidata.siteID, rubidata.accountID, rubidata.visitorTargeting, rubidata.inventoryTargeting)), - }, - }, - } - if tag.mediaType == "video" { - pbin.AdUnits[i].Video = pbs.PBSVideo{ - Mimes: []string{"video/mp4"}, - Minduration: 15, - Maxduration: 30, - Startdelay: 5, - Skippable: 0, - PlaybackMethod: 1, - Protocols: []int8{1, 2, 3, 4, 5}, - } - } - } - - body := new(bytes.Buffer) - err := json.NewEncoder(body).Encode(pbin) - if err != nil { - t.Fatalf("Json encoding failed: %v", err) - } - - req := httptest.NewRequest("POST", server.URL, body) - req.Header.Add("Referer", rubidata.page) - req.Header.Add("User-Agent", rubidata.deviceUA) - req.Header.Add("X-Real-IP", rubidata.deviceIP) - - pc := usersync.ParseCookieFromRequest(req, &config.HostCookie{}) - pc.TrySync("rubicon", rubidata.buyerUID) - fakewriter := httptest.NewRecorder() - - pc.SetCookieOnResponse(fakewriter, false, &config.HostCookie{Domain: ""}, 90*24*time.Hour) - req.Header.Add("Cookie", fakewriter.Header().Get("Set-Cookie")) - - cacheClient, _ := dummycache.New() - hcc := config.HostCookie{} - - pbReq, err = pbs.ParsePBSRequest(req, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, cacheClient, &hcc) - pbReq.IsDebug = true - - assert.Nil(t, err, "ParsePBSRequest failed: %v", err) - - assert.Equal(t, 1, len(pbReq.Bidders), - "ParsePBSRequest returned %d bidders instead of 1", len(pbReq.Bidders)) - - assert.Equal(t, "rubicon", pbReq.Bidders[0].BidderCode, - "ParsePBSRequest returned invalid bidder") - - ctx = context.TODO() - return -} - func TestOpenRTBRequest(t *testing.T) { SIZE_ID := getTestSizes() bidder := new(RubiconAdapter) @@ -1311,7 +665,12 @@ func TestOpenRTBRequestWithImpAndAdSlotIncluded(t *testing.T) { if err := json.Unmarshal(rubiconReq.Imp[0].Ext, &rpImpExt); err != nil { t.Fatal("Error unmarshalling imp.ext") } - assert.Equal(t, rpImpExt.GPID, "/test-adslot") + + dfpAdUnitCode, err := jsonparser.GetString(rpImpExt.RP.Target, "dfp_ad_unit_code") + if err != nil { + t.Fatal("Error extracting dfp_ad_unit_code") + } + assert.Equal(t, dfpAdUnitCode, "/test-adslot") } func TestOpenRTBFirstPartyDataPopulating(t *testing.T) { diff --git a/adapters/rubicon/rubicontest/exemplary/app-imp-fpd.json b/adapters/rubicon/rubicontest/exemplary/app-imp-fpd.json index eec396f8873..d688d7beee3 100644 --- a/adapters/rubicon/rubicontest/exemplary/app-imp-fpd.json +++ b/adapters/rubicon/rubicontest/exemplary/app-imp-fpd.json @@ -145,19 +145,26 @@ "data": { "adserver": { "name": "gam", - "adslot": "someAdSlot" + "adslot": "adSlotFromContextData" }, "dataAttr1": "dataVal1", "dataAttr2": "dataVal2" } }, + "data": { + "adserver": { + "name": "gam", + "adslot": "adSlotFromData" + } + }, "bidder": { "video": { }, "accountId": 1001, "siteId": 113932, "zoneId": 535510 - } + }, + "gpid": "gpid" } } ] @@ -306,9 +313,9 @@ "h": 576 }, "ext": { - "gpid": "someAdSlot", "rp": { "target": { + "dfp_ad_unit_code": "adSlotFromContextData", "dataAttr1": [ "dataVal1" ], @@ -348,7 +355,8 @@ "mint_version": "" }, "zone_id": 535510 - } + }, + "gpid": "gpid" } } ] diff --git a/adapters/rubicon/rubicontest/exemplary/simple-video.json b/adapters/rubicon/rubicontest/exemplary/simple-video.json index 592f9fb279f..bb0b73d6e58 100644 --- a/adapters/rubicon/rubicontest/exemplary/simple-video.json +++ b/adapters/rubicon/rubicontest/exemplary/simple-video.json @@ -132,6 +132,9 @@ "h": 576 }, "ext": { + "data": { + "pbadslot": "pbadslot" + }, "bidder": { "video": { }, @@ -314,6 +317,7 @@ "ext": { "rp": { "target": { + "pbadslot": "pbadslot", "pagecat": [ "val1", "val2" diff --git a/adapters/rubicon/rubicontest/exemplary/site-imp-fpd.json b/adapters/rubicon/rubicontest/exemplary/site-imp-fpd.json index 56f5ce6128e..e28244d5dbb 100644 --- a/adapters/rubicon/rubicontest/exemplary/site-imp-fpd.json +++ b/adapters/rubicon/rubicontest/exemplary/site-imp-fpd.json @@ -200,7 +200,7 @@ "data": { "adserver": { "name": "gam", - "adslot": "someAdSlot" + "adslot": "adSlotFromData" }, "dataAttr1": "dataVal1", "dataAttr2": "dataVal2" @@ -428,7 +428,6 @@ "h": 576 }, "ext": { - "gpid": "someAdSlot", "rp": { "target": { "dataAttr1": [ @@ -472,7 +471,8 @@ "sectioncat": [ "sectionCat1", "sectionCat2" - ] + ], + "dfp_ad_unit_code": "adSlotFromData" }, "track": { "mint": "", diff --git a/adapters/rubicon/rubicontest/exemplary/user-fpd.json b/adapters/rubicon/rubicontest/exemplary/user-fpd.json index b2be736fefa..9034c62ca34 100644 --- a/adapters/rubicon/rubicontest/exemplary/user-fpd.json +++ b/adapters/rubicon/rubicontest/exemplary/user-fpd.json @@ -257,7 +257,6 @@ "h": 576 }, "ext": { - "gpid": "someAdSlot", "rp": { "target": { "dataAttr1": [ @@ -274,7 +273,8 @@ ], "search": [ "someSearch" - ] + ], + "dfp_ad_unit_code": "someAdSlot" }, "track": { "mint": "", diff --git a/adapters/sharethrough/utils_test.go b/adapters/sharethrough/utils_test.go index b842cf0b0c0..70e97947880 100644 --- a/adapters/sharethrough/utils_test.go +++ b/adapters/sharethrough/utils_test.go @@ -151,10 +151,6 @@ type userAgentTest struct { expected bool } -type userAgentFailureTest struct { - input string -} - func runUserAgentTests(tests map[string]userAgentTest, fn func(string) bool, t *testing.T) { for testName, test := range tests { t.Logf("Test case: %s\n", testName) diff --git a/adapters/smaato/smaatotest/exemplary/simple-banner.json b/adapters/smaato/smaatotest/exemplary/simple-banner.json index babce4f892d..fee49ee9aca 100644 --- a/adapters/smaato/smaatotest/exemplary/simple-banner.json +++ b/adapters/smaato/smaatotest/exemplary/simple-banner.json @@ -31,6 +31,7 @@ } ] }, + "instl": 1, "bidfloor": 0.00123, "ext": { "bidder": { @@ -82,6 +83,7 @@ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", "tagid": "130563103", "bidfloor": 0.00123, + "instl": 1, "banner": { "h": 50, "w": 320, diff --git a/adapters/smaato/smaatotest/exemplary/video.json b/adapters/smaato/smaatotest/exemplary/video.json index 205c02ad84c..853a63b29f6 100644 --- a/adapters/smaato/smaatotest/exemplary/video.json +++ b/adapters/smaato/smaatotest/exemplary/video.json @@ -29,6 +29,7 @@ "rewarded": 0 } }, + "instl": 1, "bidfloor": 0.00123, "ext": { "bidder": { @@ -84,6 +85,7 @@ "id": "postbid_iframe", "tagid": "130563103", "bidfloor": 0.00123, + "instl": 1, "video": { "w": 1024, "h": 768, diff --git a/adapters/sonobi/sonobi.go b/adapters/sonobi/sonobi.go index 690d5f59f67..0ff71cdb0e5 100644 --- a/adapters/sonobi/sonobi.go +++ b/adapters/sonobi/sonobi.go @@ -25,10 +25,6 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -type sonobiParams struct { - TagID string `json:"TagID"` -} - // MakeRequests Makes the OpenRTB request payload func (a *SonobiAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error @@ -152,9 +148,3 @@ func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, Message: fmt.Sprintf("Failed to find impression \"%s\" ", impID), } } - -func addHeaderIfNonEmpty(headers http.Header, headerName string, headerValue string) { - if len(headerValue) > 0 { - headers.Add(headerName, headerValue) - } -} diff --git a/adapters/sovrn/sovrn.go b/adapters/sovrn/sovrn.go index 40969d3638e..98264ce3a1b 100644 --- a/adapters/sovrn/sovrn.go +++ b/adapters/sovrn/sovrn.go @@ -1,176 +1,23 @@ package sovrn import ( - "bytes" - "context" "encoding/json" "fmt" - "io/ioutil" "net/http" "net/url" - "sort" "strconv" "strings" - "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/pbs" - "golang.org/x/net/context/ctxhttp" + + "github.com/mxmCherry/openrtb/v15/openrtb2" ) type SovrnAdapter struct { - http *adapters.HTTPAdapter - URI string -} - -// Name - export adapter name */ -func (s *SovrnAdapter) Name() string { - return "sovrn" -} - -// FamilyName used for cookies and such -func (s *SovrnAdapter) FamilyName() string { - return "sovrn" -} - -func (s *SovrnAdapter) SkipNoCookies() bool { - return false -} - -// Call send bid requests to sovrn and receive responses -func (s *SovrnAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { - supportedMediaTypes := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER} - sReq, err := adapters.MakeOpenRTBGeneric(req, bidder, s.FamilyName(), supportedMediaTypes) - - if err != nil { - return nil, err - } - - sovrnReq := openrtb2.BidRequest{ - ID: sReq.ID, - Imp: sReq.Imp, - Site: sReq.Site, - User: sReq.User, - Regs: sReq.Regs, - } - - // add tag ids to impressions - for i, unit := range bidder.AdUnits { - var params openrtb_ext.ExtImpSovrn - err = json.Unmarshal(unit.Params, ¶ms) - if err != nil { - return nil, err - } - - // Fixes some segfaults. Since this is legacy code, I'm not looking into it too deeply - if len(sovrnReq.Imp) <= i { - break - } - sovrnReq.Imp[i].TagID = getTagid(params) - } - - reqJSON, err := json.Marshal(sovrnReq) - if err != nil { - return nil, err - } - - debug := &pbs.BidderDebug{ - RequestURI: s.URI, - } - - httpReq, _ := http.NewRequest("POST", s.URI, bytes.NewReader(reqJSON)) - httpReq.Header.Set("Content-Type", "application/json") - if sReq.Device != nil { - addHeaderIfNonEmpty(httpReq.Header, "User-Agent", sReq.Device.UA) - addHeaderIfNonEmpty(httpReq.Header, "X-Forwarded-For", sReq.Device.IP) - addHeaderIfNonEmpty(httpReq.Header, "Accept-Language", sReq.Device.Language) - if sReq.Device.DNT != nil { - addHeaderIfNonEmpty(httpReq.Header, "DNT", strconv.Itoa(int(*sReq.Device.DNT))) - } - } - if sReq.User != nil { - userID := strings.TrimSpace(sReq.User.BuyerUID) - if len(userID) > 0 { - httpReq.AddCookie(&http.Cookie{Name: "ljt_reader", Value: userID}) - } - } - sResp, err := ctxhttp.Do(ctx, s.http.Client, httpReq) - if err != nil { - return nil, err - } - defer sResp.Body.Close() - - debug.StatusCode = sResp.StatusCode - - if sResp.StatusCode == http.StatusNoContent { - return nil, nil - } - - body, err := ioutil.ReadAll(sResp.Body) - if err != nil { - return nil, err - } - responseBody := string(body) - - if sResp.StatusCode == http.StatusBadRequest { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("HTTP status %d; body: %s", sResp.StatusCode, responseBody), - } - } - - if sResp.StatusCode != http.StatusOK { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("HTTP status %d; body: %s", sResp.StatusCode, responseBody), - } - } - - if req.IsDebug { - debug.RequestBody = string(reqJSON) - bidder.Debug = append(bidder.Debug, debug) - debug.ResponseBody = responseBody - } - - var bidResp openrtb2.BidResponse - err = json.Unmarshal(body, &bidResp) - if err != nil { - return nil, &errortypes.BadServerResponse{ - Message: err.Error(), - } - } - - bids := make(pbs.PBSBidSlice, 0) - - for _, sb := range bidResp.SeatBid { - for _, bid := range sb.Bid { - bidID := bidder.LookupBidID(bid.ImpID) - if bidID == "" { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unknown ad unit code '%s'", bid.ImpID), - } - } - - adm, _ := url.QueryUnescape(bid.AdM) - pbid := pbs.PBSBid{ - BidID: bidID, - AdUnitCode: bid.ImpID, - BidderCode: bidder.BidderCode, - Price: bid.Price, - Adm: adm, - Creative_id: bid.CrID, - Width: bid.W, - Height: bid.H, - DealId: bid.DealID, - NURL: bid.NURL, - } - bids = append(bids, &pbid) - } - } - - sort.Sort(bids) - return bids, nil + URI string } func (s *SovrnAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { @@ -303,14 +150,6 @@ func getTagid(sovrnExt openrtb_ext.ExtImpSovrn) string { } } -// NewSovrnLegacyAdapter create a new SovrnAdapter instance -func NewSovrnLegacyAdapter(config *adapters.HTTPAdapterConfig, endpoint string) *SovrnAdapter { - return &SovrnAdapter{ - http: adapters.NewHTTPAdapter(config), - URI: endpoint, - } -} - // Builder builds a new instance of the Sovrn adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { bidder := &SovrnAdapter{ diff --git a/adapters/sovrn/sovrn_test.go b/adapters/sovrn/sovrn_test.go index 407c505437a..49ed52844f3 100644 --- a/adapters/sovrn/sovrn_test.go +++ b/adapters/sovrn/sovrn_test.go @@ -1,28 +1,11 @@ package sovrn import ( - "bytes" - "encoding/json" - "fmt" - "io/ioutil" - "net/http/httptest" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/pbs" - "github.com/prebid/prebid-server/usersync" - - "context" - "net/http" - - "strconv" - "time" - - "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/cache/dummycache" "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" ) func TestJsonSamples(t *testing.T) { @@ -35,262 +18,3 @@ func TestJsonSamples(t *testing.T) { adapterstest.RunJSONBidderTest(t, "sovrntest", bidder) } - -// ---------------------------------------------------------------------------- -// Code below this line tests the legacy, non-openrtb code flow. It can be deleted after we -// clean up the existing code and make everything openrtb2. - -var testSovrnUserId = "SovrnUser123" -var testUserAgent = "user-agent-test" -var testUrl = "http://news.pub/topnews" -var testIp = "123.123.123.123" - -func TestSovrnAdapterNames(t *testing.T) { - adapter := NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "http://sovrn/rtb/bid") - adapterstest.VerifyStringValue(adapter.Name(), "sovrn", t) - adapterstest.VerifyStringValue(adapter.FamilyName(), "sovrn", t) -} - -func TestSovrnAdapter_SkipNoCookies(t *testing.T) { - adapter := NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "http://sovrn/rtb/bid") - adapterstest.VerifyBoolValue(adapter.SkipNoCookies(), false, t) -} - -func TestSovrnOpenRtbRequest(t *testing.T) { - service := CreateSovrnService(adapterstest.BidOnTags("")) - server := service.Server - ctx := context.Background() - req := SampleSovrnRequest(1, t) - bidder := req.Bidders[0] - adapter := NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - adapter.Call(ctx, req, bidder) - - adapterstest.VerifyIntValue(len(service.LastBidRequest.Imp), 1, t) - adapterstest.VerifyStringValue(service.LastBidRequest.Imp[0].TagID, "123456", t) - adapterstest.VerifyBannerSize(service.LastBidRequest.Imp[0].Banner, 728, 90, t) - checkHttpRequest(*service.LastHttpRequest, t) -} - -func TestSovrnBiddingBehavior(t *testing.T) { - service := CreateSovrnService(adapterstest.BidOnTags("123456")) - server := service.Server - ctx := context.TODO() - req := SampleSovrnRequest(1, t) - bidder := req.Bidders[0] - adapter := NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - bids, _ := adapter.Call(ctx, req, bidder) - - adapterstest.VerifyIntValue(len(bids), 1, t) - adapterstest.VerifyStringValue(bids[0].AdUnitCode, "div-adunit-1", t) - adapterstest.VerifyStringValue(bids[0].BidderCode, "sovrn", t) - adapterstest.VerifyStringValue(bids[0].Adm, "
This is an Ad
", t) - adapterstest.VerifyStringValue(bids[0].Creative_id, "Cr-234", t) - adapterstest.VerifyIntValue(int(bids[0].Width), 728, t) - adapterstest.VerifyIntValue(int(bids[0].Height), 90, t) - adapterstest.VerifyIntValue(int(bids[0].Price*100), 210, t) - checkHttpRequest(*service.LastHttpRequest, t) -} - -/** - * Verify bidding behavior on multiple impressions, some impressions make a bid - */ -func TestSovrntMultiImpPartialBidding(t *testing.T) { - // setup server endpoint to return bid. - service := CreateSovrnService(adapterstest.BidOnTags("123456")) - server := service.Server - ctx := context.TODO() - req := SampleSovrnRequest(2, t) - bidder := req.Bidders[0] - adapter := NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - bids, _ := adapter.Call(ctx, req, bidder) - // two impressions sent. - // number of bids should be 1 - adapterstest.VerifyIntValue(len(service.LastBidRequest.Imp), 2, t) - adapterstest.VerifyIntValue(len(bids), 1, t) - adapterstest.VerifyStringValue(bids[0].AdUnitCode, "div-adunit-1", t) - checkHttpRequest(*service.LastHttpRequest, t) -} - -/** - * Verify bidding behavior on multiple impressions, all impressions passed back. - */ -func TestSovrnMultiImpAllBid(t *testing.T) { - // setup server endpoint to return bid. - service := CreateSovrnService(adapterstest.BidOnTags("123456,123457")) - server := service.Server - ctx := context.TODO() - req := SampleSovrnRequest(2, t) - bidder := req.Bidders[0] - adapter := NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - bids, _ := adapter.Call(ctx, req, bidder) - // two impressions sent. - // number of bids should be 1 - adapterstest.VerifyIntValue(len(service.LastBidRequest.Imp), 2, t) - adapterstest.VerifyIntValue(len(bids), 2, t) - adapterstest.VerifyStringValue(bids[0].AdUnitCode, "div-adunit-1", t) - adapterstest.VerifyStringValue(bids[1].AdUnitCode, "div-adunit-2", t) - checkHttpRequest(*service.LastHttpRequest, t) -} - -func checkHttpRequest(req http.Request, t *testing.T) { - adapterstest.VerifyStringValue(req.Header.Get("Accept-Language"), "murican", t) - var cookie, _ = req.Cookie("ljt_reader") - adapterstest.VerifyStringValue((*cookie).Value, testSovrnUserId, t) - adapterstest.VerifyStringValue(req.Header.Get("User-Agent"), testUserAgent, t) - adapterstest.VerifyStringValue(req.Header.Get("Content-Type"), "application/json", t) - adapterstest.VerifyStringValue(req.Header.Get("X-Forwarded-For"), testIp, t) - adapterstest.VerifyStringValue(req.Header.Get("DNT"), "0", t) -} - -func SampleSovrnRequest(numberOfImpressions int, t *testing.T) *pbs.PBSRequest { - dnt := int8(0) - device := openrtb2.Device{ - Language: "murican", - DNT: &dnt, - } - - user := openrtb2.User{ - ID: testSovrnUserId, - } - - req := pbs.PBSRequest{ - AccountID: "1", - AdUnits: make([]pbs.AdUnit, 2), - Device: &device, - User: &user, - } - - tagID := 123456 - - for i := 0; i < numberOfImpressions; i++ { - req.AdUnits[i] = pbs.AdUnit{ - Code: fmt.Sprintf("div-adunit-%d", i+1), - Sizes: []openrtb2.Format{ - { - W: 728, - H: 90, - }, - }, - Bids: []pbs.Bids{ - { - BidderCode: "sovrn", - BidID: fmt.Sprintf("Bid-%d", i+1), - Params: json.RawMessage(fmt.Sprintf("{\"tagid\": \"%s\" }", strconv.Itoa(tagID+i))), - }, - }, - } - - } - - body := new(bytes.Buffer) - err := json.NewEncoder(body).Encode(req) - if err != nil { - t.Fatalf("Error when serializing request") - } - - httpReq := httptest.NewRequest("POST", CreateSovrnService(adapterstest.BidOnTags("")).Server.URL, body) - httpReq.Header.Add("Referer", testUrl) - httpReq.Header.Add("User-Agent", testUserAgent) - httpReq.Header.Add("X-Forwarded-For", testIp) - pc := usersync.ParseCookieFromRequest(httpReq, &config.HostCookie{}) - pc.TrySync("sovrn", testSovrnUserId) - fakewriter := httptest.NewRecorder() - - pc.SetCookieOnResponse(fakewriter, false, &config.HostCookie{Domain: ""}, 90*24*time.Hour) - httpReq.Header.Add("Cookie", fakewriter.Header().Get("Set-Cookie")) - // parse the http request - cacheClient, _ := dummycache.New() - hcc := config.HostCookie{} - - parsedReq, err := pbs.ParsePBSRequest(httpReq, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, cacheClient, &hcc) - if err != nil { - t.Fatalf("Error when parsing request: %v", err) - } - return parsedReq - -} - -func TestNoContentResponse(t *testing.T) { - - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNoContent) - })) - defer server.Close() - - ctx := context.TODO() - req := SampleSovrnRequest(1, t) - bidder := req.Bidders[0] - adapter := NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - _, err := adapter.Call(ctx, req, bidder) - - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - -} - -func TestNotFoundResponse(t *testing.T) { - - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotFound) - })) - defer server.Close() - - ctx := context.TODO() - req := SampleSovrnRequest(1, t) - bidder := req.Bidders[0] - adapter := NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - _, err := adapter.Call(ctx, req, bidder) - - adapterstest.VerifyStringValue(err.Error(), "HTTP status 404; body: ", t) - -} - -func CreateSovrnService(tagsToBid map[string]bool) adapterstest.OrtbMockService { - service := adapterstest.OrtbMockService{} - var lastBidRequest openrtb2.BidRequest - var lastHttpReq http.Request - - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - lastHttpReq = *r - defer r.Body.Close() - body, err := ioutil.ReadAll(r.Body) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - var breq openrtb2.BidRequest - err = json.Unmarshal(body, &breq) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - lastBidRequest = breq - var bids []openrtb2.Bid - for i, imp := range breq.Imp { - if tagsToBid[imp.TagID] { - bids = append(bids, adapterstest.SampleBid(imp.Banner.W, imp.Banner.H, imp.ID, i+1)) - } - } - - // serialize the bids to openrtb2.BidResponse - js, _ := json.Marshal(openrtb2.BidResponse{ - SeatBid: []openrtb2.SeatBid{ - { - Bid: bids, - }, - }, - }) - w.Header().Set("Content-Type", "application/json") - w.Write(js) - })) - - service.Server = server - service.LastBidRequest = &lastBidRequest - service.LastHttpRequest = &lastHttpReq - - return service -} diff --git a/adapters/videobyte/params_test.go b/adapters/videobyte/params_test.go new file mode 100644 index 00000000000..fa38836b03a --- /dev/null +++ b/adapters/videobyte/params_test.go @@ -0,0 +1,55 @@ +package videobyte + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +// This file actually intends to test static/bidder-params/videobyte.json +// +// These also validate the format of the external API: request.imp[i].ext.videobyte + +// TestValidParams makes sure that the videobyte schema accepts all imp.ext fields which we intend to support. +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderVideoByte, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected videobyte params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the videobyte schema rejects all the imp.ext fields we don't support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderVideoByte, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"pubId": "123"}`, + `{"pubId": "123", "placementId": "4"}`, + `{"pubId": "123", "nid": "5"}`, + `{"pubId": "123", "placementId": "4", "nid": "5"}`, +} + +var invalidParams = []string{ + `{"invalidParam" : "a"}`, + `{}`, + `{"placementId": "4"}`, + `{"nid": "5"}`, + `{"placementId": "4", "nid": "5"}`, +} diff --git a/adapters/videobyte/videobyte.go b/adapters/videobyte/videobyte.go new file mode 100644 index 00000000000..a4c0df615b8 --- /dev/null +++ b/adapters/videobyte/videobyte.go @@ -0,0 +1,158 @@ +package videobyte + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" + + "github.com/mxmCherry/openrtb/v15/openrtb2" +) + +type adapter struct { + endpoint string +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + impressions := request.Imp + adapterRequests := make([]*adapters.RequestData, 0, len(impressions)) + var errs []error + + for _, impression := range impressions { + impExt, err := parseExt(&impression) + if err != nil { + errs = append(errs, err) + continue + } + + request.Imp = []openrtb2.Imp{impression} + body, err := json.Marshal(request) + if err != nil { + errs = append(errs, err) + continue + } + + adapterRequests = append(adapterRequests, &adapters.RequestData{ + Method: http.MethodPost, + Uri: a.endpoint + "?" + getParams(impExt).Encode(), + Body: body, + Headers: getHeaders(request), + }) + } + + request.Imp = impressions + return adapterRequests, errs +} + +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + + if response.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Bad user input: HTTP status %d. Run with request.debug = 1 for more info", response.StatusCode), + }} + } + + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), + }} + } + + var ortbResponse openrtb2.BidResponse + err := json.Unmarshal(response.Body, &ortbResponse) + if err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Bad Server Response", + }} + } + + impIdToImp := make(map[string]*openrtb2.Imp) + for i := range internalRequest.Imp { + imp := internalRequest.Imp[i] + impIdToImp[imp.ID] = &imp + } + + bidderResponse := adapters.NewBidderResponseWithBidsCapacity(1) + for _, seatBid := range ortbResponse.SeatBid { + for i := range seatBid.Bid { + bid := seatBid.Bid[i] + bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: getMediaTypeForImp(impIdToImp[bid.ImpID]), + }) + } + } + + return bidderResponse, nil +} + +func getMediaTypeForImp(imp *openrtb2.Imp) openrtb_ext.BidType { + if imp != nil && imp.Banner != nil { + return openrtb_ext.BidTypeBanner + } + return openrtb_ext.BidTypeVideo +} + +func getParams(impExt *openrtb_ext.ExtImpVideoByte) url.Values { + params := url.Values{} + params.Add("source", "pbs") + params.Add("pid", impExt.PublisherId) + if impExt.PlacementId != "" { + params.Add("placementId", impExt.PlacementId) + } + if impExt.NetworkId != "" { + params.Add("nid", impExt.NetworkId) + } + return params +} + +func getHeaders(request *openrtb2.BidRequest) http.Header { + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + + if request.Site != nil { + if request.Site.Domain != "" { + headers.Add("Origin", request.Site.Domain) + } + if request.Site.Ref != "" { + headers.Set("Referer", request.Site.Ref) + } + } + return headers +} + +func parseExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpVideoByte, error) { + var bidderExt adapters.ExtImpBidder + + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("Ignoring imp id=%s, error while decoding extImpBidder, err: %s", imp.ID, err), + } + } + + impExt := openrtb_ext.ExtImpVideoByte{} + err := json.Unmarshal(bidderExt.Bidder, &impExt) + if err != nil { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("Ignoring imp id=%s, error while decoding impExt, err: %s", imp.ID, err), + } + } + + return &impExt, nil +} diff --git a/adapters/videobyte/videobyte_test.go b/adapters/videobyte/videobyte_test.go new file mode 100644 index 00000000000..9357a533997 --- /dev/null +++ b/adapters/videobyte/videobyte_test.go @@ -0,0 +1,17 @@ +package videobyte + +import ( + "testing" + + "github.com/prebid/prebid-server/config" + + "github.com/prebid/prebid-server/adapters/adapterstest" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder("videobyte", config.Adapter{Endpoint: "https://mock.videobyte.com"}) + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + adapterstest.RunJSONBidderTest(t, "videobytetest", bidder) +} diff --git a/adapters/videobyte/videobytetest/exemplary/banner.json b/adapters/videobyte/videobytetest/exemplary/banner.json new file mode 100644 index 00000000000..ecf733cca56 --- /dev/null +++ b/adapters/videobyte/videobytetest/exemplary/banner.json @@ -0,0 +1,145 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId", + "placementId": "examplePlacementId", + "nid": "exampleNetworkId" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Referer": [ + "http://referer.com/page-2" + ], + "Origin": [ + "example.com" + ], + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ] + }, + "uri": "https://mock.videobyte.com?nid=exampleNetworkId&pid=examplePublisherId&placementId=examplePlacementId&source=pbs", + "body": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId", + "placementId": "examplePlacementId", + "nid": "exampleNetworkId" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b", + "crid": "72745", + "adomain": [ + "ad-domain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "564", + "adm": "" + } + ], + "seat": "videobyte" + } + ], + "bidid": "test-request-id", + "id": "test-request-id" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b", + "crid": "72745", + "adomain": [ + "ad-domain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "564", + "adm": "" + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/videobyte/videobytetest/exemplary/empty-placement-network.json b/adapters/videobyte/videobytetest/exemplary/empty-placement-network.json new file mode 100644 index 00000000000..ddda5aa2a4c --- /dev/null +++ b/adapters/videobyte/videobytetest/exemplary/empty-placement-network.json @@ -0,0 +1,149 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Referer": [ + "http://referer.com/page-2" + ], + "Origin": [ + "example.com" + ], + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ] + }, + "uri": "https://mock.videobyte.com?pid=examplePublisherId&source=pbs", + "body": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b", + "crid": "72745", + "adomain": [ + "ad-domain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "564", + "adm": "" + } + ], + "seat": "videobyte" + } + ], + "bidid": "test-request-id", + "id": "test-request-id" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b", + "crid": "72745", + "adomain": [ + "ad-domain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "564", + "adm": "" + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/videobyte/videobytetest/exemplary/empty-site-domain-ref.json b/adapters/videobyte/videobytetest/exemplary/empty-site-domain-ref.json new file mode 100644 index 00000000000..e10a4aa52f4 --- /dev/null +++ b/adapters/videobyte/videobytetest/exemplary/empty-site-domain-ref.json @@ -0,0 +1,143 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId", + "placementId": "examplePlacementId", + "nid": "exampleNetworkId" + } + } + } + ], + "site": { + "page": "http://example.com/page-1" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ] + }, + "uri": "https://mock.videobyte.com?nid=exampleNetworkId&pid=examplePublisherId&placementId=examplePlacementId&source=pbs", + "body": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId", + "placementId": "examplePlacementId", + "nid": "exampleNetworkId" + } + } + } + ], + "site": { + "page": "http://example.com/page-1" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b", + "crid": "72745", + "adomain": [ + "ad-domain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "564", + "adm": "" + } + ], + "seat": "videobyte" + } + ], + "bidid": "test-request-id", + "id": "test-request-id" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b", + "crid": "72745", + "adomain": [ + "ad-domain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "564", + "adm": "" + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/videobyte/videobytetest/exemplary/multi-format.json b/adapters/videobyte/videobytetest/exemplary/multi-format.json new file mode 100644 index 00000000000..cf62ad79dff --- /dev/null +++ b/adapters/videobyte/videobytetest/exemplary/multi-format.json @@ -0,0 +1,161 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "video": { + "w": 300, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId", + "placementId": "examplePlacementId", + "nid": "exampleNetworkId" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Referer": [ + "http://referer.com/page-2" + ], + "Origin": [ + "example.com" + ], + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ] + }, + "uri": "https://mock.videobyte.com?nid=exampleNetworkId&pid=examplePublisherId&placementId=examplePlacementId&source=pbs", + "body": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "video": { + "w": 300, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId", + "placementId": "examplePlacementId", + "nid": "exampleNetworkId" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b", + "crid": "72745", + "adomain": [ + "ad-domain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "564", + "adm": "" + } + ], + "seat": "videobyte" + } + ], + "bidid": "test-request-id", + "id": "test-request-id" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b", + "crid": "72745", + "adomain": [ + "ad-domain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "564", + "adm": "" + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/videobyte/videobytetest/exemplary/multi-imp.json b/adapters/videobyte/videobytetest/exemplary/multi-imp.json new file mode 100644 index 00000000000..547a86da6a2 --- /dev/null +++ b/adapters/videobyte/videobytetest/exemplary/multi-imp.json @@ -0,0 +1,277 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id-1", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId-1", + "placementId": "examplePlacementId-1", + "nid": "exampleNetworkId-1" + } + } + }, + { + "id": "test-imp-id-2", + "video": { + "w": 800, + "h": 150, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId-2", + "placementId": "examplePlacementId-2", + "nid": "exampleNetworkId-2" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Referer": [ + "http://referer.com/page-2" + ], + "Origin": [ + "example.com" + ], + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ] + }, + "uri": "https://mock.videobyte.com?nid=exampleNetworkId-1&pid=examplePublisherId-1&placementId=examplePlacementId-1&source=pbs", + "body": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id-1", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId-1", + "placementId": "examplePlacementId-1", + "nid": "exampleNetworkId-1" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b", + "crid": "72745", + "adomain": [ + "ad-domain.com" + ], + "price": 3, + "impid": "test-imp-id-1", + "adid": "564", + "adm": "" + } + ], + "seat": "videobyte" + } + ], + "bidid": "test-request-id", + "id": "test-request-id" + } + } + }, + { + "expectedRequest": { + "method": "GET", + "headers": { + "Referer": [ + "http://referer.com/page-2" + ], + "Origin": [ + "example.com" + ], + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ] + }, + "uri": "https://mock.videobyte.com?nid=exampleNetworkId-2&pid=examplePublisherId-2&placementId=examplePlacementId-2&source=pbs", + "body": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id-2", + "video": { + "w": 800, + "h": 150, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId-2", + "placementId": "examplePlacementId-2", + "nid": "exampleNetworkId-2" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "fe69dd6d-e85c-4186-80a7-605b530bc23c", + "crid": "72746", + "adomain": [ + "ad-domain.com" + ], + "price": 4, + "impid": "test-imp-id-2", + "adid": "565", + "adm": "" + } + ], + "seat": "videobyte" + } + ], + "bidid": "test-request-id", + "id": "test-request-id" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b", + "crid": "72745", + "adomain": [ + "ad-domain.com" + ], + "price": 3, + "impid": "test-imp-id-1", + "adid": "564", + "adm": "" + }, + "type": "video" + } + ] + }, + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "fe69dd6d-e85c-4186-80a7-605b530bc23c", + "crid": "72746", + "adomain": [ + "ad-domain.com" + ], + "price": 4, + "impid": "test-imp-id-2", + "adid": "565", + "adm": "" + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/videobyte/videobytetest/exemplary/video.json b/adapters/videobyte/videobytetest/exemplary/video.json new file mode 100644 index 00000000000..7cb07808020 --- /dev/null +++ b/adapters/videobyte/videobytetest/exemplary/video.json @@ -0,0 +1,153 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId", + "placementId": "examplePlacementId", + "nid": "exampleNetworkId" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Referer": [ + "http://referer.com/page-2" + ], + "Origin": [ + "example.com" + ], + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ] + }, + "uri": "https://mock.videobyte.com?nid=exampleNetworkId&pid=examplePublisherId&placementId=examplePlacementId&source=pbs", + "body": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId", + "placementId": "examplePlacementId", + "nid": "exampleNetworkId" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b", + "crid": "72745", + "adomain": [ + "ad-domain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "564", + "adm": "" + } + ], + "seat": "videobyte" + } + ], + "bidid": "test-request-id", + "id": "test-request-id" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b", + "crid": "72745", + "adomain": [ + "ad-domain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "564", + "adm": "" + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/videobyte/videobytetest/supplemental/invalid-imp-ext-bidder.json b/adapters/videobyte/videobytetest/supplemental/invalid-imp-ext-bidder.json new file mode 100644 index 00000000000..1b806ed57ab --- /dev/null +++ b/adapters/videobyte/videobytetest/supplemental/invalid-imp-ext-bidder.json @@ -0,0 +1,45 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": "invalid-bidder-ext" + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Ignoring imp id=test-imp-id, error while decoding impExt, err: json: cannot unmarshal string into Go value of type openrtb_ext.ExtImpVideoByte", + "comparison": "literal" + } + ] +} diff --git a/adapters/videobyte/videobytetest/supplemental/invalid-imp-ext.json b/adapters/videobyte/videobytetest/supplemental/invalid-imp-ext.json new file mode 100644 index 00000000000..199f4c5570c --- /dev/null +++ b/adapters/videobyte/videobytetest/supplemental/invalid-imp-ext.json @@ -0,0 +1,43 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": "invalid-ext" + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Ignoring imp id=test-imp-id, error while decoding extImpBidder, err: json: cannot unmarshal string into Go value of type adapters.ExtImpBidder", + "comparison": "literal" + } + ] +} diff --git a/adapters/videobyte/videobytetest/supplemental/invalid-response.json b/adapters/videobyte/videobytetest/supplemental/invalid-response.json new file mode 100644 index 00000000000..35bce9a732e --- /dev/null +++ b/adapters/videobyte/videobytetest/supplemental/invalid-response.json @@ -0,0 +1,116 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId", + "placementId": "examplePlacementId", + "nid": "exampleNetworkId" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Referer": [ + "http://referer.com/page-2" + ], + "Origin": [ + "example.com" + ], + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ] + }, + "uri": "https://mock.videobyte.com?nid=exampleNetworkId&pid=examplePublisherId&placementId=examplePlacementId&source=pbs", + "body": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId", + "placementId": "examplePlacementId", + "nid": "exampleNetworkId" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + } + }, + "mockResponse": { + "status": 200, + "body": "invalid-body" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Bad Server Response", + "comparison": "literal" + } + ] +} diff --git a/adapters/videobyte/videobytetest/supplemental/status-code-bad-request.json b/adapters/videobyte/videobytetest/supplemental/status-code-bad-request.json new file mode 100644 index 00000000000..09ad1bbb2fc --- /dev/null +++ b/adapters/videobyte/videobytetest/supplemental/status-code-bad-request.json @@ -0,0 +1,115 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId", + "placementId": "examplePlacementId", + "nid": "exampleNetworkId" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Referer": [ + "http://referer.com/page-2" + ], + "Origin": [ + "example.com" + ], + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ] + }, + "uri": "https://mock.videobyte.com?nid=exampleNetworkId&pid=examplePublisherId&placementId=examplePlacementId&source=pbs", + "body": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId", + "placementId": "examplePlacementId", + "nid": "exampleNetworkId" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + } + }, + "mockResponse": { + "status": 400 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Bad user input: HTTP status 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/videobyte/videobytetest/supplemental/status-code-no-content.json b/adapters/videobyte/videobytetest/supplemental/status-code-no-content.json new file mode 100644 index 00000000000..eaa61c74877 --- /dev/null +++ b/adapters/videobyte/videobytetest/supplemental/status-code-no-content.json @@ -0,0 +1,111 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId", + "placementId": "examplePlacementId", + "nid": "exampleNetworkId" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Referer": [ + "http://referer.com/page-2" + ], + "Origin": [ + "example.com" + ], + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ] + }, + "uri": "https://mock.videobyte.com?nid=exampleNetworkId&pid=examplePublisherId&placementId=examplePlacementId&source=pbs", + "body": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId", + "placementId": "examplePlacementId", + "nid": "exampleNetworkId" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + } + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [] +} diff --git a/adapters/videobyte/videobytetest/supplemental/status-code-other-error.json b/adapters/videobyte/videobytetest/supplemental/status-code-other-error.json new file mode 100644 index 00000000000..d8a1c1f4034 --- /dev/null +++ b/adapters/videobyte/videobytetest/supplemental/status-code-other-error.json @@ -0,0 +1,115 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId", + "placementId": "examplePlacementId", + "nid": "exampleNetworkId" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Referer": [ + "http://referer.com/page-2" + ], + "Origin": [ + "example.com" + ], + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ] + }, + "uri": "https://mock.videobyte.com?nid=exampleNetworkId&pid=examplePublisherId&placementId=examplePlacementId&source=pbs", + "body": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId", + "placementId": "examplePlacementId", + "nid": "exampleNetworkId" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + } + }, + "mockResponse": { + "status": 505 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 505. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/visx/visx.go b/adapters/visx/visx.go index f0b25b4a2a3..53277ff8fe4 100644 --- a/adapters/visx/visx.go +++ b/adapters/visx/visx.go @@ -16,16 +16,29 @@ type VisxAdapter struct { endpoint string } +type visxBidExtPrebidMeta struct { + MediaType openrtb_ext.BidType `json:"mediaType"` +} + +type visxBidExtPrebid struct { + Meta visxBidExtPrebidMeta `json:"meta"` +} + +type visxBidExt struct { + Prebid visxBidExtPrebid `json:"prebid"` +} + type visxBid struct { - ImpID string `json:"impid"` - Price float64 `json:"price"` - UID int `json:"auid"` - CrID string `json:"crid,omitempty"` - AdM string `json:"adm,omitempty"` - ADomain []string `json:"adomain,omitempty"` - DealID string `json:"dealid,omitempty"` - W uint64 `json:"w,omitempty"` - H uint64 `json:"h,omitempty"` + ImpID string `json:"impid"` + Price float64 `json:"price"` + UID int `json:"auid"` + CrID string `json:"crid,omitempty"` + AdM string `json:"adm,omitempty"` + ADomain []string `json:"adomain,omitempty"` + DealID string `json:"dealid,omitempty"` + W uint64 `json:"w,omitempty"` + H uint64 `json:"h,omitempty"` + Ext json.RawMessage `json:"ext,omitempty"` } type visxSeatBid struct { @@ -101,8 +114,9 @@ func (a *VisxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalReq bid.H = int64(sb.Bid[i].H) bid.ADomain = sb.Bid[i].ADomain bid.DealID = sb.Bid[i].DealID + bid.Ext = sb.Bid[i].Ext - bidType, err := getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp) + bidType, err := getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp, sb.Bid[i]) if err != nil { return nil, []error{err} } @@ -117,9 +131,19 @@ func (a *VisxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalReq } -func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { +func getMediaTypeForImp(impID string, imps []openrtb2.Imp, bid visxBid) (openrtb_ext.BidType, error) { for _, imp := range imps { if imp.ID == impID { + var ext visxBidExt + if err := json.Unmarshal(bid.Ext, &ext); err == nil { + if ext.Prebid.Meta.MediaType == openrtb_ext.BidTypeBanner { + return openrtb_ext.BidTypeBanner, nil + } + if ext.Prebid.Meta.MediaType == openrtb_ext.BidTypeVideo { + return openrtb_ext.BidTypeVideo, nil + } + } + if imp.Banner != nil { return openrtb_ext.BidTypeBanner, nil } diff --git a/adapters/visx/visxtest/exemplary/multitype-banner-response.json b/adapters/visx/visxtest/exemplary/multitype-banner-response.json new file mode 100644 index 00000000000..0ef214dd372 --- /dev/null +++ b/adapters/visx/visxtest/exemplary/multitype-banner-response.json @@ -0,0 +1,199 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "domain": "good.site", + "page": "https://good.site/url", + "publisher": { + "id": "test-publisher-id" + }, + "ext": { + "amp": 0 + } + }, + "imp": [{ + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 300, + "h": 250 + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "secure": 1, + "ext": { + "bidder": { + "size": [ + 300, + 250 + ], + "uid": 11 + } + } + }], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "h": 700, + "w": 375, + "ext": { + "prebid": { + "interstitial": { + "minwidthperc": 50, + "minheightperc": 40 + } + } + } + }, + "at": 1, + "tmax": 2000, + "source": { + "tid": "283746293874293" + }, + "ext": { + "prebid": { + "aliases": { + "districtm": "appnexus", + "emxdigital": "appnexus" + } + } + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "id": "test-request-id", + "site": { + "domain": "good.site", + "page": "https://good.site/url", + "publisher": { + "id": "test-publisher-id" + }, + "ext": { + "amp": 0 + } + }, + "cur": ["USD"], + "imp": [{ + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 300, + "h": 250 + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "secure": 1, + "ext": { + "bidder": { + "size": [ + 300, + 250 + ], + "uid": 11 + } + } + }], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "h": 700, + "w": 375, + "ext": { + "prebid": { + "interstitial": { + "minwidthperc": 50, + "minheightperc": 40 + } + } + } + }, + "at": 1, + "tmax": 2000, + "source": { + "tid": "283746293874293" + }, + "ext": { + "prebid": { + "aliases": { + "districtm": "appnexus", + "emxdigital": "appnexus" + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": ["USD"], + "seatbid": [ + { + "bid": [ + { + "crid": "2_260", + "price": 0.500000, + "adm": "some-test-ad", + "impid": "test-imp-id", + "auid": 11, + "h": 250, + "adomain": [ + "goodadvertiser.com" + ], + "dealid": "test_deal_id", + "w": 300, + "ext": { + "prebid": { + "meta": { + "mediaType": "banner" + } + } + } + } + ], + "seat": "51" + } + ] + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "price": 0.5, + "adm": "some-test-ad", + "impid": "test-imp-id", + "id": "test-request-id", + "h": 250, + "adomain": [ + "goodadvertiser.com" + ], + "dealid": "test_deal_id", + "crid": "2_260", + "w": 300, + "ext": { + "prebid": { + "meta": { + "mediaType": "banner" + } + } + } + }, + "type": "banner" + }] + }] +} diff --git a/adapters/visx/visxtest/exemplary/multitype-video-response.json b/adapters/visx/visxtest/exemplary/multitype-video-response.json new file mode 100644 index 00000000000..17877315fcf --- /dev/null +++ b/adapters/visx/visxtest/exemplary/multitype-video-response.json @@ -0,0 +1,199 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "domain": "good.site", + "page": "https://good.site/url", + "publisher": { + "id": "test-publisher-id" + }, + "ext": { + "amp": 0 + } + }, + "imp": [{ + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 300, + "h": 250 + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "secure": 1, + "ext": { + "bidder": { + "size": [ + 300, + 250 + ], + "uid": 11 + } + } + }], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "h": 700, + "w": 375, + "ext": { + "prebid": { + "interstitial": { + "minwidthperc": 50, + "minheightperc": 40 + } + } + } + }, + "at": 1, + "tmax": 2000, + "source": { + "tid": "283746293874293" + }, + "ext": { + "prebid": { + "aliases": { + "districtm": "appnexus", + "emxdigital": "appnexus" + } + } + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "id": "test-request-id", + "site": { + "domain": "good.site", + "page": "https://good.site/url", + "publisher": { + "id": "test-publisher-id" + }, + "ext": { + "amp": 0 + } + }, + "cur": ["USD"], + "imp": [{ + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 300, + "h": 250 + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "secure": 1, + "ext": { + "bidder": { + "size": [ + 300, + 250 + ], + "uid": 11 + } + } + }], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "h": 700, + "w": 375, + "ext": { + "prebid": { + "interstitial": { + "minwidthperc": 50, + "minheightperc": 40 + } + } + } + }, + "at": 1, + "tmax": 2000, + "source": { + "tid": "283746293874293" + }, + "ext": { + "prebid": { + "aliases": { + "districtm": "appnexus", + "emxdigital": "appnexus" + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": ["USD"], + "seatbid": [ + { + "bid": [ + { + "crid": "2_260", + "price": 0.500000, + "adm": "some-test-ad-vast", + "impid": "test-imp-id", + "auid": 11, + "h": 250, + "adomain": [ + "goodadvertiser.com" + ], + "dealid": "test_deal_id", + "w": 300, + "ext": { + "prebid": { + "meta": { + "mediaType": "video" + } + } + } + } + ], + "seat": "51" + } + ] + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "price": 0.5, + "adm": "some-test-ad-vast", + "impid": "test-imp-id", + "id": "test-request-id", + "h": 250, + "adomain": [ + "goodadvertiser.com" + ], + "dealid": "test_deal_id", + "crid": "2_260", + "w": 300, + "ext": { + "prebid": { + "meta": { + "mediaType": "video" + } + } + } + }, + "type": "video" + }] + }] +} diff --git a/analytics/filesystem/file_module.go b/analytics/filesystem/file_module.go index a0721d98a2a..43853382354 100644 --- a/analytics/filesystem/file_module.go +++ b/analytics/filesystem/file_module.go @@ -102,8 +102,6 @@ func NewFileLogger(filename string) (analytics.PBSAnalyticsModule, error) { } } -type fileAuctionObject analytics.AuctionObject - func jsonifyAuctionObject(ao *analytics.AuctionObject) string { type alias analytics.AuctionObject b, err := json.Marshal(&struct { diff --git a/analytics/pubstack/pubstack_module_test.go b/analytics/pubstack/pubstack_module_test.go index cb8f088d0bf..0e0b3634508 100644 --- a/analytics/pubstack/pubstack_module_test.go +++ b/analytics/pubstack/pubstack_module_test.go @@ -2,59 +2,15 @@ package pubstack import ( "encoding/json" - "io/ioutil" "net/http" "net/http/httptest" - "os" "testing" "time" - "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/analytics" "github.com/stretchr/testify/assert" ) -func loadJSONFromFile() (*analytics.AuctionObject, error) { - req, err := os.Open("mocks/mock_openrtb_request.json") - if err != nil { - return nil, err - } - defer req.Close() - - reqCtn := openrtb2.BidRequest{} - reqPayload, err := ioutil.ReadAll(req) - if err != nil { - return nil, err - } - - err = json.Unmarshal(reqPayload, &reqCtn) - if err != nil { - return nil, err - } - - res, err := os.Open("mocks/mock_openrtb_response.json") - if err != nil { - return nil, err - } - defer res.Close() - - resCtn := openrtb2.BidResponse{} - resPayload, err := ioutil.ReadAll(res) - if err != nil { - return nil, err - } - - err = json.Unmarshal(resPayload, &resCtn) - if err != nil { - return nil, err - } - - return &analytics.AuctionObject{ - Request: &reqCtn, - Response: &resCtn, - }, nil -} - func TestPubstackModuleErrors(t *testing.T) { tests := []struct { description string diff --git a/cache/dummycache/dummycache.go b/cache/dummycache/dummycache.go deleted file mode 100644 index 02fe726d043..00000000000 --- a/cache/dummycache/dummycache.go +++ /dev/null @@ -1,65 +0,0 @@ -package dummycache - -import ( - "fmt" - - "github.com/prebid/prebid-server/cache" -) - -// Cache dummy config that will echo back results -type Cache struct { - accounts *accountService - config *configService -} - -// New creates new dummy.Cache -func New() (*Cache, error) { - return &Cache{ - accounts: &accountService{}, - config: &configService{}, - }, nil -} - -func (c *Cache) Accounts() cache.AccountsService { - return c.accounts -} -func (c *Cache) Config() cache.ConfigService { - return c.config -} - -// AccountService handles the account information -type accountService struct { -} - -// Get echos back the account -func (s *accountService) Get(id string) (*cache.Account, error) { - return &cache.Account{ - ID: id, - }, nil -} - -// ConfigService not supported, always returns an error -type configService struct { - c string -} - -// Get not supported, always returns an error -func (s *configService) Get(id string) (string, error) { - if s.c == "" { - return s.c, fmt.Errorf("No configuration provided") - } - return s.c, nil -} - -// Set will set a string in memory as the configuration -// this is so we can use it in tests such as pbs/pbsrequest_test.go -// it will ignore the id so this will pass tests -func (s *configService) Set(id, val string) error { - s.c = val - return nil -} - -// Close will always return nil -func (c *Cache) Close() error { - return nil -} diff --git a/cache/dummycache/dummycache_test.go b/cache/dummycache/dummycache_test.go deleted file mode 100644 index 74004feaa38..00000000000 --- a/cache/dummycache/dummycache_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package dummycache - -import "testing" - -func TestDummyCache(t *testing.T) { - - c, _ := New() - - account, err := c.Accounts().Get("account1") - if err != nil { - t.Fatal(err) - } - - if account.ID != "account1" { - t.Error("Wrong account returned") - } - - if err := c.Config().Set("config", "abc123"); err != nil { - t.Errorf("Dummy config should return nil") - } - - cfg, err := c.Config().Get("config") - if err != nil { - t.Error("Dummy configs should be supported") - } - - if cfg != "abc123" { - t.Error("Dummy config did not return back expected string") - } - -} diff --git a/cache/filecache/filecache.go b/cache/filecache/filecache.go deleted file mode 100644 index 7bc4bea43f0..00000000000 --- a/cache/filecache/filecache.go +++ /dev/null @@ -1,123 +0,0 @@ -package filecache - -import ( - "fmt" - "io/ioutil" - - "github.com/golang/glog" - "github.com/prebid/prebid-server/cache" - "gopkg.in/yaml.v2" -) - -type shared struct { - Configs map[string]string - Accounts map[string]bool -} - -// Cache is a file backed cache -type Cache struct { - shared *shared - accounts *accountService - config *configService -} - -type fileConfig struct { - ID string `yaml:"id"` - Config string `yaml:"config"` -} - -type fileCacheFile struct { - Configs []fileConfig `yaml:"configs"` - Accounts []string `yaml:"accounts"` -} - -// New will load the file into memory -func New(filename string) (*Cache, error) { - if glog.V(2) { - glog.Infof("Reading inventory urls from %s", filename) - } - - b, err := ioutil.ReadFile(filename) - if err != nil { - return nil, err - } - - if glog.V(2) { - glog.Infof("Parsing filecache YAML") - } - - var u fileCacheFile - if err = yaml.Unmarshal(b, &u); err != nil { - return nil, err - } - - if glog.V(2) { - glog.Infof("Building URL map") - } - - s := &shared{} - - s.Configs = make(map[string]string, len(u.Configs)) - for _, config := range u.Configs { - s.Configs[config.ID] = config.Config - } - glog.Infof("Loaded %d configs", len(u.Configs)) - - s.Accounts = make(map[string]bool, len(u.Accounts)) - for _, Account := range u.Accounts { - s.Accounts[Account] = true - } - glog.Infof("Loaded %d accounts", len(u.Accounts)) - - return &Cache{ - shared: s, - accounts: &accountService{s}, - config: &configService{s}, - }, nil -} - -// This empty function exists so the Cache struct implements the Cache interface defined in cache/legacy.go -func (c *Cache) Close() error { - return nil -} - -func (c *Cache) Accounts() cache.AccountsService { - return c.accounts -} -func (c *Cache) Config() cache.ConfigService { - return c.config -} - -// AccountService handles the account information -type accountService struct { - shared *shared -} - -// Get will return Account from memory if it exists -func (s *accountService) Get(id string) (*cache.Account, error) { - if _, ok := s.shared.Accounts[id]; !ok { - return nil, fmt.Errorf("Not found") - } - return &cache.Account{ - ID: id, - }, nil -} - -// ConfigService not supported, always returns an error -type configService struct { - shared *shared -} - -// Get will return config from memory if it exists -func (s *configService) Get(id string) (string, error) { - cfg, ok := s.shared.Configs[id] - if !ok { - return "", fmt.Errorf("Not found") - } - return cfg, nil -} - -// Set not supported, always returns an error -func (s *configService) Set(id, value string) error { - return fmt.Errorf("Not supported") -} diff --git a/cache/filecache/filecache_test.go b/cache/filecache/filecache_test.go deleted file mode 100644 index 80a72803bbe..00000000000 --- a/cache/filecache/filecache_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package filecache - -import ( - "io/ioutil" - "os" - "testing" - - yaml "gopkg.in/yaml.v2" -) - -func TestFileCache(t *testing.T) { - fcf := fileCacheFile{ - Accounts: []string{"account1", "account2", "account3"}, - Configs: []fileConfig{ - { - ID: "one", - Config: "config1", - }, { - ID: "two", - Config: "config2", - }, { - ID: "three", - Config: "config3", - }, - }, - } - - bytes, err := yaml.Marshal(&fcf) - if err != nil { - t.Fatal(err) - } - - tmpfile, err := ioutil.TempFile("", "filecache") - if err != nil { - t.Fatal(err) - } - defer os.Remove(tmpfile.Name()) - - if _, err := tmpfile.Write(bytes); err != nil { - t.Fatal(err) - } - - if err := tmpfile.Close(); err != nil { - t.Fatal(err) - } - - dataCache, err := New(tmpfile.Name()) - if err != nil { - t.Fatal(err) - } - - a, err := dataCache.Accounts().Get("account1") - if err != nil { - t.Fatal(err) - } - - if a.ID != "account1" { - t.Error("fetched invalid account") - } - - a, err = dataCache.Accounts().Get("abc123") - if err == nil { - t.Error("account should not exist in cache") - } - - c, err := dataCache.Config().Get("one") - if err != nil { - t.Fatal(err) - } - - if c != "config1" { - t.Error("fetched invalid config") - } - - c, err = dataCache.Config().Get("abc123") - if err == nil { - t.Error("config should not exist in cache") - } -} diff --git a/cache/legacy.go b/cache/legacy.go deleted file mode 100644 index 19c5ae5a4fe..00000000000 --- a/cache/legacy.go +++ /dev/null @@ -1,33 +0,0 @@ -package cache - -type Domain struct { - Domain string `json:"domain"` -} - -type App struct { - Bundle string `json:"bundle"` -} - -type Account struct { - ID string `json:"id"` - PriceGranularity string `json:"price_granularity"` -} - -type Configuration struct { - Type string `json:"type"` // required -} - -type Cache interface { - Close() error - Accounts() AccountsService - Config() ConfigService -} - -type AccountsService interface { - Get(string) (*Account, error) -} - -type ConfigService interface { - Get(string) (string, error) - Set(string, string) error -} diff --git a/cache/postgrescache/postgrescache.go b/cache/postgrescache/postgrescache.go deleted file mode 100644 index 2333e08269e..00000000000 --- a/cache/postgrescache/postgrescache.go +++ /dev/null @@ -1,139 +0,0 @@ -package postgrescache - -import ( - "bytes" - "context" - "database/sql" - "encoding/gob" - "time" - - "github.com/prebid/prebid-server/stored_requests" - - "github.com/coocood/freecache" - "github.com/lib/pq" - "github.com/prebid/prebid-server/cache" -) - -type CacheConfig struct { - TTL int - Size int -} - -// shared configuration that get used by all of the services -type shared struct { - db *sql.DB - lru *freecache.Cache - ttlSeconds int -} - -// Cache postgres -type Cache struct { - shared *shared - accounts *accountService - config *configService -} - -// New creates new postgres.Cache -func New(db *sql.DB, cfg CacheConfig) *Cache { - shared := &shared{ - db: db, - lru: freecache.NewCache(cfg.Size), - ttlSeconds: cfg.TTL, - } - return &Cache{ - shared: shared, - accounts: &accountService{shared: shared}, - config: &configService{shared: shared}, - } -} - -func (c *Cache) Accounts() cache.AccountsService { - return c.accounts -} -func (c *Cache) Config() cache.ConfigService { - return c.config -} - -func (c *Cache) Close() error { - return c.shared.db.Close() -} - -// AccountService handles the account information -type accountService struct { - shared *shared -} - -// Get echos back the account -func (s *accountService) Get(key string) (*cache.Account, error) { - var account cache.Account - - b, err := s.shared.lru.Get([]byte(key)) - if err == nil { - return decodeAccount(b), nil - } - - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(50)*time.Millisecond) - defer cancel() - var id string - var priceGranularity sql.NullString - if err := s.shared.db.QueryRowContext(ctx, "SELECT uuid, price_granularity FROM accounts_account where uuid = $1 LIMIT 1", key).Scan(&id, &priceGranularity); err != nil { - /* TODO -- We should store failed attempts in the LRU as well to stop from hitting to DB */ - return nil, err - } - - account.ID = id - if priceGranularity.Valid { - account.PriceGranularity = priceGranularity.String - } - - buf := bytes.Buffer{} - if err := gob.NewEncoder(&buf).Encode(&account); err != nil { - panic(err) - } - - s.shared.lru.Set([]byte(key), buf.Bytes(), s.shared.ttlSeconds) - return &account, nil -} - -func decodeAccount(b []byte) *cache.Account { - var account cache.Account - buf := bytes.NewReader(b) - if err := gob.NewDecoder(buf).Decode(&account); err != nil { - panic(err) - } - return &account -} - -// ConfigService -type configService struct { - shared *shared -} - -func (s *configService) Set(id, value string) error { - return nil -} - -func (s *configService) Get(key string) (string, error) { - if b, err := s.shared.lru.Get([]byte(key)); err == nil { - return string(b), nil - } - - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(50)*time.Millisecond) - defer cancel() - var config string - if err := s.shared.db.QueryRowContext(ctx, "SELECT config FROM s2sconfig_config where uuid = $1 LIMIT 1", key).Scan(&config); err != nil { - // TODO -- We should store failed attempts in the LRU as well to stop from hitting to DB - - // If the user didn't give us a UUID, the query fails with this error. Wrap it so that we don't - // pollute the app logs with bad user input. - if pqErr, ok := err.(*pq.Error); ok && string(pqErr.Code) == "22P02" { - err = &stored_requests.NotFoundError{ - ID: key, - DataType: "Legacy Config", - } - } - return "", err - } - s.shared.lru.Set([]byte(key), []byte(config), s.shared.ttlSeconds) - return config, nil -} diff --git a/cache/postgrescache/postgrescache_test.go b/cache/postgrescache/postgrescache_test.go deleted file mode 100644 index bab96f1a11e..00000000000 --- a/cache/postgrescache/postgrescache_test.go +++ /dev/null @@ -1,94 +0,0 @@ -package postgrescache - -import ( - "database/sql" - "testing" - - "github.com/coocood/freecache" - "github.com/erikstmartin/go-testdb" -) - -type StubCache struct { - shared *shared - accounts *accountService - config *configService -} - -// New creates new postgres.Cache -func StubNew(cfg CacheConfig) *Cache { - shared := stubnewShared(cfg) - return &Cache{ - shared: shared, - accounts: &accountService{shared: shared}, - config: &configService{shared: shared}, - } -} - -func stubnewShared(conf CacheConfig) *shared { - db, _ := sql.Open("testdb", "") - - s := &shared{ - db: db, - lru: freecache.NewCache(conf.Size), - ttlSeconds: 0, - } - return s -} - -func TestPostgresDbPriceGranularity(t *testing.T) { - defer testdb.Reset() - - sql := "SELECT uuid, price_granularity FROM accounts_account where uuid = $1 LIMIT 1" - columns := []string{"uuid", "price_granularity"} - result := ` - bdc928ef-f725-4688-8171-c104cc715bdf,med - ` - testdb.StubQuery(sql, testdb.RowsFromCSVString(columns, result)) - - conf := CacheConfig{ - TTL: 3434, - Size: 100, - } - dataCache := StubNew(conf) - - account, err := dataCache.Accounts().Get("bdc928ef-f725-4688-8171-c104cc715bdf") - if err != nil { - t.Fatalf("test postgres db errored: %v", err) - } - - if account.ID != "bdc928ef-f725-4688-8171-c104cc715bdf" { - t.Error("Expected bdc928ef-f725-4688-8171-c104cc715bdf") - } - if account.PriceGranularity != "med" { - t.Error("Expected med") - } -} - -func TestPostgresDbNullPriceGranularity(t *testing.T) { - defer testdb.Reset() - - sql := "SELECT uuid, price_granularity FROM accounts_account where uuid = $1 LIMIT 1" - columns := []string{"uuid", "price_granularity"} - result := ` - bdc928ef-f725-4688-8171-c104cc715bdf - ` - testdb.StubQuery(sql, testdb.RowsFromCSVString(columns, result)) - - conf := CacheConfig{ - TTL: 3434, - Size: 100, - } - dataCache := StubNew(conf) - - account, err := dataCache.Accounts().Get("bdc928ef-f725-4688-8171-c104cc715bdf") - if err != nil { - t.Fatalf("test postgres db errored: %v", err) - } - - if account.ID != "bdc928ef-f725-4688-8171-c104cc715bdf" { - t.Error("Expected bdc928ef-f725-4688-8171-c104cc715bdf") - } - if account.PriceGranularity != "" { - t.Error("Expected null string") - } -} diff --git a/config/bidderinfo.go b/config/bidderinfo.go index 750604c31b3..12e51a3c381 100644 --- a/config/bidderinfo.go +++ b/config/bidderinfo.go @@ -68,6 +68,9 @@ type Syncer struct { // endpoint in the Prebid.js project. Redirect *SyncerEndpoint `yaml:"redirect" mapstructure:"redirect"` + // ExternalURL is available as a macro to the RedirectURL template. + ExternalURL string `yaml:"externalUrl" mapstructure:"external_url"` + // SupportCORS identifies if CORS is supported for the user syncing endpoints. SupportCORS *bool `yaml:"supportCors" mapstructure:"support_cors"` } @@ -99,14 +102,14 @@ func (s *Syncer) Override(original *Syncer) *Syncer { if original == nil { copy.IFrame = s.IFrame.Override(nil) + copy.Redirect = s.Redirect.Override(nil) } else { copy.IFrame = s.IFrame.Override(original.IFrame) + copy.Redirect = s.Redirect.Override(original.Redirect) } - if original == nil { - copy.Redirect = s.Redirect.Override(nil) - } else { - copy.Redirect = s.Redirect.Override(original.Redirect) + if s.ExternalURL != "" { + copy.ExternalURL = s.ExternalURL } if s.SupportCORS != nil { @@ -162,8 +165,8 @@ type SyncerEndpoint struct { // `{{.ExternalURL}}/setuid?bidder={{.SyncerKey}}&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&f={{.SyncType}}&uid={{.UserMacro}}` RedirectURL string `yaml:"redirectUrl" mapstructure:"redirect_url"` - // ExternalURL is available as a macro to the RedirectURL template. If not specified, the host configuration - // value is used. + // ExternalURL is available as a macro to the RedirectURL template. If not specified, either the syncer configuration + // value or the host configuration value is used. ExternalURL string `yaml:"externalUrl" mapstructure:"external_url"` // UserMacro is available as a macro to the RedirectURL template. This value is specific to the bidder server diff --git a/config/bidderinfo_test.go b/config/bidderinfo_test.go index 19abdc43f59..9cf5fe09095 100644 --- a/config/bidderinfo_test.go +++ b/config/bidderinfo_test.go @@ -226,6 +226,12 @@ func TestSyncerOverride(t *testing.T) { givenOverride: &Syncer{Redirect: &SyncerEndpoint{URL: "override"}}, expected: &Syncer{Redirect: &SyncerEndpoint{URL: "override"}}, }, + { + description: "Override ExternalURL", + givenOriginal: &Syncer{ExternalURL: "original"}, + givenOverride: &Syncer{ExternalURL: "override"}, + expected: &Syncer{ExternalURL: "override"}, + }, { description: "Override SupportCORS", givenOriginal: &Syncer{SupportCORS: &trueValue}, diff --git a/config/config.go b/config/config.go index 597ac1b41a7..41809aea97f 100644 --- a/config/config.go +++ b/config/config.go @@ -24,6 +24,10 @@ type Configuration struct { CacheClient HTTPClient `mapstructure:"http_client_cache"` AdminPort int `mapstructure:"admin_port"` EnableGzip bool `mapstructure:"enable_gzip"` + // GarbageCollectorThreshold allocates virtual memory (in bytes) which is not used by PBS but + // serves as a hack to trigger the garbage collector only when the heap reaches at least this size. + // More info: https://github.com/golang/go/issues/48409 + GarbageCollectorThreshold int `mapstructure:"garbage_collector_threshold"` // StatusResponse is the string which will be returned by the /status endpoint when things are OK. // If empty, it will return a 204 with no content. StatusResponse string `mapstructure:"status_response"` @@ -33,7 +37,6 @@ type Configuration struct { RecaptchaSecret string `mapstructure:"recaptcha_secret"` HostCookie HostCookie `mapstructure:"host_cookie"` Metrics Metrics `mapstructure:"metrics"` - DataCache DataCache `mapstructure:"datacache"` StoredRequests StoredRequests `mapstructure:"stored_requests"` StoredRequestsAMP StoredRequests `mapstructure:"stored_amp_req"` CategoryMapping StoredRequests `mapstructure:"category_mapping"` @@ -85,10 +88,6 @@ type Configuration struct { GenerateBidID bool `mapstructure:"generate_bid_id"` // GenerateRequestID overrides the bidrequest.id in an AMP Request or an App Stored Request with a generated UUID if set to true. The default is false. GenerateRequestID bool `mapstructure:"generate_request_id"` - - // EnableLegacyAuction specifies if the original /auction endpoint with a custom PBS data model is allowed - // by the host. - EnableLegacyAuction bool `mapstructure:"enable_legacy_auction"` } const MIN_COOKIE_SIZE_BYTES = 500 @@ -229,6 +228,31 @@ func (cfg *GDPR) validate(v *viper.Viper, errs []error) []error { if cfg.AMPException == true { errs = append(errs, fmt.Errorf("gdpr.amp_exception has been discontinued and must be removed from your config. If you need to disable GDPR for AMP, you may do so per-account (gdpr.integration_enabled.amp) or at the host level for the default account (account_defaults.gdpr.integration_enabled.amp)")) } + return cfg.validatePurposes(errs) +} + +func (cfg *GDPR) validatePurposes(errs []error) []error { + purposeConfigs := []TCF2Purpose{ + cfg.TCF2.Purpose1, + cfg.TCF2.Purpose2, + cfg.TCF2.Purpose3, + cfg.TCF2.Purpose4, + cfg.TCF2.Purpose5, + cfg.TCF2.Purpose6, + cfg.TCF2.Purpose7, + cfg.TCF2.Purpose8, + cfg.TCF2.Purpose9, + cfg.TCF2.Purpose10, + } + + for i := 0; i < len(purposeConfigs); i++ { + enforcePurposeValue := purposeConfigs[i].EnforcePurpose + enforcePurposeField := fmt.Sprintf("gdpr.tcf2.purpose%d.enforce_purpose", (i + 1)) + + if enforcePurposeValue != TCF2NoEnforcement && enforcePurposeValue != TCF2FullEnforcement { + errs = append(errs, fmt.Errorf("%s must be \"no\" or \"full\". Got %s", enforcePurposeField, enforcePurposeValue)) + } + } return errs } @@ -245,6 +269,11 @@ func (t *GDPRTimeouts) ActiveTimeout() time.Duration { return time.Duration(t.ActiveVendorlistFetch) * time.Millisecond } +const ( + TCF2FullEnforcement = "full" + TCF2NoEnforcement = "no" +) + // TCF2 defines the TCF2 specific configurations for GDPR type TCF2 struct { Enabled bool `mapstructure:"enabled"` @@ -264,8 +293,9 @@ type TCF2 struct { // Making a purpose struct so purpose specific details can be added later. type TCF2Purpose struct { - Enabled bool `mapstructure:"enabled"` - EnforceVendors bool `mapstructure:"enforce_vendors"` + Enabled bool `mapstructure:"enabled"` // Deprecated: Use enforce_purpose instead + EnforcePurpose string `mapstructure:"enforce_purpose"` + EnforceVendors bool `mapstructure:"enforce_vendors"` // Array of vendor exceptions that is used to create the hash table VendorExceptionMap so vendor names can be instantly accessed VendorExceptions []openrtb_ext.BidderName `mapstructure:"vendor_exceptions"` VendorExceptionMap map[openrtb_ext.BidderName]struct{} @@ -401,13 +431,6 @@ func (m *PrometheusMetrics) Timeout() time.Duration { return time.Duration(m.TimeoutMillisRaw) * time.Millisecond } -type DataCache struct { - Type string `mapstructure:"type"` - Filename string `mapstructure:"filename"` - CacheSize int `mapstructure:"cache_size"` - TTLSeconds int `mapstructure:"ttl_seconds"` -} - // ExternalCache configures the externally accessible cache url. type ExternalCache struct { Scheme string `mapstructure:"scheme"` @@ -615,6 +638,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("port", 8000) v.SetDefault("admin_port", 6060) v.SetDefault("enable_gzip", false) + v.SetDefault("garbage_collector_threshold", 0) v.SetDefault("status_response", "") v.SetDefault("auction_timeouts_ms.default", 0) v.SetDefault("auction_timeouts_ms.max", 0) @@ -660,10 +684,6 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("metrics.prometheus.namespace", "") v.SetDefault("metrics.prometheus.subsystem", "") v.SetDefault("metrics.prometheus.timeout_ms", 10000) - v.SetDefault("datacache.type", "dummy") - v.SetDefault("datacache.filename", "") - v.SetDefault("datacache.cache_size", 0) - v.SetDefault("datacache.ttl_seconds", 0) v.SetDefault("category_mapping.filesystem.enabled", true) v.SetDefault("category_mapping.filesystem.directorypath", "./static/category-mapping") v.SetDefault("category_mapping.http.endpoint", "") @@ -748,7 +768,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.acuityads.endpoint", "http://{{.Host}}.admanmedia.com/bid?token={{.AccountID}}") v.SetDefault("adapters.adagio.endpoint", "https://mp.4dex.io/ortb2") v.SetDefault("adapters.adf.endpoint", "https://adx.adform.net/adx/openrtb") - v.SetDefault("adapters.adform.endpoint", "https://adx.adform.net/adx") + v.SetDefault("adapters.adform.endpoint", "https://adx.adform.net/adx/openrtb") v.SetDefault("adapters.adgeneration.endpoint", "https://d.socdm.com/adsv/v1") v.SetDefault("adapters.adhese.endpoint", "https://ads-{{.AccountID}}.adhese.com/json") v.SetDefault("adapters.adkernel.endpoint", "http://{{.Host}}/hb?zone={{.ZoneID}}") @@ -756,6 +776,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.adman.endpoint", "http://pub.admanmedia.com/?c=o&m=ortb") v.SetDefault("adapters.admixer.endpoint", "http://inv-nets.admixer.net/pbs.aspx") v.SetDefault("adapters.adocean.endpoint", "https://{{.Host}}") + v.SetDefault("adapters.adnuntius.endpoint", "https://ads.adnuntius.delivery/i") v.SetDefault("adapters.adoppler.endpoint", "http://{{.AccountID}}.trustedmarketplace.io/ads/processHeaderBid/{{.AdUnit}}") v.SetDefault("adapters.adot.endpoint", "https://dsp.adotmob.com/headerbidding/bidrequest") v.SetDefault("adapters.adpone.endpoint", "http://rtb.adpone.com/bid-request?src=prebid_server") @@ -785,6 +806,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.bidscube.endpoint", "http://supply.bidscube.com/?c=o&m=rtb") v.SetDefault("adapters.bmtm.endpoint", "https://one.elitebidder.com/api/pbs") v.SetDefault("adapters.brightroll.endpoint", "http://east-bid.ybp.yahoo.com/bid/appnexuspbs") + v.SetDefault("adapters.coinzilla.endpoint", "http://request-global.czilladx.com/serve/prebid-server.php") v.SetDefault("adapters.colossus.endpoint", "http://colossusssp.com/?c=o&m=rtb") v.SetDefault("adapters.connectad.endpoint", "http://bidder.connectad.io/API?src=pbs") v.SetDefault("adapters.consumable.endpoint", "https://e.serverbid.com/api/v2") @@ -804,6 +826,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.gamma.endpoint", "https://hb.gammaplatform.com/adx/request/") v.SetDefault("adapters.gamoshi.endpoint", "https://rtb.gamoshi.io") v.SetDefault("adapters.grid.endpoint", "https://grid.bidswitch.net/sp_bid?sp=prebid") + v.SetDefault("adapters.groupm.endpoint", "https://hbopenbid.pubmatic.com/translator?source=prebid-server") v.SetDefault("adapters.gumgum.endpoint", "https://g2.gumgum.com/providers/prbds2s/bid") v.SetDefault("adapters.huaweiads.endpoint", "https://acd.op.hicloud.com/ppsadx/getResult") v.SetDefault("adapters.huaweiads.disabled", true) @@ -846,6 +869,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.revcontent.disabled", true) v.SetDefault("adapters.revcontent.endpoint", "https://trends.revcontent.com/rtb") v.SetDefault("adapters.rhythmone.endpoint", "http://tag.1rx.io/rmp") + v.SetDefault("adapters.richaudience.endpoint", "http://ortb.richaudience.com/ortb/?bidder=pbs") v.SetDefault("adapters.rtbhouse.endpoint", "http://prebidserver-s2s-ams.creativecdn.com/bidder/prebidserver/bids") v.SetDefault("adapters.rubicon.disabled", true) v.SetDefault("adapters.rubicon.endpoint", "http://exapi-us-east.rubiconproject.com/a/api/exchange.json") @@ -860,6 +884,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.somoaudience.endpoint", "http://publisher-east.mobileadtrading.com/rtb/bid") v.SetDefault("adapters.sonobi.endpoint", "https://apex.go.sonobi.com/prebid?partnerid=71d9d3d8af") v.SetDefault("adapters.sovrn.endpoint", "http://ap.lijit.com/rtb/bid?src=prebid_server") + v.SetDefault("adapters.streamkey.endpoint", "http://ghb.hb.streamkey.net/pbs/ortb") v.SetDefault("adapters.synacormedia.endpoint", "http://{{.Host}}.technoratimedia.com/openrtb/bids/{{.Host}}") v.SetDefault("adapters.tappx.endpoint", "http://{{.Host}}") v.SetDefault("adapters.telaria.endpoint", "https://ads.tremorhub.com/ad/rtb/prebid") @@ -872,6 +897,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.unruly.endpoint", "https://targeting.unrulymedia.com/unruly_prebid_server") v.SetDefault("adapters.valueimpression.endpoint", "https://rtb.valueimpression.com/endpoint") v.SetDefault("adapters.verizonmedia.disabled", true) + v.SetDefault("adapters.videobyte.endpoint", "https://x.videobyte.com/ortbhb") v.SetDefault("adapters.viewdeos.endpoint", "http://ghb.sync.viewdeos.com/pbs/ortb") v.SetDefault("adapters.visx.endpoint", "https://t.visx.net/s2s_bid?wrapperType=s2s_prebid_standard:0.1.0") v.SetDefault("adapters.vrtcal.endpoint", "http://rtb.vrtcal.com/bidder_prebid.vap?ssp=1804") @@ -900,16 +926,6 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("gdpr.timeouts_ms.active_vendorlist_fetch", 0) v.SetDefault("gdpr.non_standard_publishers", []string{""}) v.SetDefault("gdpr.tcf2.enabled", true) - v.SetDefault("gdpr.tcf2.purpose1.enabled", true) - v.SetDefault("gdpr.tcf2.purpose2.enabled", true) - v.SetDefault("gdpr.tcf2.purpose3.enabled", true) - v.SetDefault("gdpr.tcf2.purpose4.enabled", true) - v.SetDefault("gdpr.tcf2.purpose5.enabled", true) - v.SetDefault("gdpr.tcf2.purpose6.enabled", true) - v.SetDefault("gdpr.tcf2.purpose7.enabled", true) - v.SetDefault("gdpr.tcf2.purpose8.enabled", true) - v.SetDefault("gdpr.tcf2.purpose9.enabled", true) - v.SetDefault("gdpr.tcf2.purpose10.enabled", true) v.SetDefault("gdpr.tcf2.purpose1.enforce_vendors", true) v.SetDefault("gdpr.tcf2.purpose2.enforce_vendors", true) v.SetDefault("gdpr.tcf2.purpose3.enforce_vendors", true) @@ -954,7 +970,6 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("auto_gen_source_tid", true) v.SetDefault("generate_bid_id", false) v.SetDefault("generate_request_id", false) - v.SetDefault("enable_legacy_auction", false) v.SetDefault("request_timeout_headers.request_time_in_queue", "") v.SetDefault("request_timeout_headers.request_timeout_in_queue", "") @@ -989,7 +1004,31 @@ func SetupViper(v *viper.Viper, filename string) { // Migrate config settings to maintain compatibility with old configs migrateConfig(v) migrateConfigPurposeOneTreatment(v) + migrateConfigTCF2PurposeEnabledFlags(v) + // These defaults must be set after the migrate functions because those functions look for the presence of these + // config fields and there isn't a way to detect presence of a config field using the viper package if a default + // is set. Viper IsSet and Get functions consider default values. + v.SetDefault("gdpr.tcf2.purpose1.enabled", true) + v.SetDefault("gdpr.tcf2.purpose2.enabled", true) + v.SetDefault("gdpr.tcf2.purpose3.enabled", true) + v.SetDefault("gdpr.tcf2.purpose4.enabled", true) + v.SetDefault("gdpr.tcf2.purpose5.enabled", true) + v.SetDefault("gdpr.tcf2.purpose6.enabled", true) + v.SetDefault("gdpr.tcf2.purpose7.enabled", true) + v.SetDefault("gdpr.tcf2.purpose8.enabled", true) + v.SetDefault("gdpr.tcf2.purpose9.enabled", true) + v.SetDefault("gdpr.tcf2.purpose10.enabled", true) + v.SetDefault("gdpr.tcf2.purpose1.enforce_purpose", TCF2FullEnforcement) + v.SetDefault("gdpr.tcf2.purpose2.enforce_purpose", TCF2FullEnforcement) + v.SetDefault("gdpr.tcf2.purpose3.enforce_purpose", TCF2FullEnforcement) + v.SetDefault("gdpr.tcf2.purpose4.enforce_purpose", TCF2FullEnforcement) + v.SetDefault("gdpr.tcf2.purpose5.enforce_purpose", TCF2FullEnforcement) + v.SetDefault("gdpr.tcf2.purpose6.enforce_purpose", TCF2FullEnforcement) + v.SetDefault("gdpr.tcf2.purpose7.enforce_purpose", TCF2FullEnforcement) + v.SetDefault("gdpr.tcf2.purpose8.enforce_purpose", TCF2FullEnforcement) + v.SetDefault("gdpr.tcf2.purpose9.enforce_purpose", TCF2FullEnforcement) + v.SetDefault("gdpr.tcf2.purpose10.enforce_purpose", TCF2FullEnforcement) v.SetDefault("gdpr.tcf2.purpose_one_treatment.enabled", true) v.SetDefault("gdpr.tcf2.purpose_one_treatment.access_allowed", true) } @@ -1019,6 +1058,35 @@ func migrateConfigPurposeOneTreatment(v *viper.Viper) { } } +func migrateConfigTCF2PurposeEnabledFlags(v *viper.Viper) { + for i := 1; i <= 10; i++ { + oldField := fmt.Sprintf("gdpr.tcf2.purpose%d.enabled", i) + newField := fmt.Sprintf("gdpr.tcf2.purpose%d.enforce_purpose", i) + + if v.IsSet(oldField) { + oldConfig := v.GetBool(oldField) + if v.IsSet(newField) { + glog.Warningf("using %s and ignoring deprecated %s", newField, oldField) + } else { + glog.Warningf("%s is deprecated and should be changed to %s", oldField, newField) + if oldConfig { + v.Set(newField, TCF2FullEnforcement) + } else { + v.Set(newField, TCF2NoEnforcement) + } + } + } + + if v.IsSet(newField) { + if v.GetString(newField) == TCF2FullEnforcement { + v.Set(oldField, "true") + } else { + v.Set(oldField, "false") + } + } + } +} + func setBidderDefaults(v *viper.Viper, bidder string) { adapterCfgPrefix := "adapters." + bidder v.SetDefault(adapterCfgPrefix+".endpoint", "") @@ -1042,6 +1110,7 @@ func setBidderDefaults(v *viper.Viper, bidder string) { v.BindEnv(adapterCfgPrefix + ".usersync.redirect.redirect_url") v.BindEnv(adapterCfgPrefix + ".usersync.redirect.external_url") v.BindEnv(adapterCfgPrefix + ".usersync.redirect.user_macro") + v.BindEnv(adapterCfgPrefix + ".usersync.external_url") v.BindEnv(adapterCfgPrefix + ".usersync.support_cors") } diff --git a/config/config_test.go b/config/config_test.go index 819eb21f819..f86f13bda7c 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -130,7 +130,6 @@ func TestDefaults(t *testing.T) { cmpInts(t, "max_request_size", int(cfg.MaxRequestSize), 1024*256) cmpInts(t, "host_cookie.ttl_days", int(cfg.HostCookie.TTL), 90) cmpInts(t, "host_cookie.max_cookie_size_bytes", cfg.HostCookie.MaxCookieSizeBytes, 0) - cmpStrings(t, "datacache.type", cfg.DataCache.Type, "dummy") cmpStrings(t, "adapters.pubmatic.endpoint", cfg.Adapters[string(openrtb_ext.BidderPubmatic)].Endpoint, "https://hbopenbid.pubmatic.com/translator?source=prebid-server") cmpInts(t, "currency_converter.fetch_interval_seconds", cfg.CurrencyConverter.FetchIntervalSeconds, 1800) cmpStrings(t, "currency_converter.fetch_url", cfg.CurrencyConverter.FetchURL, "https://cdn.jsdelivr.net/gh/prebid/currency-file@1/latest.json") @@ -144,67 +143,76 @@ func TestDefaults(t *testing.T) { cmpStrings(t, "stored_requests.filesystem.directorypath", "./stored_requests/data/by_id", cfg.StoredRequests.Files.Path) cmpBools(t, "auto_gen_source_tid", cfg.AutoGenSourceTID, true) cmpBools(t, "generate_bid_id", cfg.GenerateBidID, false) - cmpBools(t, "enable_legacy_auction", cfg.EnableLegacyAuction, false) //Assert purpose VendorExceptionMap hash tables were built correctly expectedTCF2 := TCF2{ Enabled: true, Purpose1: TCF2Purpose{ Enabled: true, + EnforcePurpose: TCF2FullEnforcement, EnforceVendors: true, VendorExceptions: []openrtb_ext.BidderName{}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose2: TCF2Purpose{ Enabled: true, + EnforcePurpose: TCF2FullEnforcement, EnforceVendors: true, VendorExceptions: []openrtb_ext.BidderName{}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose3: TCF2Purpose{ Enabled: true, + EnforcePurpose: TCF2FullEnforcement, EnforceVendors: true, VendorExceptions: []openrtb_ext.BidderName{}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose4: TCF2Purpose{ Enabled: true, + EnforcePurpose: TCF2FullEnforcement, EnforceVendors: true, VendorExceptions: []openrtb_ext.BidderName{}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose5: TCF2Purpose{ Enabled: true, + EnforcePurpose: TCF2FullEnforcement, EnforceVendors: true, VendorExceptions: []openrtb_ext.BidderName{}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose6: TCF2Purpose{ Enabled: true, + EnforcePurpose: TCF2FullEnforcement, EnforceVendors: true, VendorExceptions: []openrtb_ext.BidderName{}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose7: TCF2Purpose{ Enabled: true, + EnforcePurpose: TCF2FullEnforcement, EnforceVendors: true, VendorExceptions: []openrtb_ext.BidderName{}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose8: TCF2Purpose{ Enabled: true, + EnforcePurpose: TCF2FullEnforcement, EnforceVendors: true, VendorExceptions: []openrtb_ext.BidderName{}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose9: TCF2Purpose{ Enabled: true, + EnforcePurpose: TCF2FullEnforcement, EnforceVendors: true, VendorExceptions: []openrtb_ext.BidderName{}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose10: TCF2Purpose{ Enabled: true, + EnforcePurpose: TCF2FullEnforcement, EnforceVendors: true, VendorExceptions: []openrtb_ext.BidderName{}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, @@ -237,6 +245,7 @@ gdpr: vendor_exceptions: ["foo1a", "foo1b"] purpose2: enabled: false + enforce_purpose: "no" enforce_vendors: false vendor_exceptions: ["foo2"] purpose3: @@ -280,6 +289,7 @@ external_url: http://prebid-server.prebid.org/ host: prebid-server.prebid.org port: 1234 admin_port: 5678 +garbage_collector_threshold: 1 auction_timeouts_ms: max: 123 default: 50 @@ -316,11 +326,6 @@ metrics: account_adapter_details: true adapter_connections_metrics: true adapter_gdpr_request_blocked: true -datacache: - type: postgres - filename: /usr/db/db.db - cache_size: 10000000 - ttl_seconds: 3600 adapters: appnexus: endpoint: http://ib.adnxs.com/some/endpoint @@ -354,7 +359,6 @@ request_validation: ipv4_private_networks: ["1.1.1.0/24"] ipv6_private_networks: ["1111::/16", "2222::/16"] generate_bid_id: true -enable_legacy_auction: true `) var adapterExtraInfoConfig = []byte(` @@ -423,6 +427,7 @@ func TestFullConfig(t *testing.T) { cmpStrings(t, "host", cfg.Host, "prebid-server.prebid.org") cmpInts(t, "port", cfg.Port, 1234) cmpInts(t, "admin_port", cfg.AdminPort, 5678) + cmpInts(t, "garbage_collector_threshold", cfg.GarbageCollectorThreshold, 1) cmpInts(t, "auction_timeouts_ms.default", int(cfg.AuctionTimeouts.Default), 50) cmpInts(t, "auction_timeouts_ms.max", int(cfg.AuctionTimeouts.Max), 123) cmpStrings(t, "cache.scheme", cfg.CacheURL.Scheme, "http") @@ -467,60 +472,70 @@ func TestFullConfig(t *testing.T) { Enabled: true, Purpose1: TCF2Purpose{ Enabled: true, // true by default + EnforcePurpose: TCF2FullEnforcement, EnforceVendors: false, VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo1a"), openrtb_ext.BidderName("foo1b")}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo1a"): {}, openrtb_ext.BidderName("foo1b"): {}}, }, Purpose2: TCF2Purpose{ Enabled: false, + EnforcePurpose: TCF2NoEnforcement, EnforceVendors: false, VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo2")}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo2"): {}}, }, Purpose3: TCF2Purpose{ Enabled: true, // true by default + EnforcePurpose: TCF2FullEnforcement, EnforceVendors: false, VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo3")}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo3"): {}}, }, Purpose4: TCF2Purpose{ Enabled: true, // true by default + EnforcePurpose: TCF2FullEnforcement, EnforceVendors: false, VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo4")}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo4"): {}}, }, Purpose5: TCF2Purpose{ Enabled: true, // true by default + EnforcePurpose: TCF2FullEnforcement, EnforceVendors: false, VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo5")}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo5"): {}}, }, Purpose6: TCF2Purpose{ Enabled: true, // true by default + EnforcePurpose: TCF2FullEnforcement, EnforceVendors: false, VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo6")}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo6"): {}}, }, Purpose7: TCF2Purpose{ Enabled: true, // true by default + EnforcePurpose: TCF2FullEnforcement, EnforceVendors: false, VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo7")}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo7"): {}}, }, Purpose8: TCF2Purpose{ Enabled: true, // true by default + EnforcePurpose: TCF2FullEnforcement, EnforceVendors: false, VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo8")}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo8"): {}}, }, Purpose9: TCF2Purpose{ Enabled: true, // true by default + EnforcePurpose: TCF2FullEnforcement, EnforceVendors: false, VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo9")}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo9"): {}}, }, Purpose10: TCF2Purpose{ Enabled: true, // true by default + EnforcePurpose: TCF2FullEnforcement, EnforceVendors: false, VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo10")}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo10"): {}}, @@ -545,10 +560,6 @@ func TestFullConfig(t *testing.T) { cmpStrings(t, "metrics.influxdb.username", cfg.Metrics.Influxdb.Username, "admin") cmpStrings(t, "metrics.influxdb.password", cfg.Metrics.Influxdb.Password, "admin1324") cmpInts(t, "metrics.influxdb.metric_send_interval", cfg.Metrics.Influxdb.MetricSendInterval, 30) - cmpStrings(t, "datacache.type", cfg.DataCache.Type, "postgres") - cmpStrings(t, "datacache.filename", cfg.DataCache.Filename, "/usr/db/db.db") - cmpInts(t, "datacache.cache_size", cfg.DataCache.CacheSize, 10000000) - cmpInts(t, "datacache.ttl_seconds", cfg.DataCache.TTLSeconds, 3600) cmpStrings(t, "", cfg.CacheURL.GetBaseURL(), "http://prebidcache.net") cmpStrings(t, "", cfg.GetCachedAssetURL("a0eebc99-9c0b-4ef8-bb00-6bb9bd380a11"), "http://prebidcache.net/cache?uuid=a0eebc99-9c0b-4ef8-bb00-6bb9bd380a11") cmpStrings(t, "adapters.appnexus.endpoint", cfg.Adapters[string(openrtb_ext.BidderAppnexus)].Endpoint, "http://ib.adnxs.com/some/endpoint") @@ -579,7 +590,6 @@ func TestFullConfig(t *testing.T) { cmpStrings(t, "request_validation.ipv6_private_networks", cfg.RequestValidation.IPv6PrivateNetworks[1], "2222::/16") cmpBools(t, "generate_bid_id", cfg.GenerateBidID, true) cmpStrings(t, "debug.override_token", cfg.Debug.OverrideToken, "") - cmpBools(t, "enable_legacy_auction", cfg.EnableLegacyAuction, true) } func TestUnmarshalAdapterExtraInfo(t *testing.T) { @@ -616,6 +626,18 @@ func TestValidateConfig(t *testing.T) { cfg := Configuration{ GDPR: GDPR{ DefaultValue: "1", + TCF2: TCF2{ + Purpose1: TCF2Purpose{EnforcePurpose: TCF2FullEnforcement}, + Purpose2: TCF2Purpose{EnforcePurpose: TCF2FullEnforcement}, + Purpose3: TCF2Purpose{EnforcePurpose: TCF2FullEnforcement}, + Purpose4: TCF2Purpose{EnforcePurpose: TCF2FullEnforcement}, + Purpose5: TCF2Purpose{EnforcePurpose: TCF2FullEnforcement}, + Purpose6: TCF2Purpose{EnforcePurpose: TCF2FullEnforcement}, + Purpose7: TCF2Purpose{EnforcePurpose: TCF2FullEnforcement}, + Purpose8: TCF2Purpose{EnforcePurpose: TCF2FullEnforcement}, + Purpose9: TCF2Purpose{EnforcePurpose: TCF2FullEnforcement}, + Purpose10: TCF2Purpose{EnforcePurpose: TCF2FullEnforcement}, + }, }, StoredRequests: StoredRequests{ Files: FileFetcherConfig{Enabled: true}, @@ -743,6 +765,244 @@ func TestMigrateConfigPurposeOneTreatment(t *testing.T) { } } +func TestMigrateConfigTCF2PurposeEnabledFlags(t *testing.T) { + trueStr := "true" + falseStr := "false" + + tests := []struct { + description string + config []byte + wantPurpose1EnforcePurpose string + wantPurpose2EnforcePurpose string + wantPurpose3EnforcePurpose string + wantPurpose4EnforcePurpose string + wantPurpose5EnforcePurpose string + wantPurpose6EnforcePurpose string + wantPurpose7EnforcePurpose string + wantPurpose8EnforcePurpose string + wantPurpose9EnforcePurpose string + wantPurpose10EnforcePurpose string + wantPurpose1Enabled string + wantPurpose2Enabled string + wantPurpose3Enabled string + wantPurpose4Enabled string + wantPurpose5Enabled string + wantPurpose6Enabled string + wantPurpose7Enabled string + wantPurpose8Enabled string + wantPurpose9Enabled string + wantPurpose10Enabled string + }{ + { + description: "New config and old config flags not set", + config: []byte{}, + }, + { + description: "New config not set, old config set - use old flags", + config: []byte(` + gdpr: + tcf2: + purpose1: + enabled: false + purpose2: + enabled: true + purpose3: + enabled: false + purpose4: + enabled: true + purpose5: + enabled: false + purpose6: + enabled: true + purpose7: + enabled: false + purpose8: + enabled: true + purpose9: + enabled: false + purpose10: + enabled: true + `), + wantPurpose1EnforcePurpose: TCF2NoEnforcement, + wantPurpose2EnforcePurpose: TCF2FullEnforcement, + wantPurpose3EnforcePurpose: TCF2NoEnforcement, + wantPurpose4EnforcePurpose: TCF2FullEnforcement, + wantPurpose5EnforcePurpose: TCF2NoEnforcement, + wantPurpose6EnforcePurpose: TCF2FullEnforcement, + wantPurpose7EnforcePurpose: TCF2NoEnforcement, + wantPurpose8EnforcePurpose: TCF2FullEnforcement, + wantPurpose9EnforcePurpose: TCF2NoEnforcement, + wantPurpose10EnforcePurpose: TCF2FullEnforcement, + wantPurpose1Enabled: falseStr, + wantPurpose2Enabled: trueStr, + wantPurpose3Enabled: falseStr, + wantPurpose4Enabled: trueStr, + wantPurpose5Enabled: falseStr, + wantPurpose6Enabled: trueStr, + wantPurpose7Enabled: falseStr, + wantPurpose8Enabled: trueStr, + wantPurpose9Enabled: falseStr, + wantPurpose10Enabled: trueStr, + }, + { + description: "New config flags set, old config flags not set - use new flags", + config: []byte(` + gdpr: + tcf2: + purpose1: + enforce_purpose: "full" + purpose2: + enforce_purpose: "no" + purpose3: + enforce_purpose: "full" + purpose4: + enforce_purpose: "no" + purpose5: + enforce_purpose: "full" + purpose6: + enforce_purpose: "no" + purpose7: + enforce_purpose: "full" + purpose8: + enforce_purpose: "no" + purpose9: + enforce_purpose: "full" + purpose10: + enforce_purpose: "no" + `), + wantPurpose1EnforcePurpose: TCF2FullEnforcement, + wantPurpose2EnforcePurpose: TCF2NoEnforcement, + wantPurpose3EnforcePurpose: TCF2FullEnforcement, + wantPurpose4EnforcePurpose: TCF2NoEnforcement, + wantPurpose5EnforcePurpose: TCF2FullEnforcement, + wantPurpose6EnforcePurpose: TCF2NoEnforcement, + wantPurpose7EnforcePurpose: TCF2FullEnforcement, + wantPurpose8EnforcePurpose: TCF2NoEnforcement, + wantPurpose9EnforcePurpose: TCF2FullEnforcement, + wantPurpose10EnforcePurpose: TCF2NoEnforcement, + wantPurpose1Enabled: trueStr, + wantPurpose2Enabled: falseStr, + wantPurpose3Enabled: trueStr, + wantPurpose4Enabled: falseStr, + wantPurpose5Enabled: trueStr, + wantPurpose6Enabled: falseStr, + wantPurpose7Enabled: trueStr, + wantPurpose8Enabled: falseStr, + wantPurpose9Enabled: trueStr, + wantPurpose10Enabled: falseStr, + }, + { + description: "New config flags and old config flags set - use new flags", + config: []byte(` + gdpr: + tcf2: + purpose1: + enabled: false + enforce_purpose: "full" + purpose2: + enabled: false + enforce_purpose: "full" + purpose3: + enabled: false + enforce_purpose: "full" + purpose4: + enabled: false + enforce_purpose: "full" + purpose5: + enabled: false + enforce_purpose: "full" + purpose6: + enabled: false + enforce_purpose: "full" + purpose7: + enabled: false + enforce_purpose: "full" + purpose8: + enabled: false + enforce_purpose: "full" + purpose9: + enabled: false + enforce_purpose: "full" + purpose10: + enabled: false + enforce_purpose: "full" + `), + wantPurpose1EnforcePurpose: TCF2FullEnforcement, + wantPurpose2EnforcePurpose: TCF2FullEnforcement, + wantPurpose3EnforcePurpose: TCF2FullEnforcement, + wantPurpose4EnforcePurpose: TCF2FullEnforcement, + wantPurpose5EnforcePurpose: TCF2FullEnforcement, + wantPurpose6EnforcePurpose: TCF2FullEnforcement, + wantPurpose7EnforcePurpose: TCF2FullEnforcement, + wantPurpose8EnforcePurpose: TCF2FullEnforcement, + wantPurpose9EnforcePurpose: TCF2FullEnforcement, + wantPurpose10EnforcePurpose: TCF2FullEnforcement, + wantPurpose1Enabled: trueStr, + wantPurpose2Enabled: trueStr, + wantPurpose3Enabled: trueStr, + wantPurpose4Enabled: trueStr, + wantPurpose5Enabled: trueStr, + wantPurpose6Enabled: trueStr, + wantPurpose7Enabled: trueStr, + wantPurpose8Enabled: trueStr, + wantPurpose9Enabled: trueStr, + wantPurpose10Enabled: trueStr, + }, + } + + for _, tt := range tests { + v := viper.New() + v.SetConfigType("yaml") + v.ReadConfig(bytes.NewBuffer(tt.config)) + + migrateConfigTCF2PurposeEnabledFlags(v) + + if len(tt.config) > 0 { + assert.Equal(t, tt.wantPurpose1EnforcePurpose, v.GetString("gdpr.tcf2.purpose1.enforce_purpose"), tt.description) + assert.Equal(t, tt.wantPurpose2EnforcePurpose, v.GetString("gdpr.tcf2.purpose2.enforce_purpose"), tt.description) + assert.Equal(t, tt.wantPurpose3EnforcePurpose, v.GetString("gdpr.tcf2.purpose3.enforce_purpose"), tt.description) + assert.Equal(t, tt.wantPurpose4EnforcePurpose, v.GetString("gdpr.tcf2.purpose4.enforce_purpose"), tt.description) + assert.Equal(t, tt.wantPurpose5EnforcePurpose, v.GetString("gdpr.tcf2.purpose5.enforce_purpose"), tt.description) + assert.Equal(t, tt.wantPurpose6EnforcePurpose, v.GetString("gdpr.tcf2.purpose6.enforce_purpose"), tt.description) + assert.Equal(t, tt.wantPurpose7EnforcePurpose, v.GetString("gdpr.tcf2.purpose7.enforce_purpose"), tt.description) + assert.Equal(t, tt.wantPurpose8EnforcePurpose, v.GetString("gdpr.tcf2.purpose8.enforce_purpose"), tt.description) + assert.Equal(t, tt.wantPurpose9EnforcePurpose, v.GetString("gdpr.tcf2.purpose9.enforce_purpose"), tt.description) + assert.Equal(t, tt.wantPurpose10EnforcePurpose, v.GetString("gdpr.tcf2.purpose10.enforce_purpose"), tt.description) + assert.Equal(t, tt.wantPurpose1Enabled, v.GetString("gdpr.tcf2.purpose1.enabled"), tt.description) + assert.Equal(t, tt.wantPurpose2Enabled, v.GetString("gdpr.tcf2.purpose2.enabled"), tt.description) + assert.Equal(t, tt.wantPurpose3Enabled, v.GetString("gdpr.tcf2.purpose3.enabled"), tt.description) + assert.Equal(t, tt.wantPurpose4Enabled, v.GetString("gdpr.tcf2.purpose4.enabled"), tt.description) + assert.Equal(t, tt.wantPurpose5Enabled, v.GetString("gdpr.tcf2.purpose5.enabled"), tt.description) + assert.Equal(t, tt.wantPurpose6Enabled, v.GetString("gdpr.tcf2.purpose6.enabled"), tt.description) + assert.Equal(t, tt.wantPurpose7Enabled, v.GetString("gdpr.tcf2.purpose7.enabled"), tt.description) + assert.Equal(t, tt.wantPurpose8Enabled, v.GetString("gdpr.tcf2.purpose8.enabled"), tt.description) + assert.Equal(t, tt.wantPurpose9Enabled, v.GetString("gdpr.tcf2.purpose9.enabled"), tt.description) + assert.Equal(t, tt.wantPurpose10Enabled, v.GetString("gdpr.tcf2.purpose10.enabled"), tt.description) + } else { + assert.Nil(t, v.Get("gdpr.tcf2.purpose1.enforce_purpose"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose2.enforce_purpose"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose3.enforce_purpose"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose4.enforce_purpose"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose5.enforce_purpose"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose6.enforce_purpose"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose7.enforce_purpose"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose8.enforce_purpose"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose9.enforce_purpose"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose10.enforce_purpose"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose1.enabled"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose2.enabled"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose3.enabled"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose4.enabled"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose5.enabled"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose6.enabled"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose7.enabled"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose8.enabled"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose9.enabled"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose10.enabled"), tt.description) + } + } +} + func TestInvalidAdapterEndpointConfig(t *testing.T) { v := viper.New() SetupViper(v, "") @@ -817,6 +1077,30 @@ func TestMissingGDPRDefaultValue(t *testing.T) { assertOneError(t, cfg.validate(v), "gdpr.default_value is required and must be specified") } +func TestInvalidEnforcePurpose(t *testing.T) { + cfg, v := newDefaultConfig(t) + cfg.GDPR.TCF2.Purpose1.EnforcePurpose = "" + cfg.GDPR.TCF2.Purpose2.EnforcePurpose = TCF2NoEnforcement + cfg.GDPR.TCF2.Purpose3.EnforcePurpose = TCF2NoEnforcement + cfg.GDPR.TCF2.Purpose4.EnforcePurpose = TCF2NoEnforcement + cfg.GDPR.TCF2.Purpose5.EnforcePurpose = "invalid1" + cfg.GDPR.TCF2.Purpose6.EnforcePurpose = "invalid2" + cfg.GDPR.TCF2.Purpose7.EnforcePurpose = TCF2FullEnforcement + cfg.GDPR.TCF2.Purpose8.EnforcePurpose = TCF2FullEnforcement + cfg.GDPR.TCF2.Purpose9.EnforcePurpose = TCF2FullEnforcement + cfg.GDPR.TCF2.Purpose10.EnforcePurpose = "invalid3" + + errs := cfg.validate(v) + + expectedErrs := []error{ + errors.New("gdpr.tcf2.purpose1.enforce_purpose must be \"no\" or \"full\". Got "), + errors.New("gdpr.tcf2.purpose5.enforce_purpose must be \"no\" or \"full\". Got invalid1"), + errors.New("gdpr.tcf2.purpose6.enforce_purpose must be \"no\" or \"full\". Got invalid2"), + errors.New("gdpr.tcf2.purpose10.enforce_purpose must be \"no\" or \"full\". Got invalid3"), + } + assert.ElementsMatch(t, errs, expectedErrs, "gdpr.tcf2.purposeX.enforce_purpose should prevent invalid values but it doesn't") +} + func TestNegativeCurrencyConverterFetchInterval(t *testing.T) { v := viper.New() v.Set("gdpr.default_value", "0") diff --git a/config/stored_requests.go b/config/stored_requests.go index ee78179eb65..e752e9e4d9d 100644 --- a/config/stored_requests.go +++ b/config/stored_requests.go @@ -133,7 +133,6 @@ func resolvedStoredRequestsConfig(cfg *Configuration) { cfg.StoredVideo.dataType = VideoDataType cfg.CategoryMapping.dataType = CategoryDataType cfg.Accounts.dataType = AccountDataType - return } func (cfg *StoredRequests) validate(errs []error) []error { diff --git a/config/structlog.go b/config/structlog.go index a91e5ab857e..911f475717d 100644 --- a/config/structlog.go +++ b/config/structlog.go @@ -12,7 +12,7 @@ import ( type logMsg func(string, ...interface{}) var mapregex = regexp.MustCompile(`mapstructure:"([^"]+)"`) -var blacklistregexp = []*regexp.Regexp{ +var blocklistregexp = []*regexp.Regexp{ regexp.MustCompile("password"), } @@ -84,7 +84,7 @@ func fieldNameByTag(f reflect.StructField) string { } func allowedName(name string) bool { - for _, r := range blacklistregexp { + for _, r := range blocklistregexp { if r.MatchString(name) { return false } diff --git a/currency/validation.go b/currency/validation.go new file mode 100644 index 00000000000..7a0e2aa02bd --- /dev/null +++ b/currency/validation.go @@ -0,0 +1,34 @@ +package currency + +import ( + "fmt" + + "golang.org/x/text/currency" + + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +// ValidateCustomRates throws a bad input error if any of the 3-digit currency codes found in +// the bidRequest.ext.prebid.currency field is invalid, malfomed or does not represent any actual +// currency. No error is thrown if bidRequest.ext.prebid.currency is invalid or empty. +func ValidateCustomRates(bidReqCurrencyRates *openrtb_ext.ExtRequestCurrency) error { + if bidReqCurrencyRates == nil { + return nil + } + + for fromCurrency, rates := range bidReqCurrencyRates.ConversionRates { + // Check if fromCurrency is a valid 3-letter currency code + if _, err := currency.ParseISO(fromCurrency); err != nil { + return &errortypes.BadInput{Message: fmt.Sprintf("currency code %s is not recognized or malformed", fromCurrency)} + } + + // Check if currencies mapped to fromCurrency are valid 3-letter currency codes + for toCurrency := range rates { + if _, err := currency.ParseISO(toCurrency); err != nil { + return &errortypes.BadInput{Message: fmt.Sprintf("currency code %s is not recognized or malformed", toCurrency)} + } + } + } + return nil +} diff --git a/currency/validation_test.go b/currency/validation_test.go new file mode 100644 index 00000000000..d49b9824986 --- /dev/null +++ b/currency/validation_test.go @@ -0,0 +1,116 @@ +package currency + +import ( + "testing" + + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestValidateCustomRates(t *testing.T) { + boolTrue := true + boolFalse := false + + testCases := []struct { + desc string + inBidReqCurrencies *openrtb_ext.ExtRequestCurrency + outCurrencyError error + }{ + { + desc: "nil input, no errors expected", + inBidReqCurrencies: nil, + outCurrencyError: nil, + }, + { + desc: "empty custom currency rates but UsePBSRates is set to false, we don't return error nor warning", + inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: map[string]map[string]float64{}, + UsePBSRates: &boolFalse, + }, + outCurrencyError: nil, + }, + { + desc: "empty custom currency rates but UsePBSRates is set to true, no need to return error because we can use PBS rates", + inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: map[string]map[string]float64{}, + UsePBSRates: &boolTrue, + }, + outCurrencyError: nil, + }, + { + desc: "UsePBSRates is nil and defaults to true, bidExt fromCurrency is invalid, expect bad input error", + inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: map[string]map[string]float64{ + "FOO": { + "GBP": 1.2, + "MXN": 0.05, + "JPY": 0.95, + }, + }, + }, + outCurrencyError: &errortypes.BadInput{Message: "currency code FOO is not recognized or malformed"}, + }, + { + desc: "UsePBSRates set to false, bidExt fromCurrency is invalid, expect bad input error", + inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: map[string]map[string]float64{ + "FOO": { + "GBP": 1.2, + "MXN": 0.05, + "JPY": 0.95, + }, + }, + UsePBSRates: &boolFalse, + }, + outCurrencyError: &errortypes.BadInput{Message: "currency code FOO is not recognized or malformed"}, + }, + { + desc: "UsePBSRates set to false, some of the bidExt 'to' Currencies are invalid, expect bad input error when parsing the first invalid currency code", + inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: map[string]map[string]float64{ + "USD": { + "FOO": 10.0, + "MXN": 0.05, + }, + }, + UsePBSRates: &boolFalse, + }, + outCurrencyError: &errortypes.BadInput{Message: "currency code FOO is not recognized or malformed"}, + }, + { + desc: "UsePBSRates set to false, some of the bidExt 'from' and 'to' currencies are invalid, expect bad input error when parsing the first invalid currency code", + inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: map[string]map[string]float64{ + "FOO": { + "MXN": 0.05, + "CAD": 0.95, + }, + }, + UsePBSRates: &boolFalse, + }, + outCurrencyError: &errortypes.BadInput{Message: "currency code FOO is not recognized or malformed"}, + }, + { + desc: "All 3-digit currency codes exist, expect no error", + inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: map[string]map[string]float64{ + "USD": { + "MXN": 0.05, + }, + "MXN": { + "JPY": 10.0, + "EUR": 10.95, + }, + }, + UsePBSRates: &boolFalse, + }, + }, + } + + for _, tc := range testCases { + actualErr := ValidateCustomRates(tc.inBidReqCurrencies) + + assert.Equal(t, tc.outCurrencyError, actualErr, tc.desc) + } +} diff --git a/endpoints/auction.go b/endpoints/auction.go deleted file mode 100644 index 10d2ced6c37..00000000000 --- a/endpoints/auction.go +++ /dev/null @@ -1,513 +0,0 @@ -package endpoints - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "runtime/debug" - "sort" - "strconv" - "time" - - "github.com/golang/glog" - "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/cache" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/exchange" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/pbs" - pbc "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/prebid/prebid-server/privacy" - gdprPrivacy "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/prebid/prebid-server/usersync" -) - -var allSyncTypes []usersync.SyncType = []usersync.SyncType{usersync.SyncTypeIFrame, usersync.SyncTypeRedirect} - -type bidResult struct { - bidder *pbs.PBSBidder - bidList pbs.PBSBidSlice -} - -const defaultPriceGranularity = "med" - -func min(x, y int) int { - if x < y { - return x - } - return y -} - -func writeAuctionError(w http.ResponseWriter, s string, err error) { - var resp pbs.PBSResponse - if err != nil { - resp.Status = fmt.Sprintf("%s: %v", s, err) - } else { - resp.Status = s - } - b, err := json.Marshal(&resp) - if err != nil { - glog.Errorf("Failed to marshal auction error JSON: %s", err) - } else { - w.Write(b) - } -} - -type auction struct { - cfg *config.Configuration - syncersByBidder map[string]usersync.Syncer - gdprPerms gdpr.Permissions - metricsEngine metrics.MetricsEngine - dataCache cache.Cache - exchanges map[string]adapters.Adapter -} - -func Auction(cfg *config.Configuration, syncersByBidder map[string]usersync.Syncer, gdprPerms gdpr.Permissions, metricsEngine metrics.MetricsEngine, dataCache cache.Cache, exchanges map[string]adapters.Adapter) httprouter.Handle { - a := &auction{ - cfg: cfg, - syncersByBidder: syncersByBidder, - gdprPerms: gdprPerms, - metricsEngine: metricsEngine, - dataCache: dataCache, - exchanges: exchanges, - } - return a.auction -} - -func (a *auction) auction(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - w.Header().Add("Content-Type", "application/json") - var labels = getDefaultLabels(r) - req, err := pbs.ParsePBSRequest(r, &a.cfg.AuctionTimeouts, a.dataCache, &(a.cfg.HostCookie)) - - defer a.recordMetrics(req, labels) - - if err != nil { - if glog.V(2) { - glog.Infof("Failed to parse /auction request: %v", err) - } - writeAuctionError(w, "Error parsing request", err) - labels.RequestStatus = metrics.RequestStatusBadInput - return - } - status := "OK" - setLabelSource(&labels, req, &status) - ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(req.TimeoutMillis)) - defer cancel() - account, err := a.dataCache.Accounts().Get(req.AccountID) - if err != nil { - if glog.V(2) { - glog.Infof("Invalid account id: %v", err) - } - writeAuctionError(w, "Unknown account id", fmt.Errorf("Unknown account")) - labels.RequestStatus = metrics.RequestStatusBadInput - return - } - labels.PubID = req.AccountID - resp := pbs.PBSResponse{ - Status: status, - TID: req.Tid, - BidderStatus: req.Bidders, - } - ch := make(chan bidResult) - sentBids := 0 - for _, bidder := range req.Bidders { - if ex, ok := a.exchanges[bidder.BidderCode]; ok { - // Make sure we have an independent label struct for each bidder. We don't want to run into issues with the goroutine below. - blabels := metrics.AdapterLabels{ - Source: labels.Source, - RType: labels.RType, - Adapter: openrtb_ext.BidderName(bidder.BidderCode), - PubID: labels.PubID, - CookieFlag: labels.CookieFlag, - AdapterBids: metrics.AdapterBidPresent, - } - if skip := a.processUserSync(req, bidder, blabels, ex, &ctx); skip == true { - continue - } - sentBids++ - bidderRunner := a.recoverSafely(func(bidder *pbs.PBSBidder, aLabels metrics.AdapterLabels) { - - start := time.Now() - bidList, err := ex.Call(ctx, req, bidder) - a.metricsEngine.RecordAdapterTime(aLabels, time.Since(start)) - bidder.ResponseTime = int(time.Since(start) / time.Millisecond) - processBidResult(bidList, bidder, &aLabels, a.metricsEngine, err) - - ch <- bidResult{ - bidder: bidder, - bidList: bidList, - // Bidder done, record bidder metrics - } - a.metricsEngine.RecordAdapterRequest(aLabels) - }) - - go bidderRunner(bidder, blabels) - - } else if bidder.BidderCode == "lifestreet" { - bidder.Error = "Bidder is no longer available" - } else { - bidder.Error = "Unsupported bidder" - } - } - for i := 0; i < sentBids; i++ { - result := <-ch - for _, bid := range result.bidList { - resp.Bids = append(resp.Bids, bid) - } - } - if err := cacheAccordingToMarkup(req, &resp, ctx, a, &labels); err != nil { - writeAuctionError(w, "Prebid cache failed", err) - labels.RequestStatus = metrics.RequestStatusErr - return - } - if req.SortBids == 1 { - sortBidsAddKeywordsMobile(resp.Bids, req, account.PriceGranularity) - } - if glog.V(2) { - glog.Infof("Request for %d ad units on url %s by account %s got %d bids", len(req.AdUnits), req.Url, req.AccountID, len(resp.Bids)) - } - enc := json.NewEncoder(w) - enc.SetEscapeHTML(false) - enc.Encode(resp) -} - -func (a *auction) recoverSafely(inner func(*pbs.PBSBidder, metrics.AdapterLabels)) func(*pbs.PBSBidder, metrics.AdapterLabels) { - return func(bidder *pbs.PBSBidder, labels metrics.AdapterLabels) { - defer func() { - if r := recover(); r != nil { - if bidder == nil { - glog.Errorf("Legacy auction recovered panic: %v. Stack trace is: %v", r, string(debug.Stack())) - } else { - glog.Errorf("Legacy auction recovered panic from Bidder %s: %v. Stack trace is: %v", bidder.BidderCode, r, string(debug.Stack())) - } - a.metricsEngine.RecordAdapterPanic(labels) - } - }() - inner(bidder, labels) - } -} - -func (a *auction) shouldUsersync(ctx context.Context, bidder openrtb_ext.BidderName, gdprPrivacyPolicy gdprPrivacy.Policy) bool { - gdprSignal := gdpr.SignalAmbiguous - if signal, err := gdpr.SignalParse(gdprPrivacyPolicy.Signal); err != nil { - gdprSignal = signal - } - - if canSync, err := a.gdprPerms.HostCookiesAllowed(ctx, gdprSignal, gdprPrivacyPolicy.Consent); err != nil || !canSync { - return false - } - canSync, err := a.gdprPerms.BidderSyncAllowed(ctx, bidder, gdprSignal, gdprPrivacyPolicy.Consent) - return canSync && err == nil -} - -// cache video bids only for Web -func cacheVideoOnly(bids pbs.PBSBidSlice, ctx context.Context, deps *auction, labels *metrics.Labels) error { - var cobjs []*pbc.CacheObject - for _, bid := range bids { - if bid.CreativeMediaType == "video" { - cobjs = append(cobjs, &pbc.CacheObject{ - Value: bid.Adm, - IsVideo: true, - }) - } - } - err := pbc.Put(ctx, cobjs) - if err != nil { - return err - } - videoIndex := 0 - for _, bid := range bids { - if bid.CreativeMediaType == "video" { - bid.CacheID = cobjs[videoIndex].UUID - bid.CacheURL = deps.cfg.GetCachedAssetURL(bid.CacheID) - bid.NURL = "" - bid.Adm = "" - videoIndex++ - } - } - return nil -} - -// checkForValidBidSize goes through list of bids & find those which are banner mediaType and with height or width not defined -// determine the num of ad unit sizes that were used in corresponding bid request -// if num_adunit_sizes == 1, assign the height and/or width to bid's height/width -// if num_adunit_sizes > 1, reject the bid (remove from list) and return an error -// return updated bid list object for next steps in auction -func checkForValidBidSize(bids pbs.PBSBidSlice, bidder *pbs.PBSBidder) pbs.PBSBidSlice { - finalValidBids := make([]*pbs.PBSBid, len(bids)) - finalBidCounter := 0 -bidLoop: - for _, bid := range bids { - if isUndimensionedBanner(bid) { - for _, adunit := range bidder.AdUnits { - if copyBannerDimensions(&adunit, bid, finalValidBids, &finalBidCounter) { - continue bidLoop - } - } - } else { - finalValidBids[finalBidCounter] = bid - finalBidCounter = finalBidCounter + 1 - } - } - return finalValidBids[:finalBidCounter] -} - -func isUndimensionedBanner(bid *pbs.PBSBid) bool { - return bid.CreativeMediaType == "banner" && (bid.Height == 0 || bid.Width == 0) -} - -func copyBannerDimensions(adunit *pbs.PBSAdUnit, bid *pbs.PBSBid, finalValidBids []*pbs.PBSBid, finalBidCounter *int) bool { - var bidIDEqualsCode bool = false - - if adunit.BidID == bid.BidID && adunit.Code == bid.AdUnitCode && adunit.Sizes != nil { - if len(adunit.Sizes) == 1 { - bid.Width, bid.Height = adunit.Sizes[0].W, adunit.Sizes[0].H - finalValidBids[*finalBidCounter] = bid - *finalBidCounter += 1 - } else if len(adunit.Sizes) > 1 { - glog.Warningf("Bid was rejected for bidder %s because no size was defined", bid.BidderCode) - } - bidIDEqualsCode = true - } - - return bidIDEqualsCode -} - -// sortBidsAddKeywordsMobile sorts the bids and adds ad server targeting keywords to each bid. -// The bids are sorted by cpm to find the highest bid. -// The ad server targeting keywords are added to all bids, with specific keywords for the highest bid. -func sortBidsAddKeywordsMobile(bids pbs.PBSBidSlice, pbs_req *pbs.PBSRequest, priceGranularitySetting string) { - if priceGranularitySetting == "" { - priceGranularitySetting = defaultPriceGranularity - } - - // record bids by ad unit code for sorting - code_bids := make(map[string]pbs.PBSBidSlice, len(bids)) - for _, bid := range bids { - code_bids[bid.AdUnitCode] = append(code_bids[bid.AdUnitCode], bid) - } - - // loop through ad units to find top bid - for _, unit := range pbs_req.AdUnits { - bar := code_bids[unit.Code] - - if len(bar) == 0 { - if glog.V(3) { - glog.Infof("No bids for ad unit '%s'", unit.Code) - } - continue - } - sort.Sort(bar) - - // after sorting we need to add the ad targeting keywords - for i, bid := range bar { - // We should eventually check for the error and do something. - roundedCpm := exchange.GetPriceBucket(bid.Price, openrtb_ext.PriceGranularityFromString(priceGranularitySetting)) - - hbSize := "" - if bid.Width != 0 && bid.Height != 0 { - width := strconv.FormatInt(bid.Width, 10) - height := strconv.FormatInt(bid.Height, 10) - hbSize = width + "x" + height - } - - hbPbBidderKey := string(openrtb_ext.HbpbConstantKey) + "_" + bid.BidderCode - hbBidderBidderKey := string(openrtb_ext.HbBidderConstantKey) + "_" + bid.BidderCode - hbCacheIDBidderKey := string(openrtb_ext.HbCacheKey) + "_" + bid.BidderCode - hbDealIDBidderKey := string(openrtb_ext.HbDealIDConstantKey) + "_" + bid.BidderCode - hbSizeBidderKey := string(openrtb_ext.HbSizeConstantKey) + "_" + bid.BidderCode - if pbs_req.MaxKeyLength != 0 { - hbPbBidderKey = hbPbBidderKey[:min(len(hbPbBidderKey), int(pbs_req.MaxKeyLength))] - hbBidderBidderKey = hbBidderBidderKey[:min(len(hbBidderBidderKey), int(pbs_req.MaxKeyLength))] - hbCacheIDBidderKey = hbCacheIDBidderKey[:min(len(hbCacheIDBidderKey), int(pbs_req.MaxKeyLength))] - hbDealIDBidderKey = hbDealIDBidderKey[:min(len(hbDealIDBidderKey), int(pbs_req.MaxKeyLength))] - hbSizeBidderKey = hbSizeBidderKey[:min(len(hbSizeBidderKey), int(pbs_req.MaxKeyLength))] - } - - // fixes #288 where map was being overwritten instead of updated - if bid.AdServerTargeting == nil { - bid.AdServerTargeting = make(map[string]string) - } - kvs := bid.AdServerTargeting - - kvs[hbPbBidderKey] = roundedCpm - kvs[hbBidderBidderKey] = bid.BidderCode - kvs[hbCacheIDBidderKey] = bid.CacheID - - if hbSize != "" { - kvs[hbSizeBidderKey] = hbSize - } - if bid.DealId != "" { - kvs[hbDealIDBidderKey] = bid.DealId - } - // For the top bid, we want to add the following additional keys - if i == 0 { - kvs[string(openrtb_ext.HbpbConstantKey)] = roundedCpm - kvs[string(openrtb_ext.HbBidderConstantKey)] = bid.BidderCode - kvs[string(openrtb_ext.HbCacheKey)] = bid.CacheID - if bid.DealId != "" { - kvs[string(openrtb_ext.HbDealIDConstantKey)] = bid.DealId - } - if hbSize != "" { - kvs[string(openrtb_ext.HbSizeConstantKey)] = hbSize - } - } - } - } -} - -func getDefaultLabels(r *http.Request) metrics.Labels { - return metrics.Labels{ - Source: metrics.DemandUnknown, - RType: metrics.ReqTypeLegacy, - PubID: "", - CookieFlag: metrics.CookieFlagUnknown, - RequestStatus: metrics.RequestStatusOK, - } -} - -func setLabelSource(labels *metrics.Labels, req *pbs.PBSRequest, status *string) { - if req.App != nil { - labels.Source = metrics.DemandApp - } else { - labels.Source = metrics.DemandWeb - if req.Cookie.HasAnyLiveSyncs() { - labels.CookieFlag = metrics.CookieFlagYes - } else { - labels.CookieFlag = metrics.CookieFlagNo - *status = "no_cookie" - } - } -} - -func cacheAccordingToMarkup(req *pbs.PBSRequest, resp *pbs.PBSResponse, ctx context.Context, a *auction, labels *metrics.Labels) error { - if req.CacheMarkup == 1 { - cobjs := make([]*pbc.CacheObject, len(resp.Bids)) - for i, bid := range resp.Bids { - if bid.CreativeMediaType == "video" { - cobjs[i] = &pbc.CacheObject{ - Value: bid.Adm, - IsVideo: true, - } - } else { - cobjs[i] = &pbc.CacheObject{ - Value: &pbc.BidCache{ - Adm: bid.Adm, - NURL: bid.NURL, - Width: bid.Width, - Height: bid.Height, - }, - IsVideo: false, - } - } - } - if err := pbc.Put(ctx, cobjs); err != nil { - return err - } - for i, bid := range resp.Bids { - bid.CacheID = cobjs[i].UUID - bid.CacheURL = a.cfg.GetCachedAssetURL(bid.CacheID) - bid.NURL = "" - bid.Adm = "" - } - } else if req.CacheMarkup == 2 { - return cacheVideoOnly(resp.Bids, ctx, a, labels) - } - return nil -} - -func processBidResult(bidList pbs.PBSBidSlice, bidder *pbs.PBSBidder, aLabels *metrics.AdapterLabels, metricsEngine metrics.MetricsEngine, err error) { - if err != nil { - var s struct{} - if err == context.DeadlineExceeded { - aLabels.AdapterErrors = map[metrics.AdapterError]struct{}{metrics.AdapterErrorTimeout: s} - bidder.Error = "Timed out" - } else if err != context.Canceled { - bidder.Error = err.Error() - switch err.(type) { - case *errortypes.BadInput: - aLabels.AdapterErrors = map[metrics.AdapterError]struct{}{metrics.AdapterErrorBadInput: s} - case *errortypes.BadServerResponse: - aLabels.AdapterErrors = map[metrics.AdapterError]struct{}{metrics.AdapterErrorBadServerResponse: s} - default: - glog.Warningf("Error from bidder %v. Ignoring all bids: %v", bidder.BidderCode, err) - aLabels.AdapterErrors = map[metrics.AdapterError]struct{}{metrics.AdapterErrorUnknown: s} - } - } - } else if bidList != nil { - bidList = checkForValidBidSize(bidList, bidder) - bidder.NumBids = len(bidList) - for _, bid := range bidList { - var cpm = float64(bid.Price * 1000) - metricsEngine.RecordAdapterPrice(*aLabels, cpm) - switch bid.CreativeMediaType { - case "banner": - metricsEngine.RecordAdapterBidReceived(*aLabels, openrtb_ext.BidTypeBanner, bid.Adm != "") - case "video": - metricsEngine.RecordAdapterBidReceived(*aLabels, openrtb_ext.BidTypeVideo, bid.Adm != "") - } - bid.ResponseTime = bidder.ResponseTime - } - } else { - bidder.NoBid = true - aLabels.AdapterBids = metrics.AdapterBidNone - } -} - -func (a *auction) recordMetrics(req *pbs.PBSRequest, labels metrics.Labels) { - a.metricsEngine.RecordRequest(labels) - if req == nil { - a.metricsEngine.RecordLegacyImps(labels, 0) - return - } - a.metricsEngine.RecordLegacyImps(labels, len(req.AdUnits)) - a.metricsEngine.RecordRequestTime(labels, time.Since(req.Start)) -} - -func (a *auction) processUserSync(req *pbs.PBSRequest, bidder *pbs.PBSBidder, blabels metrics.AdapterLabels, ex adapters.Adapter, ctx *context.Context) bool { - var skip bool = false - if req.App != nil { - return skip - } - // If exchanges[bidderCode] exists, then a.syncers[bidderCode] exists *except for districtm*. - // OpenRTB handles aliases differently, so this hack will keep legacy code working. For all other - // bidderCodes, a.syncers[bidderCode] will exist if exchanges[bidderCode] also does. - // This is guaranteed by the TestSyncers unit test inside usersync/usersync_test.go, which compares these maps to the (source of truth) openrtb_ext.BidderMap: - syncerCode := bidder.BidderCode - if syncerCode == "districtm" { - syncerCode = "appnexus" - } - syncer := a.syncersByBidder[syncerCode] - uid, _, _ := req.Cookie.GetUID(syncer.Key()) - if uid == "" { - bidder.NoCookie = true - privacyPolicies := privacy.Policies{ - GDPR: gdprPrivacy.Policy{ - Signal: req.ParseGDPR(), - Consent: req.ParseConsent(), - }, - } - if a.shouldUsersync(*ctx, openrtb_ext.BidderName(syncerCode), privacyPolicies.GDPR) { - sync, err := syncer.GetSync(allSyncTypes, privacyPolicies) - if err == nil { - bidder.UsersyncInfo = &pbs.UsersyncInfo{ - URL: sync.URL, - Type: string(sync.Type), - SupportCORS: sync.SupportCORS, - } - } else { - glog.Errorf("Failed to get usersync info for %s: %v", syncerCode, err) - } - } - blabels.CookieFlag = metrics.CookieFlagNo - if ex.SkipNoCookies() { - skip = true - } - } - return skip -} diff --git a/endpoints/auction_test.go b/endpoints/auction_test.go deleted file mode 100644 index 1a30e025faa..00000000000 --- a/endpoints/auction_test.go +++ /dev/null @@ -1,654 +0,0 @@ -package endpoints - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/cache/dummycache" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/metrics" - metricsConf "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/pbs" - "github.com/prebid/prebid-server/prebid_cache_client" - gdprPolicy "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/prebid/prebid-server/usersync" - "github.com/spf13/viper" - - "github.com/stretchr/testify/assert" -) - -func TestSortBidsAndAddKeywordsForMobile(t *testing.T) { - body := []byte(`{ - "max_key_length":20, - "user":{ - "gender":"F", - "buyeruid":"test_buyeruid", - "yob":2000, - "id":"testid" - }, - "prebid_version":"0.21.0-pre", - "sort_bids":1, - "ad_units":[ - { - "sizes":[ - { - "w":300, - "h":250 - } - ], - "config_id":"ad5ffb41-3492-40f3-9c25-ade093eb4e5f", - "code":"test_adunitcode" - } - ], - "cache_markup":1, - "app":{ - "bundle":"AppNexus.PrebidMobileDemo", - "ver":"0.0.1" - }, - "sdk":{ - "version":"0.0.1", - "platform":"iOS", - "source":"prebid-mobile" - }, - "device":{ - "ifa":"test_device_ifa", - "osv":"9.3.5", - "os":"iOS", - "make":"Apple", - "model":"iPhone6,1" - }, - "tid":"abcd", - "account_id":"aecd6ef7-b992-4e99-9bb8-65e2d984e1dd" - } - `) - r := httptest.NewRequest("POST", "/auction", bytes.NewBuffer(body)) - d, _ := dummycache.New() - hcc := config.HostCookie{} - - pbs_req, err := pbs.ParsePBSRequest(r, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, d, &hcc) - if err != nil { - t.Errorf("Unexpected error on parsing %v", err) - } - - bids := make(pbs.PBSBidSlice, 0) - - fb_bid := pbs.PBSBid{ - BidID: "test_bidid", - AdUnitCode: "test_adunitcode", - BidderCode: "audienceNetwork", - Price: 2.00, - Adm: "test_adm", - Width: 300, - Height: 250, - CacheID: "test_cache_id1", - DealId: "2345", - } - bids = append(bids, &fb_bid) - an_bid := pbs.PBSBid{ - BidID: "test_bidid2", - AdUnitCode: "test_adunitcode", - BidderCode: "appnexus", - Price: 1.00, - Adm: "test_adm", - Width: 320, - Height: 50, - CacheID: "test_cache_id2", - DealId: "1234", - } - bids = append(bids, &an_bid) - rb_bid := pbs.PBSBid{ - BidID: "test_bidid2", - AdUnitCode: "test_adunitcode", - BidderCode: "rubicon", - Price: 1.00, - Adm: "test_adm", - Width: 300, - Height: 250, - CacheID: "test_cache_id2", - DealId: "7890", - } - rb_bid.AdServerTargeting = map[string]string{ - "rpfl_1001": "15_tier0100", - } - bids = append(bids, &rb_bid) - nosize_bid := pbs.PBSBid{ - BidID: "test_bidid2", - AdUnitCode: "test_adunitcode", - BidderCode: "nosizebidder", - Price: 1.00, - Adm: "test_adm", - CacheID: "test_cache_id2", - } - bids = append(bids, &nosize_bid) - nodeal_bid := pbs.PBSBid{ - BidID: "test_bidid2", - AdUnitCode: "test_adunitcode", - BidderCode: "nodeal", - Price: 1.00, - Adm: "test_adm", - CacheID: "test_cache_id2", - } - bids = append(bids, &nodeal_bid) - pbs_resp := pbs.PBSResponse{ - Bids: bids, - } - sortBidsAddKeywordsMobile(pbs_resp.Bids, pbs_req, "") - - for _, bid := range bids { - if bid.AdServerTargeting == nil { - t.Error("Ad server targeting should not be nil") - } - if bid.BidderCode == "audienceNetwork" { - if bid.AdServerTargeting[string(openrtb_ext.HbSizeConstantKey)] != "300x250" { - t.Error(string(openrtb_ext.HbSizeConstantKey) + " key was not parsed correctly") - } - if bid.AdServerTargeting[string(openrtb_ext.HbpbConstantKey)] != "2.00" { - t.Error(string(openrtb_ext.HbpbConstantKey)+" key was not parsed correctly ", bid.AdServerTargeting[string(openrtb_ext.HbpbConstantKey)]) - } - - if bid.AdServerTargeting[string(openrtb_ext.HbCacheKey)] != "test_cache_id1" { - t.Error(string(openrtb_ext.HbCacheKey) + " key was not parsed correctly") - } - if bid.AdServerTargeting[string(openrtb_ext.HbBidderConstantKey)] != "audienceNetwork" { - t.Error(string(openrtb_ext.HbBidderConstantKey) + " key was not parsed correctly") - } - if bid.AdServerTargeting[string(openrtb_ext.HbDealIDConstantKey)] != "2345" { - t.Error(string(openrtb_ext.HbDealIDConstantKey) + " key was not parsed correctly ") - } - } - if bid.BidderCode == "appnexus" { - if bid.AdServerTargeting[string(openrtb_ext.HbSizeConstantKey)+"_appnexus"] != "320x50" { - t.Error(string(openrtb_ext.HbSizeConstantKey) + " key for appnexus bidder was not parsed correctly") - } - if bid.AdServerTargeting[string(openrtb_ext.HbCacheKey)+"_appnexus"] != "test_cache_id2" { - t.Error(string(openrtb_ext.HbCacheKey) + " key for appnexus bidder was not parsed correctly") - } - if bid.AdServerTargeting[string(openrtb_ext.HbBidderConstantKey)+"_appnexus"] != "appnexus" { - t.Error(string(openrtb_ext.HbBidderConstantKey) + " key for appnexus bidder was not parsed correctly") - } - if bid.AdServerTargeting[string(openrtb_ext.HbpbConstantKey)+"_appnexus"] != "1.00" { - t.Error(string(openrtb_ext.HbpbConstantKey) + " key for appnexus bidder was not parsed correctly") - } - if bid.AdServerTargeting[string(openrtb_ext.HbpbConstantKey)] != "" { - t.Error(string(openrtb_ext.HbpbConstantKey) + " key was parsed for two bidders") - } - if bid.AdServerTargeting[string(openrtb_ext.HbDealIDConstantKey)+"_appnexus"] != "1234" { - t.Errorf(string(openrtb_ext.HbDealIDConstantKey)+"_appnexus was not parsed correctly %v", bid.AdServerTargeting[string(openrtb_ext.HbDealIDConstantKey)+"_appnexus"]) - } - } - if bid.BidderCode == string(openrtb_ext.BidderRubicon) { - if bid.AdServerTargeting["rpfl_1001"] != "15_tier0100" { - t.Error("custom ad_server_targeting KVPs from adapter were not preserved") - } - } - if bid.BidderCode == "nosizebidder" { - if _, exists := bid.AdServerTargeting[string(openrtb_ext.HbSizeConstantKey)+"_nosizebidder"]; exists { - t.Error(string(openrtb_ext.HbSizeConstantKey)+" key for nosize bidder was not parsed correctly", bid.AdServerTargeting) - } - } - if bid.BidderCode == "nodeal" { - if _, exists := bid.AdServerTargeting[string(openrtb_ext.HbDealIDConstantKey)+"_nodeal"]; exists { - t.Error(string(openrtb_ext.HbDealIDConstantKey) + " key for nodeal bidder was not parsed correctly") - } - } - } -} - -var ( - MaxValueLength = 1024 * 10 - MaxNumValues = 10 -) - -type responseObject struct { - UUID string `json:"uuid"` -} - -type response struct { - Responses []responseObject `json:"responses"` -} - -type putAnyObject struct { - Type string `json:"type"` - Value json.RawMessage `json:"value"` -} - -type putAnyRequest struct { - Puts []putAnyObject `json:"puts"` -} - -func DummyPrebidCacheServer(w http.ResponseWriter, r *http.Request) { - body, err := ioutil.ReadAll(r.Body) - if err != nil { - http.Error(w, "Failed to read the request body.", http.StatusBadRequest) - return - } - defer r.Body.Close() - var put putAnyRequest - - err = json.Unmarshal(body, &put) - if err != nil { - http.Error(w, "Request body "+string(body)+" is not valid JSON.", http.StatusBadRequest) - return - } - - if len(put.Puts) > MaxNumValues { - http.Error(w, fmt.Sprintf("More keys than allowed: %d", MaxNumValues), http.StatusBadRequest) - return - } - - resp := response{ - Responses: make([]responseObject, len(put.Puts)), - } - for i, p := range put.Puts { - resp.Responses[i].UUID = fmt.Sprintf("UUID-%d", i+1) // deterministic for testing - if len(p.Value) > MaxValueLength { - http.Error(w, fmt.Sprintf("Value is larger than allowed size: %d", MaxValueLength), http.StatusBadRequest) - return - } - if len(p.Value) == 0 { - http.Error(w, "Missing value.", http.StatusBadRequest) - return - } - if p.Type != "xml" && p.Type != "json" { - http.Error(w, fmt.Sprintf("Type must be one of [\"json\", \"xml\"]. Found %v", p.Type), http.StatusBadRequest) - return - } - } - - b, err := json.Marshal(&resp) - if err != nil { - http.Error(w, "Failed to serialize UUIDs into JSON.", http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "application/json") - w.Write(b) -} - -func TestCacheVideoOnly(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(DummyPrebidCacheServer)) - defer server.Close() - - bids := make(pbs.PBSBidSlice, 0) - fbBid := pbs.PBSBid{ - BidID: "test_bidid0", - AdUnitCode: "test_adunitcode0", - BidderCode: "audienceNetwork", - Price: 2.00, - Adm: "fb_test_adm", - Width: 300, - Height: 250, - DealId: "2345", - CreativeMediaType: "video", - } - bids = append(bids, &fbBid) - anBid := pbs.PBSBid{ - BidID: "test_bidid1", - AdUnitCode: "test_adunitcode1", - BidderCode: "appnexus", - Price: 1.00, - Adm: "an_test_adm", - Width: 320, - Height: 50, - DealId: "1234", - CreativeMediaType: "banner", - } - bids = append(bids, &anBid) - rbBannerBid := pbs.PBSBid{ - BidID: "test_bidid2", - AdUnitCode: "test_adunitcode2", - BidderCode: "rubicon", - Price: 1.00, - Adm: "rb_banner_test_adm", - Width: 300, - Height: 250, - DealId: "7890", - CreativeMediaType: "banner", - } - bids = append(bids, &rbBannerBid) - rbVideoBid1 := pbs.PBSBid{ - BidID: "test_bidid3", - AdUnitCode: "test_adunitcode3", - BidderCode: "rubicon", - Price: 1.00, - Adm: "rb_video_test_adm1", - Width: 300, - Height: 250, - DealId: "7890", - CreativeMediaType: "video", - } - bids = append(bids, &rbVideoBid1) - rbVideoBid2 := pbs.PBSBid{ - BidID: "test_bidid4", - AdUnitCode: "test_adunitcode4", - BidderCode: "rubicon", - Price: 1.00, - Adm: "rb_video_test_adm2", - Width: 300, - Height: 250, - DealId: "7890", - CreativeMediaType: "video", - } - bids = append(bids, &rbVideoBid2) - - ctx := context.TODO() - v := viper.New() - config.SetupViper(v, "") - v.Set("gdpr.default_value", "0") - cfg, err := config.New(v) - if err != nil { - t.Fatal(err.Error()) - } - syncersByBidder := map[string]usersync.Syncer{} - gdprPerms := gdpr.NewPermissions(context.Background(), config.GDPR{ - HostVendorID: 0, - }, nil, nil) - prebid_cache_client.InitPrebidCache(server.URL) - var labels = &metrics.Labels{} - if err := cacheVideoOnly(bids, ctx, &auction{cfg: cfg, syncersByBidder: syncersByBidder, gdprPerms: gdprPerms, metricsEngine: &metricsConf.NilMetricsEngine{}}, labels); err != nil { - t.Errorf("Prebid cache failed: %v \n", err) - return - } - if bids[0].CacheID != "UUID-1" { - t.Errorf("UUID was '%s', should have been 'UUID-1'", bids[0].CacheID) - } - if bids[1].CacheID != "" { - t.Errorf("UUID was '%s', should have been empty", bids[1].CacheID) - } - if bids[2].CacheID != "" { - t.Errorf("UUID was '%s', should have been empty", bids[2].CacheID) - } - if bids[3].CacheID != "UUID-2" { - t.Errorf("First object UUID was '%s', should have been 'UUID-2'", bids[3].CacheID) - } - if bids[4].CacheID != "UUID-3" { - t.Errorf("Second object UUID was '%s', should have been 'UUID-3'", bids[4].CacheID) - } -} - -func TestShouldUsersync(t *testing.T) { - tests := []struct { - description string - signal string - allowHostCookies bool - allowBidderSync bool - wantAllow bool - }{ - { - description: "Don't sync - GDPR on, host cookies disallows and bidder sync disallows", - signal: "1", - allowHostCookies: false, - allowBidderSync: false, - wantAllow: false, - }, - { - description: "Don't sync - GDPR on, host cookies disallows and bidder sync allows", - signal: "1", - allowHostCookies: false, - allowBidderSync: true, - wantAllow: false, - }, - { - description: "Don't sync - GDPR on, host cookies allows and bidder sync disallows", - signal: "1", - allowHostCookies: true, - allowBidderSync: false, - wantAllow: false, - }, - { - description: "Sync - GDPR on, host cookies allows and bidder sync allows", - signal: "1", - allowHostCookies: true, - allowBidderSync: true, - wantAllow: true, - }, - { - description: "Don't sync - invalid GDPR signal, host cookies disallows and bidder sync disallows", - signal: "2", - allowHostCookies: false, - allowBidderSync: false, - wantAllow: false, - }, - } - - for _, tt := range tests { - deps := auction{ - gdprPerms: &auctionMockPermissions{ - allowBidderSync: tt.allowBidderSync, - allowHostCookies: tt.allowHostCookies, - }, - } - gdprPrivacyPolicy := gdprPolicy.Policy{ - Signal: tt.signal, - } - - allow := deps.shouldUsersync(context.Background(), openrtb_ext.BidderAdform, gdprPrivacyPolicy) - assert.Equal(t, tt.wantAllow, allow, tt.description) - } -} - -type auctionMockPermissions struct { - allowBidderSync bool - allowHostCookies bool - allowBidRequest bool - passGeo bool - passID bool -} - -func (m *auctionMockPermissions) HostCookiesAllowed(ctx context.Context, gdprSignal gdpr.Signal, consent string) (bool, error) { - return m.allowHostCookies, nil -} - -func (m *auctionMockPermissions) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, gdprSignal gdpr.Signal, consent string) (bool, error) { - return m.allowBidderSync, nil -} - -func (m *auctionMockPermissions) AuctionActivitiesAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string, weakVendorEnforcement bool) (allowBidRequest bool, passGeo bool, passID bool, err error) { - return m.allowBidRequest, m.passGeo, m.passID, nil -} - -func TestBidSizeValidate(t *testing.T) { - bids := make(pbs.PBSBidSlice, 0) - // bid1 will be rejected due to undefined size when adunit has multiple sizes - bid1 := pbs.PBSBid{ - BidID: "test_bidid1", - AdUnitCode: "test_adunitcode1", - BidderCode: "randNetwork", - Price: 1.05, - Adm: "test_adm", - // Width: 100, - // Height: 100, - CreativeMediaType: "banner", - } - bids = append(bids, &bid1) - // bid2 will be considered a normal ideal banner bid - bid2 := pbs.PBSBid{ - BidID: "test_bidid2", - AdUnitCode: "test_adunitcode2", - BidderCode: "randNetwork", - Price: 1.05, - Adm: "test_adm", - Width: 100, - Height: 100, - CreativeMediaType: "banner", - } - bids = append(bids, &bid2) - // bid3 will have it's dimensions set based on sizes defined in request - bid3 := pbs.PBSBid{ - BidID: "test_bidid3", - AdUnitCode: "test_adunitcode3", - BidderCode: "randNetwork", - Price: 1.05, - Adm: "test_adm", - //Width: 200, - //Height: 200, - CreativeMediaType: "banner", - } - - bids = append(bids, &bid3) - - // bid4 will be ignored as it's a video creative type - bid4 := pbs.PBSBid{ - BidID: "test_bidid_video", - AdUnitCode: "test_adunitcode_video", - BidderCode: "randNetwork", - Price: 1.05, - Adm: "test_adm", - //Width: 400, - //Height: 400, - CreativeMediaType: "video", - } - - bids = append(bids, &bid4) - - mybidder := pbs.PBSBidder{ - BidderCode: "randNetwork", - AdUnitCode: "test_adunitcode", - AdUnits: []pbs.PBSAdUnit{ - { - BidID: "test_bidid1", - Sizes: []openrtb2.Format{ - { - W: 350, - H: 250, - }, - { - W: 300, - H: 50, - }, - }, - Code: "test_adunitcode1", - MediaTypes: []pbs.MediaType{ - pbs.MEDIA_TYPE_BANNER, - }, - }, - { - BidID: "test_bidid2", - Sizes: []openrtb2.Format{ - { - W: 100, - H: 100, - }, - }, - Code: "test_adunitcode2", - MediaTypes: []pbs.MediaType{ - pbs.MEDIA_TYPE_BANNER, - }, - }, - { - BidID: "test_bidid3", - Sizes: []openrtb2.Format{ - { - W: 200, - H: 200, - }, - }, - Code: "test_adunitcode3", - MediaTypes: []pbs.MediaType{ - pbs.MEDIA_TYPE_BANNER, - }, - }, - { - BidID: "test_bidid_video", - Sizes: []openrtb2.Format{ - { - W: 400, - H: 400, - }, - }, - Code: "test_adunitcode_video", - MediaTypes: []pbs.MediaType{ - pbs.MEDIA_TYPE_VIDEO, - }, - }, - { - BidID: "test_bidid3", - Sizes: []openrtb2.Format{ - { - W: 150, - H: 150, - }, - }, - Code: "test_adunitcode_x", - MediaTypes: []pbs.MediaType{ - pbs.MEDIA_TYPE_BANNER, - }, - }, - { - BidID: "test_bidid_y", - Sizes: []openrtb2.Format{ - { - W: 150, - H: 150, - }, - }, - Code: "test_adunitcode_3", - MediaTypes: []pbs.MediaType{ - pbs.MEDIA_TYPE_BANNER, - }, - }, - }, - } - - bids = checkForValidBidSize(bids, &mybidder) - - testdata, _ := json.MarshalIndent(bids, "", " ") - if len(bids) != 3 { - t.Errorf("Detected returned bid list did not contain only 3 bid objects as expected.\nBelow is the contents of the bid list\n%v", string(testdata)) - } - - for _, bid := range bids { - if bid.BidID == "test_bidid3" { - if bid.Width == 0 && bid.Height == 0 { - t.Errorf("Detected the Width & Height attributes in test bidID %v were not set to the dimensions used from the mybidder object", bid.BidID) - } - } - } -} - -func TestWriteAuctionError(t *testing.T) { - recorder := httptest.NewRecorder() - writeAuctionError(recorder, "some error message", nil) - var resp pbs.PBSResponse - json.Unmarshal(recorder.Body.Bytes(), &resp) - - if len(resp.Bids) != 0 { - t.Error("Error responses should return no bids.") - } - if resp.Status != "some error message" { - t.Errorf("The response status should be the error message. Got: %s", resp.Status) - } - - if len(resp.BidderStatus) != 0 { - t.Errorf("Error responses shouldn't have any BidderStatus elements. Got %d", len(resp.BidderStatus)) - } -} - -func TestPanicRecovery(t *testing.T) { - testAuction := auction{ - cfg: nil, - syncersByBidder: nil, - gdprPerms: &auctionMockPermissions{ - allowBidderSync: false, - allowHostCookies: false, - }, - metricsEngine: &metricsConf.NilMetricsEngine{}, - } - panicker := func(bidder *pbs.PBSBidder, blables metrics.AdapterLabels) { - panic("panic!") - } - recovered := testAuction.recoverSafely(panicker) - recovered(nil, metrics.AdapterLabels{}) -} diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index 123b576aa4c..ff71770d671 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -313,8 +313,7 @@ func (deps *endpointDeps) parseAmpRequest(httpRequest *http.Request) (req *openr } // At this point, we should have a valid request that definitely has Targeting and Cache turned on - - e = deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: req}) + e = deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: req}, true) errs = append(errs, e...) return } diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go index f37c60f709e..3f5eecfa0ad 100644 --- a/endpoints/openrtb2/amp_auction_test.go +++ b/endpoints/openrtb2/amp_auction_test.go @@ -1235,7 +1235,7 @@ func TestBuildAmpObject(t *testing.T) { }, AT: 1, TMax: 500, - Ext: json.RawMessage(`{"prebid":{"cache":{"bids":{"returnCreative":null},"vastxml":null},"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":20,"increment":0.1}]},"includewinners":true,"includebidderkeys":true,"includebrandcategory":null,"includeformat":false,"durationrangesec":null,"preferdeals":false}}}`), + Ext: json.RawMessage(`{"prebid":{"cache":{"bids":{"returnCreative":null},"vastxml":null},"channel":{"name":"amp","version":""},"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":20,"increment":0.1}]},"includewinners":true,"includebidderkeys":true,"includebrandcategory":null,"includeformat":false,"durationrangesec":null,"preferdeals":false}}}`), }, AuctionResponse: &openrtb2.BidResponse{ SeatBid: []openrtb2.SeatBid{{ @@ -1330,7 +1330,6 @@ func TestIdGeneration(t *testing.T) { // Set up and run test actualAmpObject, endpoint := AmpObjectTestSetup(t, "test", test.givenInStoredRequest, test.givenGenerateRequestID) endpoint(recorder, request, nil) - assert.Equalf(t, test.expectedID, actualAmpObject.Request.ID, "Bid Request ID is incorrect: %s\n", test.description) } } diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 790a8d6482d..8e9725f37f2 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -13,6 +13,8 @@ import ( "strconv" "time" + "github.com/prebid/prebid-server/firstpartydata" + "github.com/buger/jsonparser" jsonpatch "github.com/evanphx/json-patch" "github.com/gofrs/uuid" @@ -22,8 +24,10 @@ import ( nativeRequests "github.com/mxmCherry/openrtb/v15/native1/request" "github.com/mxmCherry/openrtb/v15/openrtb2" accountService "github.com/prebid/prebid-server/account" + "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/analytics" "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/exchange" "github.com/prebid/prebid-server/metrics" @@ -38,10 +42,11 @@ import ( "github.com/prebid/prebid-server/util/iputil" "github.com/prebid/prebid-server/util/uuidutil" "golang.org/x/net/publicsuffix" - "golang.org/x/text/currency" ) const storedRequestTimeoutMillis = 50 +const ampChannel = "amp" +const appChannel = "app" var ( dntKey string = http.CanonicalHeaderKey("DNT") @@ -140,10 +145,17 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http }() req, impExtInfoMap, errL := deps.parseRequest(r) - if errortypes.ContainsFatalError(errL) && writeError(errL, w, &labels) { return } + + resolvedFPD, fpdErrors := firstpartydata.ExtractFPDForBidders(req) + if len(fpdErrors) > 0 { + if errortypes.ContainsFatalError(fpdErrors) && writeError(fpdErrors, w, &labels) { + return + } + errL = append(errL, fpdErrors...) + } warnings := errortypes.WarningOnly(errL) ctx := context.Background() @@ -197,6 +209,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http Warnings: warnings, GlobalPrivacyControlHeader: secGPC, ImpExtInfoMap: impExtInfoMap, + FirstPartyData: resolvedFPD, } response, err := deps.ex.HoldAuction(ctx, auctionRequest, nil) @@ -281,6 +294,11 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request) (req *openrtb_ return } + if err := mergeBidderParams(req); err != nil { + errs = []error{err} + return + } + // Populate any "missing" OpenRTB fields with info from other sources, (e.g. HTTP request headers). deps.setFieldsImplicitly(httpRequest, req.BidRequest) @@ -291,7 +309,7 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request) (req *openrtb_ lmt.ModifyForIOS(req.BidRequest) - errL := deps.validateRequest(req) + errL := deps.validateRequest(req, false) if len(errL) > 0 { errs = append(errs, errL...) } @@ -314,7 +332,118 @@ func parseTimeout(requestJson []byte, defaultTimeout time.Duration) time.Duratio return defaultTimeout } -func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper) []error { +// mergeBidderParams merges bidder parameters passed at req.ext level with imp[].ext level. +// Preference is given to parameters at imp[].ext level over req.ext level. +// Parameters at req.ext level are propagated to adapters as is without any validation. +func mergeBidderParams(req *openrtb_ext.RequestWrapper) error { + reqBidderParams, err := adapters.ExtractReqExtBidderParams(req.BidRequest) + if err != nil { + return err + } + + impCpy := make([]openrtb2.Imp, 0, len(req.BidRequest.Imp)) + for _, imp := range req.BidRequest.Imp { + updatedImp := imp + + if len(imp.Ext) == 0 { + impCpy = append(impCpy, updatedImp) + continue + } + + var impExt map[string]map[string]json.RawMessage + err := json.Unmarshal(imp.Ext, &impExt) + if err != nil { + return err + } + + //merges bidder parameters passed at req.ext level with imp[].ext level. + err = addMissingReqExtParamsInImpExt(impExt, reqBidderParams) + if err != nil { + return err + } + + //merges bidder parameters passed at req.ext level with imp[].ext.prebid.bidder level. + err = addMissingReqExtParamsInImpExtPrebid(impExt, reqBidderParams) + if err != nil { + return err + } + + iExt, err := json.Marshal(impExt) + if err != nil { + return fmt.Errorf("error marshalling imp[].ext : %s", err.Error()) + } + updatedImp.Ext = iExt + impCpy = append(impCpy, updatedImp) + } + + req.BidRequest.Imp = impCpy + return nil +} + +// addMissingReqExtParamsInImpExtPrebid merges bidder parameters passed at req.ext level with imp[].ext.prebid.bidder level. +func addMissingReqExtParamsInImpExtPrebid(impExtBidder map[string]map[string]json.RawMessage, reqExtParams map[string]map[string]json.RawMessage) error { + var bidderParams map[string]json.RawMessage + if impExtBidder["prebid"] != nil && impExtBidder["prebid"]["bidder"] != nil { + err := json.Unmarshal(impExtBidder["prebid"]["bidder"], &bidderParams) + if err != nil { + return err + } + } + + if len(bidderParams) != 0 { + for bidder, bidderExt := range bidderParams { + if !isBidderToValidate(bidder) { + continue + } + + var params map[string]json.RawMessage + err := json.Unmarshal(bidderExt, ¶ms) + + for key, value := range reqExtParams[bidder] { + if _, present := params[key]; !present { + params[key] = value + } + } + + paramsJson, err := json.Marshal(params) + if err != nil { + return err + } + bidderParams[bidder] = paramsJson + } + + bidderParamsJson, err := json.Marshal(bidderParams) + if err != nil { + return err + } + impExtBidder["prebid"]["bidder"] = bidderParamsJson + } + + return nil +} + +// addMissingReqExtParamsInImpExt merges bidder parameters passed at req.ext level with imp[].ext level. +func addMissingReqExtParamsInImpExt(impExtBidder map[string]map[string]json.RawMessage, reqExtParams map[string]map[string]json.RawMessage) error { + for bidder, bidderExt := range impExtBidder { + if !isBidderToValidate(bidder) { + continue + } + + wasModified := false + for key, value := range reqExtParams[bidder] { + if _, present := bidderExt[key]; !present { + bidderExt[key] = value + wasModified = true + } + } + if wasModified { + impExtBidder[bidder] = bidderExt + } + } + return nil +} + +func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp bool) []error { errL := []error{} if req.ID == "" { return []error{errors.New("request missing required field: \"id\"")} @@ -368,11 +497,15 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper) []err return []error{err} } - if err := validateCustomRates(reqPrebid.CurrencyConversions); err != nil { + if err := currency.ValidateCustomRates(reqPrebid.CurrencyConversions); err != nil { return []error{err} } } + if err := validateOrFillChannel(req, isAmp); err != nil { + return []error{err} + } + if (req.Site == nil && req.App == nil) || (req.Site != nil && req.App != nil) { return append(errL, errors.New("request.site or request.app must be defined, but not both.")) } @@ -466,30 +599,6 @@ func validateSChains(sChains []*openrtb_ext.ExtRequestPrebidSChain) error { return err } -// validateCustomRates throws a bad input error if any of the 3-digit currency codes found in -// the bidRequest.ext.prebid.currency field is invalid, malfomed or does not represent any actual -// currency. No error is thrown if bidRequest.ext.prebid.currency is invalid or empty. -func validateCustomRates(bidReqCurrencyRates *openrtb_ext.ExtRequestCurrency) error { - if bidReqCurrencyRates == nil { - return nil - } - - for fromCurrency, rates := range bidReqCurrencyRates.ConversionRates { - // Check if fromCurrency is a valid 3-letter currency code - if _, err := currency.ParseISO(fromCurrency); err != nil { - return &errortypes.BadInput{Message: fmt.Sprintf("currency code %s is not recognized or malformed", fromCurrency)} - } - - // Check if currencies mapped to fromCurrency are valid 3-letter currency codes - for toCurrency := range rates { - if _, err := currency.ParseISO(toCurrency); err != nil { - return &errortypes.BadInput{Message: fmt.Sprintf("currency code %s is not recognized or malformed", toCurrency)} - } - } - } - return nil -} - func (deps *endpointDeps) validateEidPermissions(prebid *openrtb_ext.ExtRequestPrebidData, aliases map[string]string) error { if prebid == nil { return nil @@ -1068,6 +1177,8 @@ func isBidderToValidate(bidder string) bool { return false case openrtb_ext.BidderReservedSKAdN: return false + case openrtb_ext.BidderReservedBidder: + return false default: return true } @@ -1230,6 +1341,46 @@ func validateDevice(device *openrtb2.Device) error { return nil } +func validateOrFillChannel(reqWrapper *openrtb_ext.RequestWrapper, isAmp bool) error { + requestExt, err := reqWrapper.GetRequestExt() + if err != nil { + return err + } + requestPrebid := requestExt.GetPrebid() + + if requestPrebid == nil || requestPrebid.Channel == nil { + fillChannel(reqWrapper, isAmp) + } else if requestPrebid.Channel.Name == "" { + return errors.New("ext.prebid.channel.name can't be empty") + } + return nil +} + +func fillChannel(reqWrapper *openrtb_ext.RequestWrapper, isAmp bool) error { + var channelName string + requestExt, err := reqWrapper.GetRequestExt() + if err != nil { + return err + } + requestPrebid := requestExt.GetPrebid() + if isAmp { + channelName = ampChannel + } + if reqWrapper.App != nil { + channelName = appChannel + } + if channelName != "" { + if requestPrebid == nil { + requestPrebid = &openrtb_ext.ExtRequestPrebid{} + } + requestPrebid.Channel = &openrtb_ext.ExtRequestPrebidChannel{Name: channelName} + requestExt.SetPrebid(requestPrebid) + reqWrapper.RebuildRequest() + } + return nil + +} + func sanitizeRequest(r *openrtb2.BidRequest, ipValidator iputil.IPValidator) { if r.Device != nil { if ip, ver := iputil.ParseIP(r.Device.IP); ip == nil || ver != iputil.IPv4 || !ipValidator.IsValid(ip, ver) { @@ -1368,12 +1519,12 @@ func (deps *endpointDeps) processStoredRequests(ctx context.Context, requestJson // Apply the Stored BidRequest, if it exists resolvedRequest := requestJson - if deps.cfg.GenerateRequestID || bidRequestID == "{{UUID}}" { + if hasStoredBidRequest { isAppRequest, err := checkIfAppRequest(requestJson) if err != nil { return nil, nil, []error{err} } - if isAppRequest && hasStoredBidRequest { + if isAppRequest && (deps.cfg.GenerateRequestID || bidRequestID == "{{UUID}}") { uuidPatch, err := generateUuidForBidRequest(deps.uuidGenerator) if err != nil { return nil, nil, []error{err} @@ -1388,12 +1539,12 @@ func (deps *endpointDeps) processStoredRequests(ctx context.Context, requestJson errL := storedRequestErrorChecker(requestJson, storedRequests, storedBidRequestId) return nil, nil, errL } - } - } else if hasStoredBidRequest { - resolvedRequest, err = jsonpatch.MergePatch(storedRequests[storedBidRequestId], requestJson) - if err != nil { - errL := storedRequestErrorChecker(requestJson, storedRequests, storedBidRequestId) - return nil, nil, errL + } else { + resolvedRequest, err = jsonpatch.MergePatch(storedRequests[storedBidRequestId], requestJson) + if err != nil { + errL := storedRequestErrorChecker(requestJson, storedRequests, storedBidRequestId) + return nil, nil, errL + } } } @@ -1572,15 +1723,6 @@ func setDoNotTrackImplicitly(httpReq *http.Request, bidReq *openrtb2.BidRequest) } } -// parseUserID gets this user's ID for the host machine, if it exists. -func parseUserID(cfg *config.Configuration, httpReq *http.Request) (string, bool) { - if hostCookie, err := httpReq.Cookie(cfg.HostCookie.CookieName); hostCookie != nil && err == nil { - return hostCookie.Value, true - } else { - return "", false - } -} - // Write(return) errors to the client, if any. Returns true if errors were found. func writeError(errs []error, w http.ResponseWriter, labels *metrics.Labels) bool { var rc bool = false diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index a259719ba8a..af68d693243 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -17,6 +17,8 @@ import ( "testing" "time" + "github.com/prebid/prebid-server/firstpartydata" + analyticsConf "github.com/prebid/prebid-server/analytics/config" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" @@ -25,7 +27,6 @@ import ( "github.com/prebid/prebid-server/metrics" metricsConfig "github.com/prebid/prebid-server/metrics/config" "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/stored_requests" "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" "github.com/prebid/prebid-server/util/iputil" @@ -105,10 +106,6 @@ func TestJsonSampleRequests(t *testing.T) { "There are both disabled and non-disabled bidders, we expect a 200", "disabled/good", }, - { - "Requests with first party data context info found in imp[i].ext.prebid.bidder,context", - "first-party-data", - }, { "Assert we correctly use the server conversion rates when needed", "currency-conversion/server-rates/valid", @@ -1149,6 +1146,246 @@ func TestStoredRequests(t *testing.T) { } } +func TestValidateRequest(t *testing.T) { + deps := &endpointDeps{ + fakeUUIDGenerator{}, + &nobidExchange{}, + newParamsValidator(t), + &mockStoredReqFetcher{}, + empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, + &config.Configuration{MaxRequestSize: maxSize}, + &metricsConfig.NilMetricsEngine{}, + analyticsConf.NewPBSAnalytics(&config.Analytics{}), + map[string]string{}, + false, + []byte{}, + openrtb_ext.BuildBidderMap(), + nil, + nil, + hardcodedResponseIPValidator{response: true}, + } + + testCases := []struct { + description string + givenIsAmp bool + givenRequestWrapper *openrtb_ext.RequestWrapper + expectedErrorList []error + expectedChannelObject *openrtb_ext.ExtRequestPrebidChannel + }{ + { + description: "No errors in bid request with request.ext.prebid.channel info, expect validate request to throw no errors", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "Some-ID", + App: &openrtb2.App{}, + Imp: []openrtb2.Imp{ + { + ID: "Some-Imp-ID", + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ + { + W: 600, + H: 500, + }, + { + W: 300, + H: 600, + }, + }, + }, + Ext: []byte(`{"appnexus":{"placementId": 12345678}}`), + }, + }, + Ext: []byte(`{"prebid":{"channel": {"name": "nameOfChannel", "version": "1.0"}}}`), + }, + }, + givenIsAmp: false, + expectedErrorList: []error{}, + expectedChannelObject: &openrtb_ext.ExtRequestPrebidChannel{Name: "nameOfChannel", Version: "1.0"}, + }, + { + description: "Error in bid request with request.ext.prebid.channel.name being blank, expect validate request to return error", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "Some-ID", + App: &openrtb2.App{}, + Imp: []openrtb2.Imp{ + { + ID: "Some-Imp-ID", + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ + { + W: 600, + H: 500, + }, + { + W: 300, + H: 600, + }, + }, + }, + Ext: []byte(`{"appnexus":{"placementId": 12345678}}`), + }, + }, + Ext: []byte(`{"prebid":{"channel": {"name": "", "version": ""}}}`), + }, + }, + givenIsAmp: false, + expectedErrorList: []error{errors.New("ext.prebid.channel.name can't be empty")}, + }, + { + description: "No errors in bid request with request.ext.prebid but no channel info, expect validate request to throw no errors and fill channel with app", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "Some-ID", + App: &openrtb2.App{}, + Imp: []openrtb2.Imp{ + { + ID: "Some-Imp-ID", + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ + { + W: 600, + H: 500, + }, + { + W: 300, + H: 600, + }, + }, + }, + Ext: []byte(`{"appnexus":{"placementId": 12345678}}`), + }, + }, + Ext: []byte(`{"prebid":{}}`), + }, + }, + givenIsAmp: false, + expectedErrorList: []error{}, + expectedChannelObject: &openrtb_ext.ExtRequestPrebidChannel{Name: appChannel, Version: ""}, + }, + } + + for _, test := range testCases { + errorList := deps.validateRequest(test.givenRequestWrapper, test.givenIsAmp) + assert.Equalf(t, test.expectedErrorList, errorList, "Error doesn't match: %s\n", test.description) + + if len(errorList) == 0 { + requestExt, err := test.givenRequestWrapper.GetRequestExt() + assert.Empty(t, err, test.description) + requestPrebid := requestExt.GetPrebid() + + assert.Equalf(t, test.expectedChannelObject, requestPrebid.Channel, "Channel information isn't correct: %s\n", test.description) + } + } +} + +func TestValidateOrFillChannel(t *testing.T) { + testCases := []struct { + description string + givenIsAmp bool + givenRequestWrapper *openrtb_ext.RequestWrapper + expectedError error + expectedChannelObject *openrtb_ext.ExtRequestPrebidChannel + }{ + { + description: "No request.ext info in app request, so we expect channel name to be set to app", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{App: &openrtb2.App{}}, + }, + givenIsAmp: false, + expectedError: nil, + expectedChannelObject: &openrtb_ext.ExtRequestPrebidChannel{Name: appChannel, Version: ""}, + }, + { + description: "No request.ext info in amp request, so we expect channel name to be set to amp", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + givenIsAmp: true, + expectedError: nil, + expectedChannelObject: &openrtb_ext.ExtRequestPrebidChannel{Name: ampChannel, Version: ""}, + }, + { + description: "Channel object in request with populated name/version, we expect same name/version in object that's created", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"channel": {"name": "video", "version": "1.0"}}}`)}, + }, + givenIsAmp: false, + expectedError: nil, + expectedChannelObject: &openrtb_ext.ExtRequestPrebidChannel{Name: "video", Version: "1.0"}, + }, + { + description: "No channel object in site request, expect nil", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{Site: &openrtb2.Site{}, Ext: []byte(`{"prebid":{}}`)}, + }, + givenIsAmp: false, + expectedError: nil, + expectedChannelObject: nil, + }, + { + description: "No channel name given in channel object, we expect error to be thrown", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{App: &openrtb2.App{}, Ext: []byte(`{"prebid":{"channel": {"name": "", "version": ""}}}`)}, + }, + givenIsAmp: false, + expectedError: errors.New("ext.prebid.channel.name can't be empty"), + expectedChannelObject: nil, + }, + { + description: "App request, has request.ext, no request.ext.prebid, expect channel name to be filled with app", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{App: &openrtb2.App{}, Ext: []byte(`{}`)}, + }, + givenIsAmp: false, + expectedError: nil, + expectedChannelObject: &openrtb_ext.ExtRequestPrebidChannel{Name: appChannel, Version: ""}, + }, + { + description: "App request, has request.ext.prebid, but no channel object, expect channel name to be filled with app", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{App: &openrtb2.App{}, Ext: []byte(`{"prebid":{}}`)}, + }, + givenIsAmp: false, + expectedError: nil, + expectedChannelObject: &openrtb_ext.ExtRequestPrebidChannel{Name: appChannel, Version: ""}, + }, + { + description: "Amp request, has request.ext, no request.ext.prebid, expect channel name to be filled with amp", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{Ext: []byte(`{}`)}, + }, + givenIsAmp: true, + expectedError: nil, + expectedChannelObject: &openrtb_ext.ExtRequestPrebidChannel{Name: ampChannel, Version: ""}, + }, + { + description: "Amp request, has request.ext.prebid, but no channel object, expect channel name to be filled with amp", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{}}`)}, + }, + givenIsAmp: true, + expectedError: nil, + expectedChannelObject: &openrtb_ext.ExtRequestPrebidChannel{Name: ampChannel, Version: ""}, + }, + } + + for _, test := range testCases { + err := validateOrFillChannel(test.givenRequestWrapper, test.givenIsAmp) + assert.Equalf(t, test.expectedError, err, "Error doesn't match: %s\n", test.description) + + if err == nil { + requestExt, err := test.givenRequestWrapper.GetRequestExt() + assert.Empty(t, err, test.description) + requestPrebid := requestExt.GetPrebid() + + assert.Equalf(t, test.expectedChannelObject, requestPrebid.Channel, "Channel information isn't correct: %s\n", test.description) + } + } +} + func TestStoredRequestGenerateUuid(t *testing.T) { uuid := "foo" @@ -1178,6 +1415,7 @@ func TestStoredRequestGenerateUuid(t *testing.T) { givenRawData string givenGenerateRequestID bool expectedID string + expectedCur string }{ { description: "GenerateRequestID is true, rawData is an app request and has stored bid request we should generate uuid", @@ -1221,6 +1459,20 @@ func TestStoredRequestGenerateUuid(t *testing.T) { givenGenerateRequestID: false, expectedID: "ThisID", }, + { + description: "Test to check that stored requests are being merged when Macro ID is present with a site rquest", + givenRawData: testBidRequests[5], + givenGenerateRequestID: false, + expectedID: "ThisID", + expectedCur: "USD", + }, + { + description: "Test to check that stored requests are being merged when Generate Request ID flag with a site rquest", + givenRawData: testBidRequests[5], + givenGenerateRequestID: true, + expectedID: "ThisID", + expectedCur: "USD", + }, } for _, test := range testCases { @@ -1233,6 +1485,9 @@ func TestStoredRequestGenerateUuid(t *testing.T) { if err := json.Unmarshal(newRequest, req); err != nil { t.Errorf("processStoredRequests Error: %s", err.Error()) } + if test.expectedCur != "" { + assert.Equalf(t, test.expectedCur, req.Cur[0], "The stored request wasn't merged properly: %s\n", test.description) + } assert.Equalf(t, test.expectedID, req.ID, "The Bid Request ID is incorrect: %s\n", test.description) } } @@ -1409,113 +1664,6 @@ func TestContentType(t *testing.T) { } } -func TestValidateCustomRates(t *testing.T) { - boolTrue := true - boolFalse := false - - testCases := []struct { - desc string - inBidReqCurrencies *openrtb_ext.ExtRequestCurrency - outCurrencyError error - }{ - { - desc: "nil input, no errors expected", - inBidReqCurrencies: nil, - outCurrencyError: nil, - }, - { - desc: "empty custom currency rates but UsePBSRates is set to false, we don't return error nor warning", - inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{ - ConversionRates: map[string]map[string]float64{}, - UsePBSRates: &boolFalse, - }, - outCurrencyError: nil, - }, - { - desc: "empty custom currency rates but UsePBSRates is set to true, no need to return error because we can use PBS rates", - inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{ - ConversionRates: map[string]map[string]float64{}, - UsePBSRates: &boolTrue, - }, - outCurrencyError: nil, - }, - { - desc: "UsePBSRates is nil and defaults to true, bidExt fromCurrency is invalid, expect bad input error", - inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{ - ConversionRates: map[string]map[string]float64{ - "FOO": { - "GBP": 1.2, - "MXN": 0.05, - "JPY": 0.95, - }, - }, - }, - outCurrencyError: &errortypes.BadInput{Message: "currency code FOO is not recognized or malformed"}, - }, - { - desc: "UsePBSRates set to false, bidExt fromCurrency is invalid, expect bad input error", - inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{ - ConversionRates: map[string]map[string]float64{ - "FOO": { - "GBP": 1.2, - "MXN": 0.05, - "JPY": 0.95, - }, - }, - UsePBSRates: &boolFalse, - }, - outCurrencyError: &errortypes.BadInput{Message: "currency code FOO is not recognized or malformed"}, - }, - { - desc: "UsePBSRates set to false, some of the bidExt 'to' Currencies are invalid, expect bad input error when parsing the first invalid currency code", - inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{ - ConversionRates: map[string]map[string]float64{ - "USD": { - "FOO": 10.0, - "MXN": 0.05, - }, - }, - UsePBSRates: &boolFalse, - }, - outCurrencyError: &errortypes.BadInput{Message: "currency code FOO is not recognized or malformed"}, - }, - { - desc: "UsePBSRates set to false, some of the bidExt 'from' and 'to' currencies are invalid, expect bad input error when parsing the first invalid currency code", - inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{ - ConversionRates: map[string]map[string]float64{ - "FOO": { - "MXN": 0.05, - "CAD": 0.95, - }, - }, - UsePBSRates: &boolFalse, - }, - outCurrencyError: &errortypes.BadInput{Message: "currency code FOO is not recognized or malformed"}, - }, - { - desc: "All 3-digit currency codes exist, expect no error", - inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{ - ConversionRates: map[string]map[string]float64{ - "USD": { - "MXN": 0.05, - }, - "MXN": { - "JPY": 10.0, - "EUR": 10.95, - }, - }, - UsePBSRates: &boolFalse, - }, - }, - } - - for _, tc := range testCases { - actualErr := validateCustomRates(tc.inBidReqCurrencies) - - assert.Equal(t, tc.outCurrencyError, actualErr, tc.desc) - } -} - func TestValidateImpExt(t *testing.T) { type testCase struct { description string @@ -1792,7 +1940,7 @@ func TestCurrencyTrunc(t *testing.T) { Cur: []string{"USD", "EUR"}, } - errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}) + errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}, false) expectedError := errortypes.Warning{Message: "A prebid request can only process one currency. Taking the first currency in the list, USD, as the active currency"} assert.ElementsMatch(t, errL, []error{&expectedError}) @@ -1839,7 +1987,7 @@ func TestCCPAInvalid(t *testing.T) { }, } - errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}) + errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}, false) expectedWarning := errortypes.Warning{ Message: "CCPA consent is invalid and will be ignored. (request.regs.ext.us_privacy must contain 4 characters)", @@ -1889,7 +2037,7 @@ func TestNoSaleInvalid(t *testing.T) { Ext: json.RawMessage(`{"prebid": {"nosale": ["*", "appnexus"]} }`), } - errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}) + errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}, false) expectedError := errors.New("request.ext.prebid.nosale is invalid: can only specify all bidders if no other bidders are provided") assert.ElementsMatch(t, errL, []error{expectedError}) @@ -1937,7 +2085,7 @@ func TestValidateSourceTID(t *testing.T) { }, } - deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}) + deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}, false) assert.NotEmpty(t, req.Source.TID, "Expected req.Source.TID to be filled with a randomly generated UID") } @@ -1980,7 +2128,7 @@ func TestSChainInvalid(t *testing.T) { Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}, {"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":1}],"ver":"1.0"}}]}}`), } - errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}) + errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}, false) expectedError := errors.New("request.ext.prebid.schains contains multiple schains for bidder appnexus; it must contain no more than one per bidder.") assert.ElementsMatch(t, errL, []error{expectedError}) @@ -2200,7 +2348,7 @@ func TestEidPermissionsInvalid(t *testing.T) { Ext: json.RawMessage(`{"prebid": {"data": {"eidpermissions": [{"source":"a", "bidders":[]}]} } }`), } - errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}) + errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}, false) expectedError := errors.New(`request.ext.prebid.data.eidpermissions[0] missing or empty required field: "bidders"`) assert.ElementsMatch(t, errL, []error{expectedError}) @@ -2511,6 +2659,56 @@ func TestParseRequestParseImpInfoError(t *testing.T) { assert.Contains(t, errL[0].Error(), "echovideoattrs of type bool", "Incorrect error message") } +func TestAuctionFirstPartyData(t *testing.T) { + reqBody := validRequest(t, "first-party-data.json") + deps := &endpointDeps{ + fakeUUIDGenerator{}, + &mockExchangeFPD{}, + newParamsValidator(t), + &mockStoredReqFetcher{}, + empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, + &config.Configuration{MaxRequestSize: int64(len(reqBody))}, + &metricsConfig.NilMetricsEngine{}, + analyticsConf.NewPBSAnalytics(&config.Analytics{}), + map[string]string{}, + false, + []byte{}, + openrtb_ext.BuildBidderMap(), + nil, + nil, + hardcodedResponseIPValidator{response: true}, + } + + req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody)) + recorder := httptest.NewRecorder() + + deps.Auction(recorder, req, nil) + + if recorder.Code != http.StatusOK { + t.Errorf("Endpoint should return a 200") + } + resultRequest := deps.ex.(*mockExchangeFPD).lastRequest + resultFPD := deps.ex.(*mockExchangeFPD).firstPartyData + + assert.Len(t, resultFPD, 2, "Result FPD length is incorrect") + + assert.NotNil(t, resultFPD[openrtb_ext.BidderName("bidder1")], "Result FPD for bidder1 is incorrect") + assert.NotNil(t, resultFPD[openrtb_ext.BidderName("bidder1")].Site, "Result FPD for bidder1.Site is incorrect") + assert.Nil(t, resultFPD[openrtb_ext.BidderName("bidder1")].App, "Result FPD for bidder1.App is incorrect") + assert.NotNil(t, resultFPD[openrtb_ext.BidderName("bidder1")].User, "Result FPD for bidder1.User is incorrect") + + assert.NotNil(t, resultFPD[openrtb_ext.BidderName("bidder2")], "Result FPD for bidder2 is incorrect") + assert.NotNil(t, resultFPD[openrtb_ext.BidderName("bidder2")].Site, "Result FPD for bidder2.Site is incorrect") + assert.Nil(t, resultFPD[openrtb_ext.BidderName("bidder2")].App, "Result FPD for bidder2.App is incorrect") + assert.NotNil(t, resultFPD[openrtb_ext.BidderName("bidder2")].User, "Result FPD for bidder2.User is incorrect") + + assert.Nil(t, resultRequest.App, "Result request App should be nil") + assert.Nil(t, resultRequest.Site.Content.Data, "Result request Site.Content.Data is incorrect") + assert.JSONEq(t, string(resultRequest.Site.Ext), `{"amp": 1}`, "Result request Site.Ext is incorrect") + assert.Nil(t, resultRequest.User.Ext, "Result request User.Ext is incorrect") +} + func TestValidateNativeContextTypes(t *testing.T) { impIndex := 4 @@ -3139,6 +3337,7 @@ var testStoredRequestData = map[string]json.RawMessage{ } }} }`), + "4": json.RawMessage(`{"id": "{{UUID}}", "cur": ["USD"]}`), } // Stored Imp Requests @@ -3627,9 +3826,6 @@ var testBidRequests = []string{ ], "ext": { "prebid": { - "cache": { - "markup": 1 - }, "targeting": { } } @@ -3716,6 +3912,39 @@ var testBidRequests = []string{ } } }`, + `{ + "id": "ThisID", + "imp": [{ + "id": "some-impression-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + }], + "ext": { + "prebid": { + "debug": true, + "storedrequest": { + "id": "4" + } + } + }, + "site": { + "page": "https://example.com" + } + }`, } type mockStoredReqFetcher struct { @@ -3725,21 +3954,6 @@ func (cf mockStoredReqFetcher) FetchRequests(ctx context.Context, requestIDs []s return testStoredRequestData, testStoredImpData, nil } -var mockAccountData = map[string]json.RawMessage{ - "valid_acct": json.RawMessage(`{"disabled":false}`), -} - -type mockAccountFetcher struct { -} - -func (af mockAccountFetcher) FetchAccount(ctx context.Context, accountID string) (json.RawMessage, []error) { - if account, ok := mockAccountData[accountID]; ok { - return account, nil - } else { - return nil, []error{stored_requests.NotFoundError{accountID, "Account"}} - } -} - type mockExchange struct { lastRequest *openrtb2.BidRequest } @@ -3755,6 +3969,17 @@ func (m *mockExchange) HoldAuction(ctx context.Context, r exchange.AuctionReques }, nil } +type mockExchangeFPD struct { + lastRequest *openrtb2.BidRequest + firstPartyData map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData +} + +func (m *mockExchangeFPD) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) { + m.lastRequest = r.BidRequest + m.firstPartyData = r.FirstPartyData + return &openrtb2.BidResponse{}, nil +} + func getBidderInfos(cfg map[string]config.Adapter, biddersNames []openrtb_ext.BidderName) config.BidderInfos { biddersInfos := make(config.BidderInfos) for _, name := range biddersNames { @@ -3821,3 +4046,117 @@ func TestValidateBanner(t *testing.T) { assert.Equal(t, test.expectedError, result, test.description) } } + +func TestParseRequestMergeBidderParams(t *testing.T) { + type args struct { + reqBody string + } + tests := []struct { + name string + args args + expectedImpExt json.RawMessage + expectedReqExt json.RawMessage + errL int + }{ + { + name: "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext.prebid.bidder", + args: args{ + reqBody: validRequest(t, "req-ext-bidder-params.json"), + }, + expectedImpExt: getObject(t, "req-ext-bidder-params.json", "expectedImpExt"), + expectedReqExt: getObject(t, "req-ext-bidder-params.json", "expectedReqExt"), + errL: 0, + }, + { + name: "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext.prebid.bidder with preference for imp[].ext.prebid.bidder params", + args: args{ + reqBody: validRequest(t, "req-ext-bidder-params-merge.json"), + }, + expectedImpExt: getObject(t, "req-ext-bidder-params-merge.json", "expectedImpExt"), + expectedReqExt: getObject(t, "req-ext-bidder-params-merge.json", "expectedReqExt"), + errL: 0, + }, + { + name: "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext for backward compatibility", + args: args{ + reqBody: validRequest(t, "req-ext-bidder-params-backward-compatible-merge.json"), + }, + expectedImpExt: getObject(t, "req-ext-bidder-params-backward-compatible-merge.json", "expectedImpExt"), + expectedReqExt: getObject(t, "req-ext-bidder-params-backward-compatible-merge.json", "expectedReqExt"), + errL: 0, + }, + { + name: "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext.prebid.bidder for reserved keyword", + args: args{ + reqBody: validRequest(t, "req-ext-bidder-params-merge-reserved-keyword.json"), + }, + expectedImpExt: getObject(t, "req-ext-bidder-params-merge-reserved-keyword.json", "expectedImpExt"), + expectedReqExt: getObject(t, "req-ext-bidder-params-merge-reserved-keyword.json", "expectedReqExt"), + errL: 0, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + deps := &endpointDeps{ + fakeUUIDGenerator{}, + &warningsCheckExchange{}, + newParamsValidator(t), + &mockStoredReqFetcher{}, + empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, + &config.Configuration{MaxRequestSize: int64(len(tt.args.reqBody))}, + &metricsConfig.NilMetricsEngine{}, + analyticsConf.NewPBSAnalytics(&config.Analytics{}), + map[string]string{}, + false, + []byte{}, + openrtb_ext.BuildBidderMap(), + nil, + nil, + hardcodedResponseIPValidator{response: true}, + } + + req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(tt.args.reqBody)) + + resReq, _, errL := deps.parseRequest(req) + + var expIExt, iExt map[string]interface{} + err := json.Unmarshal(tt.expectedImpExt, &expIExt) + assert.Nil(t, err, "unmarshal() should return nil error") + + assert.NotNil(t, resReq.BidRequest.Imp[0].Ext, "imp[0].Ext should not be nil") + err = json.Unmarshal(resReq.BidRequest.Imp[0].Ext, &iExt) + assert.Nil(t, err, "unmarshal() should return nil error") + + assert.Equal(t, expIExt, iExt, "bidderparams in imp[].Ext should match") + + var eReqE, reqE map[string]interface{} + err = json.Unmarshal(tt.expectedReqExt, &eReqE) + assert.Nil(t, err, "unmarshal() should return nil error") + + err = json.Unmarshal(resReq.BidRequest.Ext, &reqE) + assert.Nil(t, err, "unmarshal() should return nil error") + + assert.Equal(t, eReqE, reqE, "req.Ext should match") + + assert.Len(t, errL, tt.errL, "error length should match") + }) + } +} + +func getObject(t *testing.T, filename, key string) json.RawMessage { + requestData, err := ioutil.ReadFile("sample-requests/valid-whole/supplementary/" + filename) + if err != nil { + t.Fatalf("Failed to fetch a valid request: %v", err) + } + testBidRequest, _, _, err := jsonparser.Get(requestData, key) + assert.NoError(t, err, "Error jsonparsing root.mockBidRequest from file %s. Desc: %v.", filename, err) + + var obj json.RawMessage + err = json.Unmarshal(testBidRequest, &obj) + if err != nil { + t.Fatalf("Failed to fetch object with key '%s' ... got error: %v", key, err) + } + return obj +} diff --git a/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-ext-bidder.json b/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-ext-bidder.json deleted file mode 100644 index a4b716b2040..00000000000 --- a/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-ext-bidder.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "description": "The imp.ext.context field is valid for First Party Data and should be exempted from bidder name validation.", - - "mockBidRequest": { - "id": "some-request-id", - "site": { - "page": "test.somepage.com" - }, - "imp": [{ - "id": "some-imp-id", - "banner": { - "format": [{ - "w": 600, - "h": 500 - }, { - "w": 300, - "h": 600 - }] - }, - "ext": { - "appnexus": { - "placementId": 12883451 - }, - "data": { - "keywords": "prebid server example" - }, - "context": { - "data": { - "keywords": "another prebid server example" - } - } - } - }] - }, - "expectedBidResponse": { - "id": "some-request-id", - "seatbid": [{ - "bid": [{ - "id": "appnexus-bid", - "impid": "", - "price": 0 - }], - "seat": "appnexus-bids" - }], - "bidid": "test bid id", - "cur": "USD", - "nbr": 0 - }, - "expectedReturnCode": 200 -} diff --git a/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-prebid-bidder.json b/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-prebid-bidder.json deleted file mode 100644 index 27e8c46d9d7..00000000000 --- a/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-prebid-bidder.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "description": "The imp.ext.context field is valid for First Party Data and should be exempted from bidder name validation.", - - "mockBidRequest": { - "id": "some-request-id", - "site": { - "page": "test.somepage.com" - }, - "imp": [{ - "id": "some-imp-id", - "banner": { - "format": [{ - "w": 600, - "h": 500 - }, { - "w": 300, - "h": 600 - }] - }, - "ext": { - "prebid": { - "bidder": { - "appnexus": { - "placementId": 12883451 - } - } - }, - "data": { - "keywords": "prebid server example" - }, - "context": { - "data": { - "keywords": "another prebid server example" - } - } - } - }] - }, - "expectedBidResponse": { - "id": "some-request-id", - "seatbid": [{ - "bid": [{ - "id": "appnexus-bid", - "impid": "", - "price": 0 - }], - "seat": "appnexus-bids" - }], - "bidid": "test bid id", - "cur": "USD", - "nbr": 0 - }, - "expectedReturnCode": 200 -} diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/cache-nothing.json b/endpoints/openrtb2/sample-requests/invalid-whole/cache-nothing.json index 76dc1c2bb5c..5baaa09d46c 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/cache-nothing.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/cache-nothing.json @@ -27,5 +27,5 @@ } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.ext is invalid: request.ext.prebid.cache requires one of the \"bids\" or \"vastxml\" properties\n" + "expectedErrorMessage": "Invalid request: error decoding Request.ext : request.ext.prebid.cache requires one of the \"bids\" or \"vastxml\" properties\n" } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-invalid-params.json b/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-invalid-params.json index d9ed59c8c6e..b1e9b7849e2 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-invalid-params.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-invalid-params.json @@ -20,5 +20,5 @@ } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.imp[0].ext.appnexus failed validation.\n(root): Invalid type. Expected: object, given: string\n" + "expectedErrorMessage": "Invalid request: json: cannot unmarshal string into Go value of type map[string]json.RawMessage" } diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json index 46af51635f9..fb29efee966 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json @@ -65,6 +65,10 @@ "cache": { "bids": {} }, + "channel": { + "name": "video", + "version": "1.0" + }, "targeting": { "includewinners": false, "pricegranularity": { diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/first-party-data.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/first-party-data.json new file mode 100644 index 00000000000..4552b883b10 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/first-party-data.json @@ -0,0 +1,156 @@ +{ + "description": "Valid request with First party data for two bidders, both have global and bidder specific fpd", + "mockBidRequest": { + "id": "bid_req_id", + "imp": [ + { + "id": "1_0", + "video": { + "mimes": [ + "video/mp4" + ], + "maxduration": 30, + "protocols": [ + 2, + 3 + ], + "w": 640, + "h": 480 + }, + "ext": { + "appnexus": { + "placementId": 123 + }, + "telaria": { + "seatCode": "test", + "adCode": "test" + } + } + } + ], + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900, + "data": [ + { + "id": "reqContentDataSiteId1", + "name": "reqContentDataSiteName1" + } + ] + }, + "ext": { + "amp": 1, + "data": { + "somesitefpd": "fpdSite" + } + } + }, + "user": { + "id": "reqUserID", + "keywords": "userKeyword", + "yob": 1982, + "gender": "M", + "data": [ + { + "id": "reqDataUserId1", + "name": "reqDataUserName1" + }, + { + "id": "reqDataUserId2", + "name": "reqDataUserName2" + } + ], + "ext": { + "data": { + "moreuserdata": "morefpduserdata" + } + } + }, + "test": 1, + "at": 1, + "tmax": 5000, + "ext": { + "prebid": { + "data": { + "bidders": [ + "bidder1", + "bidder2" + ] + }, + "bidderconfig": [ + { + "bidders": [ + "bidder1" + ], + "config": { + "ortb2": { + "site": { + "id": "fpdSiteId", + "keywords": "fpd keywords!", + "data": [ + { + "id": "siteId1", + "name": true + }, + { + "id": "siteId2", + "name": false + } + ], + "page": "", + "ext": { + "data": { + "morefpdData": "morefpddata", + "sitefpddata": "mysitefpddata" + } + } + } + } + } + }, + { + "bidders": [ + "bidder2" + ], + "config": { + "ortb2": { + "user": { + "id": "fpdUserId", + "yob": 2011, + "gender": "F", + "keywords": "fpd keywords", + "data": [ + { + "id": "fpdUserDataId1", + "name": "fpdUserDataName1" + }, + { + "id": "fpdUserDataId2", + "name": "fpdUserDataName2" + } + ], + "ext": { + "data": { + "userdata": "biddermyfpduserdata" + } + } + } + } + } + } + ] + } + } + }, + "expectedBidResponse": {}, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-backward-compatible-merge.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-backward-compatible-merge.json new file mode 100644 index 00000000000..274c2c70f4b --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-backward-compatible-merge.json @@ -0,0 +1,101 @@ +{ + "description": "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext for backward compatibility", + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "pmp": { + "deals": [ + { + "id": "some-deal-id" + } + ] + }, + "ext": { + "appnexus": { + "placementId": 111111 + }, + "pubmatic": { + "wrapper": { + "profile": 1234 + } + }, + "prebid": { + "bidder": {} + } + } + } + ], + "ext": { + "prebid": { + "targeting": { + "pricegranularity": "low" + }, + "cache": { + "bids": {} + }, + "bidderparams": { + "appnexus": { + "placementId": 222222 + }, + "pubmatic": { + "publisherId": "5678", + "wrapper": { + "profile": 5678, + "version": 2 + } + } + } + } + } + }, + "expectedReqExt": { + "prebid": { + "targeting": { + "pricegranularity": "low" + }, + "cache": { + "bids": {} + }, + "bidderparams": { + "appnexus": { + "placementId": 222222 + }, + "pubmatic": { + "publisherId": "5678", + "wrapper": { + "profile": 5678, + "version": 2 + } + } + } + } + }, + "expectedImpExt": { + "appnexus": { + "placementId": 111111 + }, + "pubmatic": { + "publisherId": "5678", + "wrapper": { + "profile": 1234 + } + }, + "prebid": { + "bidder": {} + } + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-merge-reserved-keyword.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-merge-reserved-keyword.json new file mode 100644 index 00000000000..982f86cb4e0 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-merge-reserved-keyword.json @@ -0,0 +1,116 @@ +{ + "description": "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext.prebid.bidder for reserved keyword", + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "pmp": { + "deals": [ + { + "id": "some-deal-id" + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "bidder": { + "param1": 111111 + }, + "appnexus": { + "placementId": 111111 + }, + "pubmatic": { + "publisherId": "1234", + "wrapper": { + "profile": 1234 + } + } + } + } + } + } + ], + "ext": { + "prebid": { + "targeting": { + "pricegranularity": "low" + }, + "cache": { + "bids": {} + }, + "bidderparams": { + "bidder": { + "param2": 111111 + }, + "appnexus": { + "placementId": 222222 + }, + "pubmatic": { + "publisherId": "5678", + "wrapper": { + "profile": 5678, + "version": 2 + } + } + } + } + } + }, + "expectedReqExt": { + "prebid": { + "targeting": { + "pricegranularity": "low" + }, + "cache": { + "bids": {} + }, + "bidderparams": { + "bidder": { + "param2": 111111 + }, + "appnexus": { + "placementId": 222222 + }, + "pubmatic": { + "publisherId": "5678", + "wrapper": { + "profile": 5678, + "version": 2 + } + } + } + } + }, + "expectedImpExt": { + "prebid": { + "bidder": { + "bidder": { + "param1": 111111 + }, + "appnexus": { + "placementId": 111111 + }, + "pubmatic": { + "publisherId": "1234", + "wrapper": { + "profile": 1234 + } + } + } + } + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-merge.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-merge.json new file mode 100644 index 00000000000..22026f56ed6 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-merge.json @@ -0,0 +1,104 @@ +{ + "description": "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext.prebid.bidder with preference for imp[].ext.prebid.bidder params", + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "pmp": { + "deals": [ + { + "id": "some-deal-id" + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 111111 + }, + "pubmatic": { + "publisherId": "1234", + "wrapper": { + "profile": 1234 + } + } + } + } + } + } + ], + "ext": { + "prebid": { + "targeting": { + "pricegranularity": "low" + }, + "cache": { + "bids": {} + }, + "bidderparams": { + "appnexus": { + "placementId": 222222 + }, + "pubmatic": { + "publisherId": "5678", + "wrapper": { + "profile": 5678, + "version": 2 + } + } + } + } + } + }, + "expectedReqExt": { + "prebid": { + "targeting": { + "pricegranularity": "low" + }, + "cache": { + "bids": {} + }, + "bidderparams": { + "appnexus": { + "placementId": 222222 + }, + "pubmatic": { + "publisherId": "5678", + "wrapper": { + "profile": 5678, + "version": 2 + } + } + } + } + }, + "expectedImpExt": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 111111 + }, + "pubmatic": { + "publisherId": "1234", + "wrapper": { + "profile": 1234 + } + } + } + } + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params.json new file mode 100644 index 00000000000..9d4bcd0dd59 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params.json @@ -0,0 +1,98 @@ +{ + "description": "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext.prebid.bidder", + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "pmp": { + "deals": [ + { + "id": "some-deal-id" + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": {}, + "pubmatic": {} + } + } + } + } + ], + "ext": { + "prebid": { + "targeting": { + "pricegranularity": "low" + }, + "cache": { + "bids": {} + }, + "bidderparams": { + "appnexus": { + "placementId": 12883451 + }, + "pubmatic": { + "publisherId": "1234", + "wrapper": { + "profile": 1234, + "version": 2 + } + } + } + } + } + }, + "expectedReqExt": { + "prebid": { + "targeting": { + "pricegranularity": "low" + }, + "cache": { + "bids": {} + }, + "bidderparams": { + "appnexus": { + "placementId": 12883451 + }, + "pubmatic": { + "publisherId": "1234", + "wrapper": { + "profile": 1234, + "version": 2 + } + } + } + } + }, + "expectedImpExt": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12883451 + }, + "pubmatic": { + "publisherId": "1234", + "wrapper": { + "profile": 1234, + "version": 2 + } + } + } + } + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index 56c67c4dd97..60372c384c9 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -244,7 +244,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re // Populate any "missing" OpenRTB fields with info from other sources, (e.g. HTTP request headers). deps.setFieldsImplicitly(r, bidReq) // move after merge - errL = deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: bidReq}) + errL = deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: bidReq}, false) if errortypes.ContainsFatalError(errL) { handleError(&labels, w, errL, &vo, &debugLog) return diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index 3163cd9d323..b7eb0b27c0b 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -1435,19 +1435,3 @@ var testVideoStoredImpData = map[string]json.RawMessage{ var testVideoStoredRequestData = map[string]json.RawMessage{ "80ce30c53c16e6ede735f123ef6e32361bfc7b22": json.RawMessage(`{"accountid": "11223344", "site": {"page": "mygame.foo.com"}}`), } - -func loadValidRequest(t *testing.T) *openrtb_ext.BidRequestVideo { - reqData, err := ioutil.ReadFile("sample-requests/video/video_valid_sample.json") - if err != nil { - t.Fatalf("Failed to fetch a valid request: %v", err) - } - - reqBody := getRequestPayload(t, reqData) - - reqVideo := &openrtb_ext.BidRequestVideo{} - if err := json.Unmarshal(reqBody, reqVideo); err != nil { - t.Fatalf("Failed to unmarshal the request: %v", err) - } - - return reqVideo -} diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index f34e74ea41b..5d99b7f3668 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -2,18 +2,18 @@ package exchange import ( "github.com/prebid/prebid-server/adapters" - ttx "github.com/prebid/prebid-server/adapters/33across" + "github.com/prebid/prebid-server/adapters/33across" "github.com/prebid/prebid-server/adapters/aceex" "github.com/prebid/prebid-server/adapters/acuityads" "github.com/prebid/prebid-server/adapters/adagio" "github.com/prebid/prebid-server/adapters/adf" - "github.com/prebid/prebid-server/adapters/adform" "github.com/prebid/prebid-server/adapters/adgeneration" "github.com/prebid/prebid-server/adapters/adhese" "github.com/prebid/prebid-server/adapters/adkernel" "github.com/prebid/prebid-server/adapters/adkernelAdn" "github.com/prebid/prebid-server/adapters/adman" "github.com/prebid/prebid-server/adapters/admixer" + "github.com/prebid/prebid-server/adapters/adnuntius" "github.com/prebid/prebid-server/adapters/adocean" "github.com/prebid/prebid-server/adapters/adoppler" "github.com/prebid/prebid-server/adapters/adot" @@ -41,6 +41,7 @@ import ( "github.com/prebid/prebid-server/adapters/bidscube" "github.com/prebid/prebid-server/adapters/bmtm" "github.com/prebid/prebid-server/adapters/brightroll" + "github.com/prebid/prebid-server/adapters/coinzilla" "github.com/prebid/prebid-server/adapters/colossus" "github.com/prebid/prebid-server/adapters/connectad" "github.com/prebid/prebid-server/adapters/consumable" @@ -97,6 +98,7 @@ import ( "github.com/prebid/prebid-server/adapters/pulsepoint" "github.com/prebid/prebid-server/adapters/revcontent" "github.com/prebid/prebid-server/adapters/rhythmone" + "github.com/prebid/prebid-server/adapters/richaudience" "github.com/prebid/prebid-server/adapters/rtbhouse" "github.com/prebid/prebid-server/adapters/rubicon" salunamedia "github.com/prebid/prebid-server/adapters/sa_lunamedia" @@ -120,6 +122,7 @@ import ( "github.com/prebid/prebid-server/adapters/unicorn" "github.com/prebid/prebid-server/adapters/unruly" "github.com/prebid/prebid-server/adapters/valueimpression" + "github.com/prebid/prebid-server/adapters/videobyte" "github.com/prebid/prebid-server/adapters/visx" "github.com/prebid/prebid-server/adapters/vrtcal" "github.com/prebid/prebid-server/adapters/yahoossp" @@ -141,13 +144,14 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderAcuityAds: acuityads.Builder, openrtb_ext.BidderAdagio: adagio.Builder, openrtb_ext.BidderAdf: adf.Builder, - openrtb_ext.BidderAdform: adform.Builder, + openrtb_ext.BidderAdform: adf.Builder, openrtb_ext.BidderAdgeneration: adgeneration.Builder, openrtb_ext.BidderAdhese: adhese.Builder, openrtb_ext.BidderAdkernel: adkernel.Builder, openrtb_ext.BidderAdkernelAdn: adkernelAdn.Builder, openrtb_ext.BidderAdman: adman.Builder, openrtb_ext.BidderAdmixer: admixer.Builder, + openrtb_ext.BidderAdnuntius: adnuntius.Builder, openrtb_ext.BidderAdOcean: adocean.Builder, openrtb_ext.BidderAdoppler: adoppler.Builder, openrtb_ext.BidderAdpone: adpone.Builder, @@ -175,6 +179,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderBidsCube: bidscube.Builder, openrtb_ext.BidderBmtm: bmtm.Builder, openrtb_ext.BidderBrightroll: brightroll.Builder, + openrtb_ext.BidderCoinzilla: coinzilla.Builder, openrtb_ext.BidderColossus: colossus.Builder, openrtb_ext.BidderConnectAd: connectad.Builder, openrtb_ext.BidderConsumable: consumable.Builder, @@ -193,6 +198,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderGamma: gamma.Builder, openrtb_ext.BidderGamoshi: gamoshi.Builder, openrtb_ext.BidderGrid: grid.Builder, + openrtb_ext.BidderGroupm: pubmatic.Builder, openrtb_ext.BidderGumGum: gumgum.Builder, openrtb_ext.BidderHuaweiAds: huaweiads.Builder, openrtb_ext.BidderImpactify: impactify.Builder, @@ -233,6 +239,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderPulsepoint: pulsepoint.Builder, openrtb_ext.BidderRevcontent: revcontent.Builder, openrtb_ext.BidderRhythmone: rhythmone.Builder, + openrtb_ext.BidderRichaudience: richaudience.Builder, openrtb_ext.BidderRTBHouse: rtbhouse.Builder, openrtb_ext.BidderRubicon: rubicon.Builder, openrtb_ext.BidderSharethrough: sharethrough.Builder, @@ -246,6 +253,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderSomoaudience: somoaudience.Builder, openrtb_ext.BidderSonobi: sonobi.Builder, openrtb_ext.BidderSovrn: sovrn.Builder, + openrtb_ext.BidderStreamkey: adtelligent.Builder, openrtb_ext.BidderSynacormedia: synacormedia.Builder, openrtb_ext.BidderTappx: tappx.Builder, openrtb_ext.BidderTelaria: telaria.Builder, @@ -257,6 +265,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderUnruly: unruly.Builder, openrtb_ext.BidderValueImpression: valueimpression.Builder, openrtb_ext.BidderVerizonMedia: yahoossp.Builder, + openrtb_ext.BidderVideoByte: videobyte.Builder, openrtb_ext.BidderViewdeos: adtelligent.Builder, openrtb_ext.BidderVisx: visx.Builder, openrtb_ext.BidderVrtcal: vrtcal.Builder, diff --git a/exchange/auction.go b/exchange/auction.go index 94e808801d9..c8aff684e41 100644 --- a/exchange/auction.go +++ b/exchange/auction.go @@ -311,13 +311,6 @@ func valOrZero(useVal bool, val int) int { return 0 } -func maybeMake(shouldMake bool, capacity int) []prebid_cache_client.Cacheable { - if shouldMake { - return make([]prebid_cache_client.Cacheable, 0, capacity) - } - return nil -} - func cacheTTL(impTTL int64, bidTTL int64, defTTL int64, buffer int64) (ttl int64) { if impTTL <= 0 && bidTTL <= 0 { // Only use default if there is no imp nor bid TTL provided. We don't want the default diff --git a/exchange/auction_test.go b/exchange/auction_test.go index ee064fcb6f1..455ae5018e8 100644 --- a/exchange/auction_test.go +++ b/exchange/auction_test.go @@ -555,12 +555,6 @@ type pbsBid struct { Bidder openrtb_ext.BidderName `json:"bidder"` } -type cacheComparator struct { - freq int - expectedKeys []string - actualKeys []string -} - type mockCache struct { scheme string host string diff --git a/exchange/bidder.go b/exchange/bidder.go index 0bdaa648619..2aae63e21b8 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -130,6 +130,7 @@ type bidderAdapterConfig struct { } func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error) { + reqData, errs := bidder.Bidder.MakeRequests(request, reqInfo) if len(reqData) == 0 { diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index da31658e32d..401c245da47 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -1301,7 +1301,7 @@ func TestMobileNativeTypes(t *testing.T) { var actualValue string for _, bid := range seatBids.bids { actualValue = bid.bid.AdM - diffJson(t, tc.description, []byte(actualValue), []byte(tc.expectedValue)) + assert.JSONEq(t, tc.expectedValue, actualValue, tc.description) } } } @@ -1795,7 +1795,6 @@ func (bidder *mixedMultiBidder) MakeBids(internalRequest *openrtb2.BidRequest, e } type bidRejector struct { - httpRequest *adapters.RequestData httpResponse *adapters.ResponseData } diff --git a/exchange/exchange.go b/exchange/exchange.go index 8b4b49e74f2..6298d88a0f6 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -18,6 +18,7 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/firstpartydata" "github.com/prebid/prebid-server/gdpr" "github.com/prebid/prebid-server/metrics" "github.com/prebid/prebid-server/openrtb_ext" @@ -159,7 +160,8 @@ type AuctionRequest struct { // LegacyLabels is included here for temporary compatability with cleanOpenRTBRequests // in HoldAuction until we get to factoring it away. Do not use for anything new. - LegacyLabels metrics.Labels + LegacyLabels metrics.Labels + FirstPartyData map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData } // BidderRequest holds the bidder specific request and all other diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 9dcf9d66a7f..c7429dffafc 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/prebid/prebid-server/firstpartydata" "io/ioutil" "net/http" "net/http/httptest" @@ -34,7 +35,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/yudai/gojsondiff" - "github.com/yudai/gojsondiff/formatter" ) func TestNewExchange(t *testing.T) { @@ -377,7 +377,7 @@ func TestDebugBehaviour(t *testing.T) { // If not nil, assert bid extension if test.in.debug { - diffJson(t, test.desc, bidRequest.Ext, actualExt.Debug.ResolvedRequest.Ext) + assert.JSONEq(t, string(bidRequest.Ext), string(actualExt.Debug.ResolvedRequest.Ext), test.desc) } } else if !test.debugData.bidderLevelDebugAllowed && test.debugData.accountLevelDebugAllowed { assert.Equal(t, len(actualExt.Debug.HttpCalls), 0, "%s. ext.debug.httpcalls array should not be empty", "With bidder level debug disable option http calls should be empty") @@ -2143,7 +2143,7 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { } if spec.IncomingRequest.OrtbRequest.Test == 1 { //compare debug info - diffJson(t, "Debug info modified", bid.Ext, spec.Response.Ext) + assert.JSONEq(t, string(bid.Ext), string(spec.Response.Ext), "Debug info modified") } } @@ -2997,7 +2997,7 @@ func TestCategoryMappingTwoBiddersOneBidEachNoCategorySamePrice(t *testing.T) { adapterBids[bidderNameApn1] = &seatBidApn1 adapterBids[bidderNameApn2] = &seatBidApn2 - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &requestExt, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}) + bidCategory, _, rejections, err := applyCategoryMapping(nil, &requestExt, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}) assert.NoError(t, err, "Category mapping error should be empty") assert.Len(t, rejections, 1, "There should be 1 bid rejection message") @@ -3613,6 +3613,60 @@ func TestMakeBidExtJSON(t *testing.T) { } } +func TestFPD(t *testing.T) { + + bidRequest := &openrtb2.BidRequest{ + ID: "some-request-id", + Imp: []openrtb2.Imp{{ + ID: "some-impression-id", + Video: &openrtb2.Video{W: 100, H: 50}, + Ext: json.RawMessage(`{"appnexus": {"placementId": 1}}`), + }}, + Site: &openrtb2.Site{ID: "Req site id", Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)}, + AT: 1, + TMax: 500, + } + + fpd := make(map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData) + + apnFpd := firstpartydata.ResolvedFirstPartyData{ + Site: &openrtb2.Site{ID: "fpdSite"}, + App: &openrtb2.App{ID: "fpdApp"}, + User: &openrtb2.User{ID: "fpdUser"}, + } + fpd[openrtb_ext.BidderName("appnexus")] = &apnFpd + + e := new(exchange) + e.adapterMap = map[openrtb_ext.BidderName]adaptedBidder{ + openrtb_ext.BidderAppnexus: &capturingRequestBidder{}, + } + e.me = &metricsConf.NilMetricsEngine{} + e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) + + ctx := context.Background() + + auctionRequest := AuctionRequest{ + BidRequest: bidRequest, + UserSyncs: &emptyUsersync{}, + FirstPartyData: fpd, + } + + debugLog := &DebugLog{DebugOverride: true, DebugEnabledOrOverridden: true} + + outBidResponse, err := e.HoldAuction(ctx, auctionRequest, debugLog) + + assert.NotNilf(t, outBidResponse, "outBidResponse should not be nil") + assert.Nil(t, err, "Error should be nil") + + request := e.adapterMap[openrtb_ext.BidderAppnexus].(*capturingRequestBidder).req + + assert.NotNil(t, request, "Bidder request should not be nil") + assert.Equal(t, apnFpd.Site, request.Site, "Site is incorrect") + assert.Equal(t, apnFpd.App, request.App, "App is incorrect") + assert.Equal(t, apnFpd.User, request.User, "User is incorrect") + +} + type exchangeSpec struct { GDPREnabled bool `json:"gdpr_enabled"` IncomingRequest exchangeRequest `json:"incomingRequest"` @@ -3733,6 +3787,15 @@ func (b *validatingBidder) requestBid(ctx context.Context, request *openrtb2.Bid return } +type capturingRequestBidder struct { + req *openrtb2.BidRequest +} + +func (b *capturingRequestBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (seatBid *pbsOrtbSeatBid, errs []error) { + b.req = request + return &pbsOrtbSeatBid{}, nil +} + func diffOrtbRequests(t *testing.T, description string, expected *openrtb2.BidRequest, actual *openrtb2.BidRequest) { t.Helper() actualJSON, err := json.Marshal(actual) @@ -3745,7 +3808,7 @@ func diffOrtbRequests(t *testing.T, description string, expected *openrtb2.BidRe t.Fatalf("%s failed to marshal expected BidRequest into JSON. %v", description, err) } - diffJson(t, description, actualJSON, expectedJSON) + assert.JSONEq(t, string(expectedJSON), string(actualJSON), description) } func diffOrtbResponses(t *testing.T, description string, expected *openrtb2.BidResponse, actual *openrtb2.BidResponse) { @@ -3768,7 +3831,7 @@ func diffOrtbResponses(t *testing.T, description string, expected *openrtb2.BidR t.Fatalf("%s failed to marshal expected BidResponse into JSON. %v", description, err) } - diffJson(t, description, actualJSON, expectedJSON) + assert.JSONEq(t, string(expectedJSON), string(actualJSON), description) } func mapifySeatBids(t *testing.T, context string, seatBids []openrtb2.SeatBid) map[string]*openrtb2.SeatBid { @@ -3784,32 +3847,6 @@ func mapifySeatBids(t *testing.T, context string, seatBids []openrtb2.SeatBid) m return seatMap } -// diffJson compares two JSON byte arrays for structural equality. It will produce an error if either -// byte array is not actually JSON. -func diffJson(t *testing.T, description string, actual []byte, expected []byte) { - t.Helper() - diff, err := gojsondiff.New().Compare(actual, expected) - if err != nil { - t.Fatalf("%s json diff failed. %v", description, err) - } - - if diff.Modified() { - var left interface{} - if err := json.Unmarshal(actual, &left); err != nil { - t.Fatalf("%s json did not match, but unmarshalling failed. %v", description, err) - } - printer := formatter.NewAsciiFormatter(left, formatter.AsciiFormatterConfig{ - ShowArrayIndex: true, - }) - output, err := printer.Format(diff) - if err != nil { - t.Errorf("%s did not match, but diff formatting failed. %v", description, err) - } else { - t.Errorf("%s json did not match expected.\n\n%s", description, output) - } - } -} - func mockHandler(statusCode int, getBody string, postBody string) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(statusCode) diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go index 8991a116624..48e054c7bda 100644 --- a/exchange/targeting_test.go +++ b/exchange/targeting_test.go @@ -8,17 +8,14 @@ import ( "testing" "time" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/gdpr" - - metricsConf "github.com/prebid/prebid-server/metrics/config" metricsConfig "github.com/prebid/prebid-server/metrics/config" - - "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/openrtb_ext" + + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" ) @@ -88,7 +85,7 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op ex := &exchange{ adapterMap: buildAdapterMap(mockBids, server.URL, server.Client()), - me: &metricsConf.NilMetricsEngine{}, + me: &metricsConfig.NilMetricsEngine{}, cache: &wellBehavedCache{}, cacheTime: time.Duration(0), gDPR: gdpr.AlwaysAllow{}, @@ -129,14 +126,6 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op return buildBidMap(bidResp.SeatBid, len(mockBids)) } -func buildBidderList(bids map[openrtb_ext.BidderName][]*openrtb2.Bid) []openrtb_ext.BidderName { - bidders := make([]openrtb_ext.BidderName, 0, len(bids)) - for name := range bids { - bidders = append(bidders, name) - } - return bidders -} - func buildAdapterMap(bids map[openrtb_ext.BidderName][]*openrtb2.Bid, mockServerURL string, client *http.Client) map[openrtb_ext.BidderName]adaptedBidder { adapterMap := make(map[openrtb_ext.BidderName]adaptedBidder, len(bids)) for bidder, bids := range bids { diff --git a/exchange/utils.go b/exchange/utils.go index 59cc7f1e40a..23ffe5e5eb6 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -11,7 +11,9 @@ import ( "github.com/prebid/go-gdpr/vendorconsent" "github.com/buger/jsonparser" + "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/firstpartydata" "github.com/prebid/prebid-server/gdpr" "github.com/prebid/prebid-server/metrics" "github.com/prebid/prebid-server/openrtb_ext" @@ -154,6 +156,10 @@ func cleanOpenRTBRequests(ctx context.Context, } } + if req.FirstPartyData != nil && req.FirstPartyData[bidderRequest.BidderName] != nil { + applyFPD(req.FirstPartyData[bidderRequest.BidderName], bidderRequest.BidRequest) + } + if bidRequestAllowed { privacyEnforcement.Apply(bidderRequest.BidRequest) allowedBidderRequests = append(allowedBidderRequests, bidderRequest) @@ -217,6 +223,11 @@ func getAuctionBidderRequests(req AuctionRequest, return nil, []error{err} } + bidderParamsInReqExt, err := adapters.ExtractReqExtBidderParams(req.BidRequest) + if err != nil { + return nil, []error{err} + } + var sChainsByBidder map[string]*openrtb_ext.ExtRequestPrebidSChainSChain // Quick extra wrapper until RequestWrapper makes its way into CleanRequests @@ -227,21 +238,33 @@ func getAuctionBidderRequests(req AuctionRequest, } } - reqExt, err := getExtJson(req.BidRequest, requestExt) - if err != nil { - return nil, []error{err} - } - var errs []error for bidder, imps := range impsByBidder { coreBidder := resolveBidder(bidder, aliases) reqCopy := *req.BidRequest reqCopy.Imp = imps - reqCopy.Ext = reqExt prepareSource(&reqCopy, bidder, sChainsByBidder) + if len(bidderParamsInReqExt) != 0 { + + // Update bidder-params(requestExt.Prebid.BidderParams) for the bidder to only contain bidder-params for + // this bidder only and remove bidder-params for all other bidders from requestExt.Prebid.BidderParams + params, err := getBidderParamsForBidder(bidderParamsInReqExt, bidder) + if err != nil { + return nil, []error{err} + } + + requestExt.Prebid.BidderParams = params + } + + reqExt, err := getExtJson(req.BidRequest, requestExt) + if err != nil { + return nil, []error{err} + } + reqCopy.Ext = reqExt + if err := removeUnpermissionedEids(&reqCopy, bidder, requestExt); err != nil { errs = append(errs, fmt.Errorf("unable to enforce request.ext.prebid.data.eidpermissions because %v", err)) continue @@ -273,6 +296,18 @@ func getAuctionBidderRequests(req AuctionRequest, return bidderRequests, errs } +func getBidderParamsForBidder(bidderParamsInReqExt map[string]map[string]json.RawMessage, bidder string) (json.RawMessage, error) { + var params json.RawMessage + if bidderParams, ok := bidderParamsInReqExt[bidder]; ok { + var err error + params, err = json.Marshal(bidderParams) + if err != nil { + return nil, err + } + } + return params, nil +} + func getExtJson(req *openrtb2.BidRequest, unpackedExt *openrtb_ext.ExtRequest) (json.RawMessage, error) { if len(req.Ext) == 0 || unpackedExt == nil { return json.RawMessage(``), nil @@ -304,6 +339,9 @@ func prepareSource(req *openrtb2.BidRequest, bidder string, sChainsByBidder map[ // set source if req.Source == nil { req.Source = &openrtb2.Source{} + } else { + sourceCopy := *req.Source + req.Source = &sourceCopy } schain := openrtb_ext.ExtRequestPrebidSChain{ SChain: *selectedSChain, @@ -760,3 +798,15 @@ func writeNameVersionRecord(sb *strings.Builder, name, version string) { sb.WriteString("/") sb.WriteString(version) } + +func applyFPD(fpd *firstpartydata.ResolvedFirstPartyData, bidReq *openrtb2.BidRequest) { + if fpd.Site != nil { + bidReq.Site = fpd.Site + } + if fpd.App != nil { + bidReq.App = fpd.App + } + if fpd.User != nil { + bidReq.User = fpd.User + } +} diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 6360fc3b9d8..d0b4006dd15 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -10,6 +10,7 @@ import ( "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/firstpartydata" "github.com/prebid/prebid-server/gdpr" "github.com/prebid/prebid-server/metrics" "github.com/prebid/prebid-server/openrtb_ext" @@ -490,6 +491,70 @@ func TestCleanOpenRTBRequests(t *testing.T) { } } +func TestCleanOpenRTBRequestsWithFPD(t *testing.T) { + fpd := make(map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData) + + apnFpd := firstpartydata.ResolvedFirstPartyData{ + Site: &openrtb2.Site{Name: "fpdApnSite"}, + App: &openrtb2.App{Name: "fpdApnApp"}, + User: &openrtb2.User{Keywords: "fpdApnUser"}, + } + fpd[openrtb_ext.BidderName("appnexus")] = &apnFpd + + brightrollFpd := firstpartydata.ResolvedFirstPartyData{ + Site: &openrtb2.Site{Name: "fpdBrightrollSite"}, + App: &openrtb2.App{Name: "fpdBrightrollApp"}, + User: &openrtb2.User{Keywords: "fpdBrightrollUser"}, + } + fpd[openrtb_ext.BidderName("brightroll")] = &brightrollFpd + + testCases := []struct { + description string + req AuctionRequest + fpdExpected bool + }{ + { + description: "Pass valid FPD data for bidder not found in the request", + req: AuctionRequest{BidRequest: getTestBuildRequest(t), UserSyncs: &emptyUsersync{}, FirstPartyData: fpd}, + fpdExpected: false, + }, + { + description: "Pass valid FPD data for bidders specified in request", + req: AuctionRequest{BidRequest: newAdapterAliasBidRequest(t), UserSyncs: &emptyUsersync{}, FirstPartyData: fpd}, + fpdExpected: true, + }, + { + description: "Bidders specified in request but there is no fpd data for this bidder", + req: AuctionRequest{BidRequest: newAdapterAliasBidRequest(t), UserSyncs: &emptyUsersync{}, FirstPartyData: make(map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData)}, + fpdExpected: false, + }, + { + description: "No FPD data passed", + req: AuctionRequest{BidRequest: newAdapterAliasBidRequest(t), UserSyncs: &emptyUsersync{}, FirstPartyData: nil}, + fpdExpected: false, + }, + } + + for _, test := range testCases { + metricsMock := metrics.MetricsEngineMock{} + bidderToSyncerKey := map[string]string{} + permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} + bidderRequests, _, err := cleanOpenRTBRequests(context.Background(), test.req, nil, bidderToSyncerKey, &permissions, &metricsMock, gdpr.SignalNo, config.Privacy{}, nil) + assert.Empty(t, err, "No errors should be returned") + for _, bidderRequest := range bidderRequests { + bidderName := bidderRequest.BidderName + if test.fpdExpected { + assert.Equal(t, fpd[bidderName].Site.Name, bidderRequest.BidRequest.Site.Name, "Incorrect FPD site name") + assert.Equal(t, fpd[bidderName].App.Name, bidderRequest.BidRequest.App.Name, "Incorrect FPD app name") + assert.Equal(t, fpd[bidderName].User.Keywords, bidderRequest.BidRequest.User.Keywords, "Incorrect FPD user keywords") + } else { + assert.Equal(t, "", bidderRequest.BidRequest.Site.Name, "Incorrect FPD site name") + assert.Equal(t, "", bidderRequest.BidRequest.User.Keywords, "Incorrect FPD user keywords") + } + } + } +} + func TestCleanOpenRTBRequestsCCPA(t *testing.T) { trueValue, falseValue := true, false @@ -868,6 +933,90 @@ func TestCleanOpenRTBRequestsSChain(t *testing.T) { } } +func TestCleanOpenRTBRequestsBidderParams(t *testing.T) { + testCases := []struct { + description string + inExt json.RawMessage + expectedExt map[string]json.RawMessage + hasError bool + }{ + { + description: "Nil Bidder params", + inExt: nil, + expectedExt: getExpectedReqExt(true, false, false), + hasError: false, + }, + { + description: "Bidder params for single partner", + inExt: json.RawMessage(`{"prebid":{"bidderparams": {"pubmatic": {"profile":1234,"version":2}}}}`), + expectedExt: getExpectedReqExt(false, true, false), + hasError: false, + }, + { + description: "Bidder params for two partners", + inExt: json.RawMessage(`{"prebid":{"bidderparams": {"pubmatic": {"profile":1234,"version":2}, "appnexus": {"key1": 123, "key2": {"innerKey1":"innerValue1"}} }}}`), + expectedExt: getExpectedReqExt(false, true, true), + hasError: false, + }, + } + + for _, test := range testCases { + req := newBidRequestWithBidderParams(t) + var extRequest *openrtb_ext.ExtRequest + if test.inExt != nil { + req.Ext = test.inExt + unmarshaledExt, err := extractBidRequestExt(req) + assert.NoErrorf(t, err, test.description+":Error unmarshaling inExt") + extRequest = unmarshaledExt + } + + auctionReq := AuctionRequest{ + BidRequest: req, + UserSyncs: &emptyUsersync{}, + } + + bidderToSyncerKey := map[string]string{} + permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} + metrics := metrics.MetricsEngineMock{} + bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, bidderToSyncerKey, &permissions, &metrics, gdpr.SignalNo, config.Privacy{}, nil) + if test.hasError == true { + assert.NotNil(t, errs) + assert.Len(t, bidderRequests, 0) + } else { + assert.Nil(t, errs) + for _, r := range bidderRequests { + expected := test.expectedExt[r.BidderName.String()] + actual := r.BidRequest.Ext + assert.Equal(t, expected, actual, test.description+" Req:Ext.Prebid.BidderParams") + } + } + } +} + +func getExpectedReqExt(nilExt, includePubmaticParams, includeAppnexusParams bool) map[string]json.RawMessage { + bidderParamsMap := make(map[string]json.RawMessage) + + if nilExt { + bidderParamsMap["pubmatic"] = json.RawMessage(``) + bidderParamsMap["appnexus"] = json.RawMessage(``) + return bidderParamsMap + } + + if includePubmaticParams { + bidderParamsMap["pubmatic"] = json.RawMessage(`{"prebid":{"bidderparams":{"profile":1234,"version":2}}}`) + } else { + bidderParamsMap["pubmatic"] = json.RawMessage(`{"prebid":{}}`) + } + + if includeAppnexusParams { + bidderParamsMap["appnexus"] = json.RawMessage(`{"prebid":{"bidderparams":{"key1":123,"key2":{"innerKey1":"innerValue1"}}}}`) + } else { + bidderParamsMap["appnexus"] = json.RawMessage(`{"prebid":{}}`) + } + + return bidderParamsMap +} + func TestExtractBidRequestExt(t *testing.T) { var boolFalse, boolTrue *bool = new(bool), new(bool) *boolFalse = false @@ -1935,6 +2084,47 @@ func newBidRequest(t *testing.T) *openrtb2.BidRequest { } } +func newBidRequestWithBidderParams(t *testing.T) *openrtb2.BidRequest { + return &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Page: "www.some.domain.com", + Domain: "domain.com", + Publisher: &openrtb2.Publisher{ + ID: "some-publisher-id", + }, + }, + Device: &openrtb2.Device{ + DIDMD5: "some device ID hash", + UA: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36", + IFA: "ifa", + IP: "132.173.230.74", + Language: "EN", + }, + Source: &openrtb2.Source{ + TID: "61018dc9-fa61-4c41-b7dc-f90b9ae80e87", + }, + User: &openrtb2.User{ + ID: "our-id", + BuyerUID: "their-id", + Yob: 1982, + Ext: json.RawMessage(`{}`), + }, + Imp: []openrtb2.Imp{{ + ID: "some-imp-id", + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{ + W: 300, + H: 250, + }, { + W: 300, + H: 600, + }}, + }, + Ext: json.RawMessage(`{"appnexus": {"placementId": 1}, "pubmatic":{"publisherId": "1234"}}`), + }}, + } +} + func TestRandomizeList(t *testing.T) { var ( bidder1 = openrtb_ext.BidderName("bidder1") @@ -2543,3 +2733,140 @@ func TestBuildXPrebidHeader(t *testing.T) { assert.Equal(t, test.result, result, test.description+":result") } } + +func TestSourceExtSChainCopied(t *testing.T) { + bidRequest := newBidRequest(t) + + bidderSchains := map[string]*openrtb_ext.ExtRequestPrebidSChainSChain{ + "bidder1": { + Ver: "1.0", + Complete: 1, + Nodes: []*openrtb_ext.ExtRequestPrebidSChainSChainNode{ + { + ASI: "bidder1.com", + SID: "0001", + HP: 1, + }, + }, + }, + "bidder2": { + Ver: "1.0", + Complete: 1, + Nodes: []*openrtb_ext.ExtRequestPrebidSChainSChainNode{ + { + ASI: "bidder2.com", + SID: "0002", + HP: 1, + }, + }, + }, + } + + copy1 := *bidRequest + originalTid := copy1.Source.TID + prepareSource(©1, "bidder1", bidderSchains) + copy2 := *bidRequest + copy2.Source.TID = "new TID" + prepareSource(©2, "bidder2", bidderSchains) + + assert.Equal(t, json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"bidder1.com","sid":"0001","hp":1}],"ver":"1.0"}}`), copy1.Source.Ext, "First schain was overwritten or not set") + assert.Equal(t, originalTid, copy1.Source.TID, "Original TID was overwritten") + assert.Equal(t, json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"bidder2.com","sid":"0002","hp":1}],"ver":"1.0"}}`), copy2.Source.Ext, "Second schain was overwritten or not set") + assert.Equal(t, "new TID", copy2.Source.TID, "New TID was not set") +} + +func TestCleanOpenRTBRequestsSChainMultipleBidders(t *testing.T) { + req := &openrtb2.BidRequest{ + Site: &openrtb2.Site{}, + Source: &openrtb2.Source{ + TID: "61018dc9-fa61-4c41-b7dc-f90b9ae80e87", + }, + Imp: []openrtb2.Imp{{ + Ext: json.RawMessage(`{"appnexus": {"placementId": 1}, "axonix": { "supplyId": "123"}}`), + }}, + Ext: json.RawMessage(`{"prebid":{"schains":[{ "bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}, {"bidders":["axonix"],"schain":{"complete":1,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":1}],"ver":"1.0"}}]}}`), + } + + unmarshaledExt, err := extractBidRequestExt(req) + assert.NoErrorf(t, err, "Error unmarshaling inExt") + extRequest := unmarshaledExt + + auctionReq := AuctionRequest{ + BidRequest: req, + UserSyncs: &emptyUsersync{}, + } + bidderToSyncerKey := map[string]string{} + permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} + metrics := metrics.MetricsEngineMock{} + bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, bidderToSyncerKey, &permissions, &metrics, gdpr.SignalNo, config.Privacy{}, nil) + + assert.Nil(t, errs) + assert.Len(t, bidderRequests, 2, "Bid request count is not 2") + + bidRequestSourceExts := map[openrtb_ext.BidderName]json.RawMessage{} + for _, bidderRequest := range bidderRequests { + bidRequestSourceExts[bidderRequest.BidderName] = bidderRequest.BidRequest.Source.Ext + } + + appnexusPrebidSchainsSchain := json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}`) + axonixPrebidSchainsSchain := json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":1}],"ver":"1.0"}}`) + assert.Equal(t, appnexusPrebidSchainsSchain, bidRequestSourceExts["appnexus"], "Incorrect appnexus bid request schain in source.ext") + assert.Equal(t, axonixPrebidSchainsSchain, bidRequestSourceExts["axonix"], "Incorrect axonix bid request schain in source.ext") +} + +func TestApplyFPD(t *testing.T) { + fpdBidderTest := map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{} + bidderTest := openrtb_ext.BidderName("test") + fpdBidderTest[bidderTest] = &firstpartydata.ResolvedFirstPartyData{Site: nil, App: nil, User: nil} + + fpdBidderNotNilFPD := map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{} + bidderNotNilFPD := openrtb_ext.BidderName("notNilFPD") + fpdBidderNotNilFPD[bidderNotNilFPD] = &firstpartydata.ResolvedFirstPartyData{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId"}} + + fpdBidderApp := map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{} + bidderApp := openrtb_ext.BidderName("AppFPD") + fpdBidderApp[bidderApp] = &firstpartydata.ResolvedFirstPartyData{App: &openrtb2.App{ID: "AppId"}} + + testCases := []struct { + description string + inputFpd map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData + bidderName openrtb_ext.BidderName + inputRequest openrtb2.BidRequest + expectedRequest openrtb2.BidRequest + }{ + { + description: "req.Site defined; bidderFPD.Site not defined; expect request.Site remains the same", + inputFpd: fpdBidderTest, + bidderName: bidderTest, + inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, + }, + { + description: "req.Site, req.App, req.User are not defined; bidderFPD.App, bidderFPD.Site and bidderFPD.User defined; " + + "expect req.Site, req.App, req.User to be overriden by bidderFPD.App, bidderFPD.Site and bidderFPD.User", + inputFpd: fpdBidderNotNilFPD, + bidderName: bidderNotNilFPD, + inputRequest: openrtb2.BidRequest{}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId"}}, + }, + { + description: "req.Site, defined; bidderFPD.App defined; expect request.App to be overriden by bidderFPD.App; expect req.Site remains the same", + inputFpd: fpdBidderApp, + bidderName: bidderApp, + inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}}, + }, + { + description: "req.Site, req.App defined; bidderFPD.App defined; expect request.App to be overriden by bidderFPD.App", + inputFpd: fpdBidderApp, + bidderName: bidderApp, + inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "TestAppId"}}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}}, + }, + } + + for _, testCase := range testCases { + applyFPD(testCase.inputFpd[testCase.bidderName], &testCase.inputRequest) + assert.Equal(t, testCase.expectedRequest, testCase.inputRequest, fmt.Sprintf("incorrect request after applying fpd, testcase %s", testCase.description)) + } +} diff --git a/firstpartydata/first_party_data.go b/firstpartydata/first_party_data.go new file mode 100644 index 00000000000..dc5b6f318e0 --- /dev/null +++ b/firstpartydata/first_party_data.go @@ -0,0 +1,641 @@ +package firstpartydata + +import ( + "encoding/json" + "fmt" + "github.com/evanphx/json-patch" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +const ( + siteKey = "site" + appKey = "app" + userKey = "user" + dataKey = "data" + extKey = "ext" + + userDataKey = "userData" + appContentDataKey = "appContentData" + siteContentDataKey = "siteContentData" + + keywordsKey = "keywords" + genderKey = "gender" + yobKey = "yob" + pageKey = "page" + nameKey = "name" + domainKey = "domain" + catKey = "cat" + sectionCatKey = "sectioncat" + pageCatKey = "pagecat" + searchKey = "search" + refKey = "ref" + bundleKey = "bundle" + storeUrlKey = "storeurl" + verKey = "ver" +) + +type ResolvedFirstPartyData struct { + Site *openrtb2.Site + App *openrtb2.App + User *openrtb2.User +} + +// ExtractGlobalFPD extracts request level FPD from the request and removes req.{site,app,user}.ext.data if exists +func ExtractGlobalFPD(req *openrtb_ext.RequestWrapper) (map[string][]byte, error) { + + fpdReqData := make(map[string][]byte, 3) + + siteExt, err := req.GetSiteExt() + if err != nil { + return nil, err + } + refreshExt := false + + if len(siteExt.GetExt()[dataKey]) > 0 { + newSiteExt := siteExt.GetExt() + fpdReqData[siteKey] = newSiteExt[dataKey] + delete(newSiteExt, dataKey) + siteExt.SetExt(newSiteExt) + refreshExt = true + } + + appExt, err := req.GetAppExt() + if err != nil { + return nil, err + } + if len(appExt.GetExt()[dataKey]) > 0 { + newAppExt := appExt.GetExt() + fpdReqData[appKey] = newAppExt[dataKey] + delete(newAppExt, dataKey) + appExt.SetExt(newAppExt) + refreshExt = true + } + + userExt, err := req.GetUserExt() + if err != nil { + return nil, err + } + if len(userExt.GetExt()[dataKey]) > 0 { + newUserExt := userExt.GetExt() + fpdReqData[userKey] = newUserExt[dataKey] + delete(newUserExt, dataKey) + userExt.SetExt(newUserExt) + refreshExt = true + } + if refreshExt { + //need to keep site/app/user ext clean in case bidder is not in global fpd bidder list + // rebuild/resync the request in the request wrapper. + if err := req.RebuildRequest(); err != nil { + return nil, err + } + } + + return fpdReqData, nil +} + +//ExtractOpenRtbGlobalFPD extracts and deletes user.data and {app/site}.content.data from request +func ExtractOpenRtbGlobalFPD(bidRequest *openrtb2.BidRequest) map[string][]openrtb2.Data { + + openRtbGlobalFPD := make(map[string][]openrtb2.Data, 3) + if bidRequest.User != nil && len(bidRequest.User.Data) > 0 { + openRtbGlobalFPD[userDataKey] = bidRequest.User.Data + bidRequest.User.Data = nil + } + + if bidRequest.Site != nil && bidRequest.Site.Content != nil && len(bidRequest.Site.Content.Data) > 0 { + openRtbGlobalFPD[siteContentDataKey] = bidRequest.Site.Content.Data + bidRequest.Site.Content.Data = nil + } + + if bidRequest.App != nil && bidRequest.App.Content != nil && len(bidRequest.App.Content.Data) > 0 { + openRtbGlobalFPD[appContentDataKey] = bidRequest.App.Content.Data + bidRequest.App.Content.Data = nil + } + + return openRtbGlobalFPD + +} + +//ResolveFPD consolidates First Party Data from different sources and returns valid FPD that will be applied to bidders later or returns errors +func ResolveFPD(bidRequest *openrtb2.BidRequest, fpdBidderConfigData map[openrtb_ext.BidderName]*openrtb_ext.ORTB2, globalFPD map[string][]byte, openRtbGlobalFPD map[string][]openrtb2.Data, biddersWithGlobalFPD []string) (map[openrtb_ext.BidderName]*ResolvedFirstPartyData, []error) { + var errL []error + + resolvedFpd := make(map[openrtb_ext.BidderName]*ResolvedFirstPartyData) + + allBiddersTable := make(map[string]struct{}) + + if biddersWithGlobalFPD == nil { + //add all bidders in bidder configs to receive global data and bidder specific data + for bidderName := range fpdBidderConfigData { + if _, present := allBiddersTable[string(bidderName)]; !present { + allBiddersTable[string(bidderName)] = struct{}{} + } + } + } else { + //only bidders in global bidder list will receive global data and bidder specific data + for _, bidderName := range biddersWithGlobalFPD { + if _, present := allBiddersTable[string(bidderName)]; !present { + allBiddersTable[string(bidderName)] = struct{}{} + } + } + } + + for bidderName := range allBiddersTable { + + fpdConfig := fpdBidderConfigData[openrtb_ext.BidderName(bidderName)] + + resolvedFpdConfig := &ResolvedFirstPartyData{} + + newUser, err := resolveUser(fpdConfig, bidRequest.User, globalFPD, openRtbGlobalFPD, bidderName) + if err != nil { + errL = append(errL, err) + } + resolvedFpdConfig.User = newUser + + newApp, err := resolveApp(fpdConfig, bidRequest.App, globalFPD, openRtbGlobalFPD, bidderName) + if err != nil { + errL = append(errL, err) + } + resolvedFpdConfig.App = newApp + + newSite, err := resolveSite(fpdConfig, bidRequest.Site, globalFPD, openRtbGlobalFPD, bidderName) + if err != nil { + errL = append(errL, err) + } + resolvedFpdConfig.Site = newSite + + if len(errL) == 0 { + resolvedFpd[openrtb_ext.BidderName(bidderName)] = resolvedFpdConfig + } + } + return resolvedFpd, errL +} + +func resolveUser(fpdConfig *openrtb_ext.ORTB2, bidRequestUser *openrtb2.User, globalFPD map[string][]byte, openRtbGlobalFPD map[string][]openrtb2.Data, bidderName string) (*openrtb2.User, error) { + var fpdConfigUser map[string]json.RawMessage + + if fpdConfig != nil && fpdConfig.User != nil { + fpdConfigUser = fpdConfig.User + } + + if bidRequestUser == nil && fpdConfigUser == nil { + return nil, nil + } + + if bidRequestUser == nil && fpdConfigUser != nil { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("incorrect First Party Data for bidder %s: User object is not defined in request, but defined in FPD config", bidderName), + } + } + + newUser := *bidRequestUser + var err error + + //apply global fpd + if len(globalFPD[userKey]) > 0 { + extData := buildExtData(globalFPD[userKey]) + if len(newUser.Ext) > 0 { + newUser.Ext, err = jsonpatch.MergePatch(newUser.Ext, extData) + } else { + newUser.Ext = extData + } + } + if openRtbGlobalFPD != nil && len(openRtbGlobalFPD[userDataKey]) > 0 { + newUser.Data = openRtbGlobalFPD[userDataKey] + } + if fpdConfigUser != nil { + //apply bidder specific fpd if present + newUser, err = mergeUsers(&newUser, fpdConfigUser) + } + + return &newUser, err +} + +func unmarshalJSONToInt64(input json.RawMessage) (int64, error) { + var num json.Number + err := json.Unmarshal(input, &num) + if err != nil { + return -1, err + } + resNum, err := num.Int64() + return resNum, err +} + +func unmarshalJSONToString(input json.RawMessage) (string, error) { + var inputString string + err := json.Unmarshal(input, &inputString) + return inputString, err +} + +func unmarshalJSONToStringArray(input json.RawMessage) ([]string, error) { + var inputString []string + err := json.Unmarshal(input, &inputString) + return inputString, err +} + +//resolveExtension inserts remaining {site/app/user} attributes back to {site/app/user}.ext.data +func resolveExtension(fpdConfig map[string]json.RawMessage, originalExt json.RawMessage) ([]byte, error) { + resExt := originalExt + var err error + + if resExt == nil && len(fpdConfig) > 0 { + fpdExt, err := json.Marshal(fpdConfig) + return buildExtData(fpdExt), err + } + + fpdConfigExt, present := fpdConfig[extKey] + if present { + delete(fpdConfig, extKey) + resExt, err = jsonpatch.MergePatch(resExt, fpdConfigExt) + if err != nil { + return nil, err + } + } + + if len(fpdConfig) > 0 { + fpdData, err := json.Marshal(fpdConfig) + if err != nil { + return nil, err + } + data := buildExtData(fpdData) + return jsonpatch.MergePatch(resExt, data) + } + return resExt, nil +} + +func mergeUsers(original *openrtb2.User, fpdConfigUser map[string]json.RawMessage) (openrtb2.User, error) { + + var err error + newUser := *original + + if keywords, present := fpdConfigUser[keywordsKey]; present { + newUser.Keywords, err = unmarshalJSONToString(keywords) + if err != nil { + return newUser, err + } + delete(fpdConfigUser, keywordsKey) + } + if gender, present := fpdConfigUser[genderKey]; present { + newUser.Gender, err = unmarshalJSONToString(gender) + if err != nil { + return newUser, err + } + delete(fpdConfigUser, genderKey) + } + if yob, present := fpdConfigUser[yobKey]; present { + newUser.Yob, err = unmarshalJSONToInt64(yob) + if err != nil { + return newUser, err + } + delete(fpdConfigUser, yobKey) + } + + if len(fpdConfigUser) > 0 { + newUser.Ext, err = resolveExtension(fpdConfigUser, original.Ext) + } + + return newUser, err +} + +func resolveSite(fpdConfig *openrtb_ext.ORTB2, bidRequestSite *openrtb2.Site, globalFPD map[string][]byte, openRtbGlobalFPD map[string][]openrtb2.Data, bidderName string) (*openrtb2.Site, error) { + var fpdConfigSite map[string]json.RawMessage + + if fpdConfig != nil && fpdConfig.Site != nil { + fpdConfigSite = fpdConfig.Site + } + + if bidRequestSite == nil && fpdConfigSite == nil { + return nil, nil + } + if bidRequestSite == nil && fpdConfigSite != nil { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("incorrect First Party Data for bidder %s: Site object is not defined in request, but defined in FPD config", bidderName), + } + } + + newSite := *bidRequestSite + var err error + + //apply global fpd + if len(globalFPD[siteKey]) > 0 { + extData := buildExtData(globalFPD[siteKey]) + if len(newSite.Ext) > 0 { + newSite.Ext, err = jsonpatch.MergePatch(newSite.Ext, extData) + } else { + newSite.Ext = extData + } + } + if openRtbGlobalFPD != nil && len(openRtbGlobalFPD[siteContentDataKey]) > 0 { + if newSite.Content != nil { + contentCopy := *newSite.Content + contentCopy.Data = openRtbGlobalFPD[siteContentDataKey] + newSite.Content = &contentCopy + } else { + newSite.Content = &openrtb2.Content{Data: openRtbGlobalFPD[siteContentDataKey]} + } + } + + if fpdConfigSite != nil { + newSite, err = mergeSites(&newSite, fpdConfigSite, bidderName) + } + return &newSite, err + +} +func mergeSites(originalSite *openrtb2.Site, fpdConfigSite map[string]json.RawMessage, bidderName string) (openrtb2.Site, error) { + + var err error + newSite := *originalSite + + if page, present := fpdConfigSite[pageKey]; present { + sitePage, err := unmarshalJSONToString(page) + if err != nil { + return newSite, err + } + //apply bidder specific fpd if present + //result site should have ID or Page, fpd becomes incorrect if it overwrites page to empty one and ID is empty in original site + if sitePage == "" && newSite.Page != "" && newSite.ID == "" { + return newSite, &errortypes.BadInput{ + Message: fmt.Sprintf("incorrect First Party Data for bidder %s: Site object cannot set empty page if req.site.id is empty", bidderName), + } + + } + newSite.Page = sitePage + delete(fpdConfigSite, pageKey) + } + if name, present := fpdConfigSite[nameKey]; present { + newSite.Name, err = unmarshalJSONToString(name) + if err != nil { + return newSite, err + } + delete(fpdConfigSite, nameKey) + } + if domain, present := fpdConfigSite[domainKey]; present { + newSite.Domain, err = unmarshalJSONToString(domain) + if err != nil { + return newSite, err + } + delete(fpdConfigSite, domainKey) + } + if cat, present := fpdConfigSite[catKey]; present { + newSite.Cat, err = unmarshalJSONToStringArray(cat) + if err != nil { + return newSite, err + } + delete(fpdConfigSite, catKey) + } + if sectionCat, present := fpdConfigSite[sectionCatKey]; present { + newSite.SectionCat, err = unmarshalJSONToStringArray(sectionCat) + if err != nil { + return newSite, err + } + delete(fpdConfigSite, sectionCatKey) + } + if pageCat, present := fpdConfigSite[pageCatKey]; present { + newSite.PageCat, err = unmarshalJSONToStringArray(pageCat) + if err != nil { + return newSite, err + } + delete(fpdConfigSite, pageCatKey) + } + if search, present := fpdConfigSite[searchKey]; present { + newSite.Search, err = unmarshalJSONToString(search) + if err != nil { + return newSite, err + } + delete(fpdConfigSite, searchKey) + } + if keywords, present := fpdConfigSite[keywordsKey]; present { + newSite.Keywords, err = unmarshalJSONToString(keywords) + if err != nil { + return newSite, err + } + delete(fpdConfigSite, keywordsKey) + } + if ref, present := fpdConfigSite[refKey]; present { + newSite.Ref, err = unmarshalJSONToString(ref) + if err != nil { + return newSite, err + } + delete(fpdConfigSite, refKey) + } + + if len(fpdConfigSite) > 0 { + newSite.Ext, err = resolveExtension(fpdConfigSite, originalSite.Ext) + } + + return newSite, err +} + +func resolveApp(fpdConfig *openrtb_ext.ORTB2, bidRequestApp *openrtb2.App, globalFPD map[string][]byte, openRtbGlobalFPD map[string][]openrtb2.Data, bidderName string) (*openrtb2.App, error) { + + var fpdConfigApp map[string]json.RawMessage + + if fpdConfig != nil && fpdConfig.App != nil { + fpdConfigApp = fpdConfig.App + } + + if bidRequestApp == nil && fpdConfigApp == nil { + return nil, nil + } + + if bidRequestApp == nil && fpdConfigApp != nil { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("incorrect First Party Data for bidder %s: App object is not defined in request, but defined in FPD config", bidderName), + } + } + + newApp := *bidRequestApp + var err error + + //apply global fpd if exists + if len(globalFPD[appKey]) > 0 { + extData := buildExtData(globalFPD[appKey]) + if len(newApp.Ext) > 0 { + newApp.Ext, err = jsonpatch.MergePatch(newApp.Ext, extData) + } else { + newApp.Ext = extData + } + } + if openRtbGlobalFPD != nil && len(openRtbGlobalFPD[appContentDataKey]) > 0 { + if newApp.Content != nil { + contentCopy := *newApp.Content + contentCopy.Data = openRtbGlobalFPD[appContentDataKey] + newApp.Content = &contentCopy + } else { + newApp.Content = &openrtb2.Content{Data: openRtbGlobalFPD[appContentDataKey]} + } + } + + if fpdConfigApp != nil { + //apply bidder specific fpd if present + newApp, err = mergeApps(&newApp, fpdConfigApp) + } + + return &newApp, err +} + +func mergeApps(originalApp *openrtb2.App, fpdConfigApp map[string]json.RawMessage) (openrtb2.App, error) { + + var err error + newApp := *originalApp + + if name, present := fpdConfigApp[nameKey]; present { + newApp.Name, err = unmarshalJSONToString(name) + if err != nil { + return newApp, err + } + delete(fpdConfigApp, nameKey) + } + if bundle, present := fpdConfigApp[bundleKey]; present { + newApp.Bundle, err = unmarshalJSONToString(bundle) + if err != nil { + return newApp, err + } + delete(fpdConfigApp, bundleKey) + } + if domain, present := fpdConfigApp[domainKey]; present { + newApp.Domain, err = unmarshalJSONToString(domain) + if err != nil { + return newApp, err + } + delete(fpdConfigApp, domainKey) + } + if storeUrl, present := fpdConfigApp[storeUrlKey]; present { + newApp.StoreURL, err = unmarshalJSONToString(storeUrl) + if err != nil { + return newApp, err + } + delete(fpdConfigApp, storeUrlKey) + } + if cat, present := fpdConfigApp[catKey]; present { + newApp.Cat, err = unmarshalJSONToStringArray(cat) + if err != nil { + return newApp, err + } + delete(fpdConfigApp, catKey) + } + if sectionCat, present := fpdConfigApp[sectionCatKey]; present { + newApp.SectionCat, err = unmarshalJSONToStringArray(sectionCat) + if err != nil { + return newApp, err + } + delete(fpdConfigApp, sectionCatKey) + } + if pageCat, present := fpdConfigApp[pageCatKey]; present { + newApp.PageCat, err = unmarshalJSONToStringArray(pageCat) + if err != nil { + return newApp, err + } + delete(fpdConfigApp, pageCatKey) + } + if version, present := fpdConfigApp[verKey]; present { + newApp.Ver, err = unmarshalJSONToString(version) + if err != nil { + return newApp, err + } + delete(fpdConfigApp, verKey) + } + if keywords, present := fpdConfigApp[keywordsKey]; present { + newApp.Keywords, err = unmarshalJSONToString(keywords) + if err != nil { + return newApp, err + } + delete(fpdConfigApp, keywordsKey) + } + + if len(fpdConfigApp) > 0 { + newApp.Ext, err = resolveExtension(fpdConfigApp, originalApp.Ext) + } + + return newApp, err +} + +func buildExtData(data []byte) []byte { + res := make([]byte, 0, len(data)) + res = append(res, []byte(`{"data":`)...) + res = append(res, data...) + res = append(res, []byte(`}`)...) + return res +} + +//ExtractBidderConfigFPD extracts bidder specific configs from req.ext.prebid.bidderconfig +func ExtractBidderConfigFPD(reqExt *openrtb_ext.RequestExt) (map[openrtb_ext.BidderName]*openrtb_ext.ORTB2, error) { + + fpd := make(map[openrtb_ext.BidderName]*openrtb_ext.ORTB2) + reqExtPrebid := reqExt.GetPrebid() + if reqExtPrebid != nil { + for _, bidderConfig := range reqExtPrebid.BidderConfigs { + for _, bidder := range bidderConfig.Bidders { + if _, present := fpd[openrtb_ext.BidderName(bidder)]; present { + //if bidder has duplicated config - throw an error + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("multiple First Party Data bidder configs provided for bidder: %s", bidder), + } + } + + fpdBidderData := &openrtb_ext.ORTB2{} + + if bidderConfig.Config != nil && bidderConfig.Config.ORTB2 != nil { + if bidderConfig.Config.ORTB2.Site != nil { + fpdBidderData.Site = bidderConfig.Config.ORTB2.Site + } + if bidderConfig.Config.ORTB2.App != nil { + fpdBidderData.App = bidderConfig.Config.ORTB2.App + } + if bidderConfig.Config.ORTB2.User != nil { + fpdBidderData.User = bidderConfig.Config.ORTB2.User + } + } + + fpd[openrtb_ext.BidderName(bidder)] = fpdBidderData + } + } + reqExtPrebid.BidderConfigs = nil + reqExt.SetPrebid(reqExtPrebid) + } + return fpd, nil + +} + +//ExtractFPDForBidders extracts FPD data from request if specified +func ExtractFPDForBidders(req *openrtb_ext.RequestWrapper) (map[openrtb_ext.BidderName]*ResolvedFirstPartyData, []error) { + + reqExt, err := req.GetRequestExt() + if err != nil { + return nil, []error{err} + } + if reqExt == nil || reqExt.GetPrebid() == nil { + return nil, nil + } + var biddersWithGlobalFPD []string + + extPrebid := reqExt.GetPrebid() + if extPrebid.Data != nil { + biddersWithGlobalFPD = extPrebid.Data.Bidders + extPrebid.Data.Bidders = nil + reqExt.SetPrebid(extPrebid) + } + + fbdBidderConfigData, err := ExtractBidderConfigFPD(reqExt) + if err != nil { + return nil, []error{err} + } + + var globalFpd map[string][]byte + var openRtbGlobalFPD map[string][]openrtb2.Data + + if biddersWithGlobalFPD != nil { + //global fpd data should not be extracted and removed from request if global bidder list is nil. + //Bidders that don't have any fpd config should receive request data as is + globalFpd, err = ExtractGlobalFPD(req) + if err != nil { + return nil, []error{err} + } + openRtbGlobalFPD = ExtractOpenRtbGlobalFPD(req.BidRequest) + } + + return ResolveFPD(req.BidRequest, fbdBidderConfigData, globalFpd, openRtbGlobalFPD, biddersWithGlobalFPD) + +} diff --git a/firstpartydata/first_party_data_test.go b/firstpartydata/first_party_data_test.go new file mode 100644 index 00000000000..1e09bab14e2 --- /dev/null +++ b/firstpartydata/first_party_data_test.go @@ -0,0 +1,1436 @@ +package firstpartydata + +import ( + "encoding/json" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/stretchr/testify/assert" + "io/ioutil" + "testing" +) + +func TestExtractGlobalFPD(t *testing.T) { + + testCases := []struct { + description string + input openrtb_ext.RequestWrapper + expectedReq openrtb_ext.RequestWrapper + expectedFpd map[string][]byte + }{ + { + description: "Site, app and user data present", + input: openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "bid_id", + Site: &openrtb2.Site{ + ID: "reqSiteId", + Page: "http://www.foobar.com/1234.html", + Publisher: &openrtb2.Publisher{ + ID: "1", + }, + Ext: json.RawMessage(`{"data": {"somesitefpd": "sitefpdDataTest"}}`), + }, + User: &openrtb2.User{ + ID: "reqUserID", + Yob: 1982, + Gender: "M", + Ext: json.RawMessage(`{"data": {"someuserfpd": "userfpdDataTest"}}`), + }, + App: &openrtb2.App{ + ID: "appId", + Ext: json.RawMessage(`{"data": {"someappfpd": "appfpdDataTest"}}`), + }, + }, + }, + expectedReq: openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + ID: "bid_id", + Site: &openrtb2.Site{ + ID: "reqSiteId", + Page: "http://www.foobar.com/1234.html", + Publisher: &openrtb2.Publisher{ + ID: "1", + }, + }, + User: &openrtb2.User{ + ID: "reqUserID", + Yob: 1982, + Gender: "M", + }, + App: &openrtb2.App{ + ID: "appId", + }, + }}, + expectedFpd: map[string][]byte{ + "site": []byte(`{"somesitefpd": "sitefpdDataTest"}`), + "user": []byte(`{"someuserfpd": "userfpdDataTest"}`), + "app": []byte(`{"someappfpd": "appfpdDataTest"}`), + }, + }, + { + description: "App FPD only present", + input: openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "bid_id", + Site: &openrtb2.Site{ + ID: "reqSiteId", + Page: "http://www.foobar.com/1234.html", + Publisher: &openrtb2.Publisher{ + ID: "1", + }, + }, + App: &openrtb2.App{ + ID: "appId", + Ext: json.RawMessage(`{"data": {"someappfpd": "appfpdDataTest"}}`), + }, + }, + }, + expectedReq: openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "bid_id", + Site: &openrtb2.Site{ + ID: "reqSiteId", + Page: "http://www.foobar.com/1234.html", + Publisher: &openrtb2.Publisher{ + ID: "1", + }, + }, + App: &openrtb2.App{ + ID: "appId", + }, + }, + }, + expectedFpd: map[string][]byte{ + "app": []byte(`{"someappfpd": "appfpdDataTest"}`), + "user": nil, + "site": nil, + }, + }, + { + description: "User FPD only present", + input: openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "bid_id", + Site: &openrtb2.Site{ + ID: "reqSiteId", + Page: "http://www.foobar.com/1234.html", + Publisher: &openrtb2.Publisher{ + ID: "1", + }, + }, + User: &openrtb2.User{ + ID: "reqUserID", + Yob: 1982, + Gender: "M", + Ext: json.RawMessage(`{"data": {"someuserfpd": "userfpdDataTest"}}`), + }, + }, + }, + expectedReq: openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "bid_id", + Site: &openrtb2.Site{ + ID: "reqSiteId", + Page: "http://www.foobar.com/1234.html", + Publisher: &openrtb2.Publisher{ + ID: "1", + }, + }, + User: &openrtb2.User{ + ID: "reqUserID", + Yob: 1982, + Gender: "M", + }, + }, + }, + expectedFpd: map[string][]byte{ + "app": nil, + "user": []byte(`{"someuserfpd": "userfpdDataTest"}`), + "site": nil, + }, + }, + { + description: "No FPD present in req", + input: openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "bid_id", + Site: &openrtb2.Site{ + ID: "reqSiteId", + Page: "http://www.foobar.com/1234.html", + Publisher: &openrtb2.Publisher{ + ID: "1", + }, + }, + User: &openrtb2.User{ + ID: "reqUserID", + Yob: 1982, + Gender: "M", + }, + App: &openrtb2.App{ + ID: "appId", + }, + }, + }, + expectedReq: openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "bid_id", + Site: &openrtb2.Site{ + ID: "reqSiteId", + Page: "http://www.foobar.com/1234.html", + Publisher: &openrtb2.Publisher{ + ID: "1", + }, + }, + User: &openrtb2.User{ + ID: "reqUserID", + Yob: 1982, + Gender: "M", + }, + App: &openrtb2.App{ + ID: "appId", + }, + }, + }, + expectedFpd: map[string][]byte{ + "app": nil, + "user": nil, + "site": nil, + }, + }, + { + description: "Site FPD only present", + input: openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "bid_id", + Site: &openrtb2.Site{ + ID: "reqSiteId", + Page: "http://www.foobar.com/1234.html", + Publisher: &openrtb2.Publisher{ + ID: "1", + }, + Ext: json.RawMessage(`{"data": {"someappfpd": true}}`), + }, + App: &openrtb2.App{ + ID: "appId", + }, + }, + }, + expectedReq: openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "bid_id", + Site: &openrtb2.Site{ + ID: "reqSiteId", + Page: "http://www.foobar.com/1234.html", + Publisher: &openrtb2.Publisher{ + ID: "1", + }, + }, + App: &openrtb2.App{ + ID: "appId", + }, + }, + }, + expectedFpd: map[string][]byte{ + "app": nil, + "user": nil, + "site": []byte(`{"someappfpd": true}`), + }, + }, + } + for _, test := range testCases { + + inputReq := &test.input + fpd, err := ExtractGlobalFPD(inputReq) + assert.NoError(t, err, "Error should be nil") + err = inputReq.RebuildRequest() + assert.NoError(t, err, "Error should be nil") + + assert.Equal(t, test.expectedReq.BidRequest, inputReq.BidRequest, "Incorrect input request after global fpd extraction") + + assert.Equal(t, test.expectedFpd[userKey], fpd[userKey], "Incorrect User FPD") + assert.Equal(t, test.expectedFpd[appKey], fpd[appKey], "Incorrect App FPD") + assert.Equal(t, test.expectedFpd[siteKey], fpd[siteKey], "Incorrect Site FPDt") + } +} + +func TestExtractOpenRtbGlobalFPD(t *testing.T) { + + testCases := []struct { + description string + input openrtb2.BidRequest + output openrtb2.BidRequest + expectedFpdData map[string][]openrtb2.Data + }{ + { + description: "Site, app and user data present", + input: openrtb2.BidRequest{ + ID: "bid_id", + Imp: []openrtb2.Imp{ + {ID: "impid"}, + }, + Site: &openrtb2.Site{ + ID: "reqSiteId", + Content: &openrtb2.Content{ + Data: []openrtb2.Data{ + {ID: "siteDataId1", Name: "siteDataName1"}, + {ID: "siteDataId2", Name: "siteDataName2"}, + }, + }, + }, + User: &openrtb2.User{ + ID: "reqUserID", + Yob: 1982, + Gender: "M", + Data: []openrtb2.Data{ + {ID: "userDataId1", Name: "userDataName1"}, + }, + }, + App: &openrtb2.App{ + ID: "appId", + Content: &openrtb2.Content{ + Data: []openrtb2.Data{ + {ID: "appDataId1", Name: "appDataName1"}, + }, + }, + }, + }, + output: openrtb2.BidRequest{ + ID: "bid_id", + Imp: []openrtb2.Imp{ + {ID: "impid"}, + }, + Site: &openrtb2.Site{ + ID: "reqSiteId", + Content: &openrtb2.Content{}, + }, + User: &openrtb2.User{ + ID: "reqUserID", + Yob: 1982, + Gender: "M", + }, + App: &openrtb2.App{ + ID: "appId", + Content: &openrtb2.Content{}, + }, + }, + expectedFpdData: map[string][]openrtb2.Data{ + siteContentDataKey: {{ID: "siteDataId1", Name: "siteDataName1"}, {ID: "siteDataId2", Name: "siteDataName2"}}, + userDataKey: {{ID: "userDataId1", Name: "userDataName1"}}, + appContentDataKey: {{ID: "appDataId1", Name: "appDataName1"}}, + }, + }, + { + description: "No Site, app or user data present", + input: openrtb2.BidRequest{ + ID: "bid_id", + Imp: []openrtb2.Imp{ + {ID: "impid"}, + }, + }, + output: openrtb2.BidRequest{ + ID: "bid_id", + Imp: []openrtb2.Imp{ + {ID: "impid"}, + }, + }, + expectedFpdData: map[string][]openrtb2.Data{ + siteContentDataKey: nil, + userDataKey: nil, + appContentDataKey: nil, + }, + }, + { + description: "Site only data present", + input: openrtb2.BidRequest{ + ID: "bid_id", + Imp: []openrtb2.Imp{ + {ID: "impid"}, + }, + Site: &openrtb2.Site{ + ID: "reqSiteId", + Page: "test/page", + Content: &openrtb2.Content{ + Data: []openrtb2.Data{ + {ID: "siteDataId1", Name: "siteDataName1"}, + }, + }, + }, + }, + output: openrtb2.BidRequest{ + ID: "bid_id", + Imp: []openrtb2.Imp{ + {ID: "impid"}, + }, + Site: &openrtb2.Site{ + ID: "reqSiteId", + Page: "test/page", + Content: &openrtb2.Content{}, + }, + }, + expectedFpdData: map[string][]openrtb2.Data{ + siteContentDataKey: {{ID: "siteDataId1", Name: "siteDataName1"}}, + userDataKey: nil, + appContentDataKey: nil, + }, + }, + { + description: "App only data present", + input: openrtb2.BidRequest{ + ID: "bid_id", + Imp: []openrtb2.Imp{ + {ID: "impid"}, + }, + App: &openrtb2.App{ + ID: "reqAppId", + Content: &openrtb2.Content{ + Data: []openrtb2.Data{ + {ID: "appDataId1", Name: "appDataName1"}, + }, + }, + }, + }, + output: openrtb2.BidRequest{ + ID: "bid_id", + Imp: []openrtb2.Imp{ + {ID: "impid"}, + }, + App: &openrtb2.App{ + ID: "reqAppId", + Content: &openrtb2.Content{}, + }, + }, + expectedFpdData: map[string][]openrtb2.Data{ + siteContentDataKey: nil, + userDataKey: nil, + appContentDataKey: {{ID: "appDataId1", Name: "appDataName1"}}, + }, + }, + { + description: "User only data present", + input: openrtb2.BidRequest{ + ID: "bid_id", + Imp: []openrtb2.Imp{ + {ID: "impid"}, + }, + Site: &openrtb2.Site{ + ID: "reqSiteId", + }, + App: &openrtb2.App{ + ID: "reqAppId", + }, + User: &openrtb2.User{ + ID: "reqUserId", + Yob: 1982, + Gender: "M", + Data: []openrtb2.Data{ + {ID: "userDataId1", Name: "userDataName1"}, + }, + }, + }, + output: openrtb2.BidRequest{ + ID: "bid_id", + Imp: []openrtb2.Imp{ + {ID: "impid"}, + }, + Site: &openrtb2.Site{ + ID: "reqSiteId", + }, + App: &openrtb2.App{ + ID: "reqAppId", + }, + User: &openrtb2.User{ + ID: "reqUserId", + Yob: 1982, + Gender: "M", + }, + }, + expectedFpdData: map[string][]openrtb2.Data{ + siteContentDataKey: nil, + userDataKey: {{ID: "userDataId1", Name: "userDataName1"}}, + appContentDataKey: nil, + }, + }, + } + for _, test := range testCases { + + inputReq := &test.input + + res := ExtractOpenRtbGlobalFPD(inputReq) + + assert.Equal(t, &test.output, inputReq, "Result request is incorrect") + assert.Equal(t, test.expectedFpdData[siteContentDataKey], res[siteContentDataKey], "siteContentData data is incorrect") + assert.Equal(t, test.expectedFpdData[userDataKey], res[userDataKey], "userData is incorrect") + assert.Equal(t, test.expectedFpdData[appContentDataKey], res[appContentDataKey], "appContentData is incorrect") + + } +} + +func TestExtractBidderConfigFPD(t *testing.T) { + + if specFiles, err := ioutil.ReadDir("./tests/extractbidderconfigfpd"); err == nil { + for _, specFile := range specFiles { + fileName := "./tests/extractbidderconfigfpd/" + specFile.Name() + + fpdFile, err := loadFpdFile(fileName) + if err != nil { + t.Errorf("Unable to load file: %s", fileName) + } + var extReq openrtb_ext.ExtRequestPrebid + err = json.Unmarshal(fpdFile.InputRequestData, &extReq) + if err != nil { + t.Errorf("Unable to unmarshal input request: %s", fileName) + } + reqExt := openrtb_ext.RequestExt{} + reqExt.SetPrebid(&extReq) + fpdData, err := ExtractBidderConfigFPD(&reqExt) + + if len(fpdFile.ValidationErrors) > 0 { + assert.Equal(t, err.Error(), fpdFile.ValidationErrors[0].Message, "Incorrect first party data error message") + continue + } + + assert.Nil(t, reqExt.GetPrebid().BidderConfigs, "Bidder specific FPD config should be removed from request") + + assert.Nil(t, err, "No error should be returned") + assert.Equal(t, len(fpdFile.BidderConfigFPD), len(fpdData), "Incorrect fpd data") + + for bidderName, bidderFPD := range fpdFile.BidderConfigFPD { + + if bidderFPD.Site != nil { + resSite := fpdData[bidderName].Site + for k, v := range bidderFPD.Site { + assert.NotNil(t, resSite[k], "Property is not found in result site") + assert.JSONEq(t, string(v), string(resSite[k]), "site is incorrect") + } + } else { + assert.Nil(t, fpdData[bidderName].Site, "Result site should be also nil") + } + + if bidderFPD.App != nil { + resApp := fpdData[bidderName].App + for k, v := range bidderFPD.App { + assert.NotNil(t, resApp[k], "Property is not found in result app") + assert.JSONEq(t, string(v), string(resApp[k]), "app is incorrect") + } + } else { + assert.Nil(t, fpdData[bidderName].App, "Result app should be also nil") + } + + if bidderFPD.User != nil { + resUser := fpdData[bidderName].User + for k, v := range bidderFPD.User { + assert.NotNil(t, resUser[k], "Property is not found in result user") + assert.JSONEq(t, string(v), string(resUser[k]), "site is incorrect") + } + } else { + assert.Nil(t, fpdData[bidderName].User, "Result user should be also nil") + } + } + } + } +} + +func TestResolveFPD(t *testing.T) { + + if specFiles, err := ioutil.ReadDir("./tests/resolvefpd"); err == nil { + for _, specFile := range specFiles { + fileName := "./tests/resolvefpd/" + specFile.Name() + + fpdFile, err := loadFpdFile(fileName) + if err != nil { + t.Errorf("Unable to load file: %s", fileName) + } + + var inputReq openrtb2.BidRequest + err = json.Unmarshal(fpdFile.InputRequestData, &inputReq) + if err != nil { + t.Errorf("Unable to unmarshal input request: %s", fileName) + } + + var inputReqCopy openrtb2.BidRequest + err = json.Unmarshal(fpdFile.InputRequestData, &inputReqCopy) + if err != nil { + t.Errorf("Unable to unmarshal input request: %s", fileName) + } + + var outputReq openrtb2.BidRequest + err = json.Unmarshal(fpdFile.OutputRequestData, &outputReq) + if err != nil { + t.Errorf("Unable to unmarshal output request: %s", fileName) + } + + reqExtFPD := make(map[string][]byte, 3) + reqExtFPD["site"] = fpdFile.GlobalFPD["site"] + reqExtFPD["app"] = fpdFile.GlobalFPD["app"] + reqExtFPD["user"] = fpdFile.GlobalFPD["user"] + + reqFPD := make(map[string][]openrtb2.Data, 3) + + reqFPDSiteContentData := fpdFile.GlobalFPD[siteContentDataKey] + if len(reqFPDSiteContentData) > 0 { + var siteConData []openrtb2.Data + err = json.Unmarshal(reqFPDSiteContentData, &siteConData) + if err != nil { + t.Errorf("Unable to unmarshal site.content.data: %s", fileName) + } + reqFPD[siteContentDataKey] = siteConData + } + + reqFPDAppContentData := fpdFile.GlobalFPD[appContentDataKey] + if len(reqFPDAppContentData) > 0 { + var appConData []openrtb2.Data + err = json.Unmarshal(reqFPDAppContentData, &appConData) + if err != nil { + t.Errorf("Unable to unmarshal app.content.data: %s", fileName) + } + reqFPD[appContentDataKey] = appConData + } + + reqFPDUserData := fpdFile.GlobalFPD[userDataKey] + if len(reqFPDUserData) > 0 { + var userData []openrtb2.Data + err = json.Unmarshal(reqFPDUserData, &userData) + if err != nil { + t.Errorf("Unable to unmarshal app.content.data: %s", fileName) + } + reqFPD[userDataKey] = userData + } + if fpdFile.BidderConfigFPD == nil { + fpdFile.BidderConfigFPD = make(map[openrtb_ext.BidderName]*openrtb_ext.ORTB2) + fpdFile.BidderConfigFPD["appnexus"] = &openrtb_ext.ORTB2{} + } + + resultFPD, errL := ResolveFPD(&inputReq, fpdFile.BidderConfigFPD, reqExtFPD, reqFPD, []string{"appnexus"}) + + if len(errL) == 0 { + assert.Equal(t, inputReq, inputReqCopy, "Original request should not be modified") + + bidderFPD := resultFPD["appnexus"] + + if outputReq.Site != nil && len(outputReq.Site.Ext) > 0 { + resSiteExt := bidderFPD.Site.Ext + expectedSiteExt := outputReq.Site.Ext + bidderFPD.Site.Ext = nil + outputReq.Site.Ext = nil + assert.JSONEq(t, string(expectedSiteExt), string(resSiteExt), "site.ext is incorrect") + + assert.Equal(t, outputReq.Site, bidderFPD.Site, "Site is incorrect") + } + if outputReq.App != nil && len(outputReq.App.Ext) > 0 { + resAppExt := bidderFPD.App.Ext + expectedAppExt := outputReq.App.Ext + bidderFPD.App.Ext = nil + outputReq.App.Ext = nil + assert.JSONEq(t, string(expectedAppExt), string(resAppExt), "app.ext is incorrect") + + assert.Equal(t, outputReq.App, bidderFPD.App, "App is incorrect") + } + if outputReq.User != nil && len(outputReq.User.Ext) > 0 { + resUserExt := bidderFPD.User.Ext + expectedUserExt := outputReq.User.Ext + bidderFPD.User.Ext = nil + outputReq.User.Ext = nil + assert.JSONEq(t, string(expectedUserExt), string(resUserExt), "user.ext is incorrect") + + assert.Equal(t, outputReq.User, bidderFPD.User, "User is incorrect") + } + } else { + assert.ElementsMatch(t, errL, fpdFile.ValidationErrors, "Incorrect first party data warning message") + } + + } + } +} + +func TestExtractFPDForBidders(t *testing.T) { + + if specFiles, err := ioutil.ReadDir("./tests/extractfpdforbidders"); err == nil { + for _, specFile := range specFiles { + fileName := "./tests/extractfpdforbidders/" + specFile.Name() + fpdFile, err := loadFpdFile(fileName) + + if err != nil { + t.Errorf("Unable to load file: %s", fileName) + } + + var expectedRequest openrtb2.BidRequest + err = json.Unmarshal(fpdFile.OutputRequestData, &expectedRequest) + if err != nil { + t.Errorf("Unable to unmarshal input request: %s", fileName) + } + + resultRequest := &openrtb_ext.RequestWrapper{} + resultRequest.BidRequest = &openrtb2.BidRequest{} + err = json.Unmarshal(fpdFile.InputRequestData, resultRequest.BidRequest) + assert.NoError(t, err, "Error should be nil") + + resultFPD, errL := ExtractFPDForBidders(resultRequest) + + if len(fpdFile.ValidationErrors) > 0 { + assert.Equal(t, len(fpdFile.ValidationErrors), len(errL), "Incorrect number of errors was returned") + assert.ElementsMatch(t, errL, fpdFile.ValidationErrors, "Incorrect errors were returned") + //in case or error no further assertions needed + continue + } + assert.Empty(t, errL, "Error should be empty") + assert.Equal(t, len(resultFPD), len(fpdFile.BiddersFPDResolved)) + + for bidderName, expectedValue := range fpdFile.BiddersFPDResolved { + actualValue := resultFPD[bidderName] + if expectedValue.Site != nil { + if len(expectedValue.Site.Ext) > 0 { + assert.JSONEq(t, string(expectedValue.Site.Ext), string(actualValue.Site.Ext), "Incorrect first party data") + expectedValue.Site.Ext = nil + actualValue.Site.Ext = nil + } + assert.Equal(t, expectedValue.Site, actualValue.Site, "Incorrect first party data") + } + if expectedValue.App != nil { + if len(expectedValue.App.Ext) > 0 { + assert.JSONEq(t, string(expectedValue.App.Ext), string(actualValue.App.Ext), "Incorrect first party data") + expectedValue.App.Ext = nil + actualValue.App.Ext = nil + } + assert.Equal(t, expectedValue.App, actualValue.App, "Incorrect first party data") + } + if expectedValue.User != nil { + if len(expectedValue.User.Ext) > 0 { + assert.JSONEq(t, string(expectedValue.User.Ext), string(actualValue.User.Ext), "Incorrect first party data") + expectedValue.User.Ext = nil + actualValue.User.Ext = nil + } + assert.Equal(t, expectedValue.User, actualValue.User, "Incorrect first party data") + } + } + + if expectedRequest.Site != nil { + if len(expectedRequest.Site.Ext) > 0 { + assert.JSONEq(t, string(expectedRequest.Site.Ext), string(resultRequest.BidRequest.Site.Ext), "Incorrect site in request") + expectedRequest.Site.Ext = nil + resultRequest.BidRequest.Site.Ext = nil + } + assert.Equal(t, expectedRequest.Site, resultRequest.BidRequest.Site, "Incorrect site in request") + } + if expectedRequest.App != nil { + if len(expectedRequest.App.Ext) > 0 { + assert.JSONEq(t, string(expectedRequest.App.Ext), string(resultRequest.BidRequest.App.Ext), "Incorrect app in request") + expectedRequest.App.Ext = nil + resultRequest.BidRequest.App.Ext = nil + } + assert.Equal(t, expectedRequest.App, resultRequest.BidRequest.App, "Incorrect app in request") + } + if expectedRequest.User != nil { + if len(expectedRequest.User.Ext) > 0 { + assert.JSONEq(t, string(expectedRequest.User.Ext), string(resultRequest.BidRequest.User.Ext), "Incorrect user in request") + expectedRequest.User.Ext = nil + resultRequest.BidRequest.User.Ext = nil + } + assert.Equal(t, expectedRequest.User, resultRequest.BidRequest.User, "Incorrect user in request") + } + + } + } +} + +func loadFpdFile(filename string) (fpdFile, error) { + var fileData fpdFile + fileContents, err := ioutil.ReadFile(filename) + if err != nil { + return fileData, err + } + err = json.Unmarshal(fileContents, &fileData) + if err != nil { + return fileData, err + } + + return fileData, nil +} + +type fpdFile struct { + InputRequestData json.RawMessage `json:"inputRequestData,omitempty"` + OutputRequestData json.RawMessage `json:"outputRequestData,omitempty"` + BidderConfigFPD map[openrtb_ext.BidderName]*openrtb_ext.ORTB2 `json:"bidderConfigFPD,omitempty"` + BiddersFPDResolved map[openrtb_ext.BidderName]*ResolvedFirstPartyData `json:"biddersFPDResolved,omitempty"` + GlobalFPD map[string]json.RawMessage `json:"globalFPD,omitempty"` + ValidationErrors []*errortypes.BadInput `json:"validationErrors,omitempty"` +} + +func TestResolveUser(t *testing.T) { + + fpdConfigUser := make(map[string]json.RawMessage, 0) + fpdConfigUser["id"] = []byte(`"fpdConfigUserId"`) + fpdConfigUser[yobKey] = []byte(`1980`) + fpdConfigUser[genderKey] = []byte(`"M"`) + fpdConfigUser[keywordsKey] = []byte(`"fpdConfigUserKeywords"`) + fpdConfigUser["data"] = []byte(`[{"id":"UserDataId1", "name":"UserDataName1"}, {"id":"UserDataId2", "name":"UserDataName2"}]`) + fpdConfigUser["ext"] = []byte(`{"data":{"fpdConfigUserExt": 123}}`) + + bidRequestUser := &openrtb2.User{ + ID: "bidRequestUserId", + Yob: 1990, + Gender: "F", + Keywords: "bidRequestUserKeywords", + } + + globalFPD := make(map[string][]byte, 0) + globalFPD[userKey] = []byte(`{"globalFPDUserData": "globalFPDUserDataValue"}`) + + openRtbGlobalFPD := make(map[string][]openrtb2.Data, 0) + openRtbGlobalFPD[userDataKey] = []openrtb2.Data{ + {ID: "openRtbGlobalFPDUserDataId1", Name: "openRtbGlobalFPDUserDataName1"}, + {ID: "openRtbGlobalFPDUserDataId2", Name: "openRtbGlobalFPDUserDataName2"}, + } + + expectedUser := &openrtb2.User{ + ID: "bidRequestUserId", + Yob: 1980, + Gender: "M", + Keywords: "fpdConfigUserKeywords", + Data: []openrtb2.Data{ + {ID: "openRtbGlobalFPDUserDataId1", Name: "openRtbGlobalFPDUserDataName1"}, + {ID: "openRtbGlobalFPDUserDataId2", Name: "openRtbGlobalFPDUserDataName2"}, + }, + } + + testCases := []struct { + description string + bidRequestUserExt []byte + expectedUserExt string + }{ + { + description: "bid request user.ext is nil", + bidRequestUserExt: nil, + expectedUserExt: `{"data":{ + "data":[ + {"id":"UserDataId1","name":"UserDataName1"}, + {"id":"UserDataId2","name":"UserDataName2"} + ], + "fpdConfigUserExt":123, + "globalFPDUserData":"globalFPDUserDataValue", + "id":"fpdConfigUserId" + } + }`, + }, + { + description: "bid request user.ext is not nil", + bidRequestUserExt: []byte(`{"bidRequestUserExt": 1234}`), + expectedUserExt: `{"data":{ + "data":[ + {"id":"UserDataId1","name":"UserDataName1"}, + {"id":"UserDataId2","name":"UserDataName2"} + ], + "fpdConfigUserExt":123, + "globalFPDUserData":"globalFPDUserDataValue", + "id":"fpdConfigUserId" + }, + "bidRequestUserExt":1234 + }`, + }, + } + + for _, test := range testCases { + bidRequestUser.Ext = test.bidRequestUserExt + + fpdConfigUser := make(map[string]json.RawMessage, 0) + fpdConfigUser["id"] = []byte(`"fpdConfigUserId"`) + fpdConfigUser[yobKey] = []byte(`1980`) + fpdConfigUser[genderKey] = []byte(`"M"`) + fpdConfigUser[keywordsKey] = []byte(`"fpdConfigUserKeywords"`) + fpdConfigUser["data"] = []byte(`[{"id":"UserDataId1", "name":"UserDataName1"}, {"id":"UserDataId2", "name":"UserDataName2"}]`) + fpdConfigUser["ext"] = []byte(`{"data":{"fpdConfigUserExt": 123}}`) + fpdConfig := &openrtb_ext.ORTB2{User: fpdConfigUser} + + resultUser, err := resolveUser(fpdConfig, bidRequestUser, globalFPD, openRtbGlobalFPD, "appnexus") + assert.NoError(t, err, "No error should be returned") + + assert.JSONEq(t, test.expectedUserExt, string(resultUser.Ext), "Result user.Ext is incorrect") + resultUser.Ext = nil + assert.Equal(t, expectedUser, resultUser, "Result user is incorrect") + } + +} + +func TestResolveUserNilValues(t *testing.T) { + resultUser, err := resolveUser(nil, nil, nil, nil, "appnexus") + assert.NoError(t, err, "No error should be returned") + assert.Nil(t, resultUser, "Result user should be nil") +} + +func TestResolveUserBadInput(t *testing.T) { + fpdConfigUser := make(map[string]json.RawMessage, 0) + fpdConfigUser["id"] = []byte(`"fpdConfigUserId"`) + fpdConfig := &openrtb_ext.ORTB2{User: fpdConfigUser} + + resultUser, err := resolveUser(fpdConfig, nil, nil, nil, "appnexus") + assert.Error(t, err, "Error should be returned") + assert.Equal(t, "incorrect First Party Data for bidder appnexus: User object is not defined in request, but defined in FPD config", err.Error(), "Incorrect error message") + assert.Nil(t, resultUser, "Result user should be nil") +} + +func TestMergeUsers(t *testing.T) { + + originalUser := &openrtb2.User{ + ID: "bidRequestUserId", + Yob: 1980, + Gender: "M", + Keywords: "fpdConfigUserKeywords", + Data: []openrtb2.Data{ + {ID: "openRtbGlobalFPDUserDataId1", Name: "openRtbGlobalFPDUserDataName1"}, + {ID: "openRtbGlobalFPDUserDataId2", Name: "openRtbGlobalFPDUserDataName2"}, + }, + Ext: []byte(`{"bidRequestUserExt": 1234}`), + } + fpdConfigUser := make(map[string]json.RawMessage, 0) + fpdConfigUser["id"] = []byte(`"fpdConfigUserId"`) + fpdConfigUser[yobKey] = []byte(`1980`) + fpdConfigUser[genderKey] = []byte(`"M"`) + fpdConfigUser[keywordsKey] = []byte(`"fpdConfigUserKeywords"`) + fpdConfigUser["data"] = []byte(`[{"id":"UserDataId1", "name":"UserDataName1"}, {"id":"UserDataId2", "name":"UserDataName2"}]`) + fpdConfigUser["ext"] = []byte(`{"data":{"fpdConfigUserExt": 123}}`) + + resultUser, err := mergeUsers(originalUser, fpdConfigUser) + assert.NoError(t, err, "No error should be returned") + + expectedUserExt := `{"bidRequestUserExt":1234, + "data":{ + "data":[ + {"id":"UserDataId1","name":"UserDataName1"}, + {"id":"UserDataId2","name":"UserDataName2"}], + "fpdConfigUserExt":123, + "id":"fpdConfigUserId"} + }` + assert.JSONEq(t, expectedUserExt, string(resultUser.Ext), "Result user.Ext is incorrect") + resultUser.Ext = nil + + expectedUser := openrtb2.User{ + ID: "bidRequestUserId", + Yob: 1980, + Gender: "M", + Keywords: "fpdConfigUserKeywords", + Data: []openrtb2.Data{ + {ID: "openRtbGlobalFPDUserDataId1", Name: "openRtbGlobalFPDUserDataName1"}, + {ID: "openRtbGlobalFPDUserDataId2", Name: "openRtbGlobalFPDUserDataName2"}, + }, + } + assert.Equal(t, expectedUser, resultUser, "Result user is incorrect") +} + +func TestResolveExtension(t *testing.T) { + + testCases := []struct { + description string + fpdConfig map[string]json.RawMessage + originalExt json.RawMessage + expectedExt string + }{ + {description: "Fpd config with ext only", + fpdConfig: map[string]json.RawMessage{"ext": json.RawMessage(`{"data":{"fpdConfigUserExt": 123}}`)}, + originalExt: json.RawMessage(`{"bidRequestUserExt": 1234}`), + expectedExt: `{"bidRequestUserExt":1234, "data":{"fpdConfigUserExt":123}}`, + }, + {description: "Fpd config with ext and another property", + fpdConfig: map[string]json.RawMessage{"ext": json.RawMessage(`{"data":{"fpdConfigUserExt": 123}}`), "prebid": json.RawMessage(`{"prebidData":{"isPrebid": true}}`)}, + originalExt: json.RawMessage(`{"bidRequestUserExt": 1234}`), + expectedExt: `{"bidRequestUserExt":1234, "data":{"fpdConfigUserExt":123, "prebid":{"prebidData":{"isPrebid": true}}}}`, + }, + {description: "Fpd config empty", + fpdConfig: nil, + originalExt: json.RawMessage(`{"bidRequestUserExt": 1234}`), + expectedExt: `{"bidRequestUserExt":1234}`, + }, + {description: "Original ext empty", + fpdConfig: map[string]json.RawMessage{"ext": json.RawMessage(`{"data":{"fpdConfigUserExt": 123}}`)}, + originalExt: nil, + expectedExt: `{"data":{"ext":{"data":{"fpdConfigUserExt":123}}}}`, + }, + } + + for _, test := range testCases { + resExt, err := resolveExtension(test.fpdConfig, test.originalExt) + assert.NoError(t, err, "No error should be returned") + assert.JSONEq(t, test.expectedExt, string(resExt), "result ext is incorrect") + } +} + +func TestResolveSite(t *testing.T) { + + fpdConfigSite := make(map[string]json.RawMessage, 0) + fpdConfigSite["id"] = []byte(`"fpdConfigSiteId"`) + fpdConfigSite[keywordsKey] = []byte(`"fpdConfigSiteKeywords"`) + fpdConfigSite[nameKey] = []byte(`"fpdConfigSiteName"`) + fpdConfigSite[pageKey] = []byte(`"fpdConfigSitePage"`) + fpdConfigSite["data"] = []byte(`[{"id":"SiteDataId1", "name":"SiteDataName1"}, {"id":"SiteDataId2", "name":"SiteDataName2"}]`) + fpdConfigSite["ext"] = []byte(`{"data":{"fpdConfigSiteExt": 123}}`) + + bidRequestSite := &openrtb2.Site{ + ID: "bidRequestSiteId", + Keywords: "bidRequestSiteKeywords", + Name: "bidRequestSiteName", + Page: "bidRequestSitePage", + Content: &openrtb2.Content{ + ID: "bidRequestSiteContentId", + Episode: 4, + Data: []openrtb2.Data{ + {ID: "bidRequestSiteContentDataId1", Name: "bidRequestSiteContentDataName1"}, + {ID: "bidRequestSiteContentDataId2", Name: "bidRequestSiteContentDataName2"}, + }, + }, + } + + globalFPD := make(map[string][]byte, 0) + globalFPD[siteKey] = []byte(`{"globalFPDSiteData": "globalFPDSiteDataValue"}`) + + openRtbGlobalFPD := make(map[string][]openrtb2.Data, 0) + openRtbGlobalFPD[siteContentDataKey] = []openrtb2.Data{ + {ID: "openRtbGlobalFPDSiteContentDataId1", Name: "openRtbGlobalFPDSiteContentDataName1"}, + {ID: "openRtbGlobalFPDSiteContentDataId2", Name: "openRtbGlobalFPDSiteContentDataName2"}, + } + + expectedSite := &openrtb2.Site{ + ID: "bidRequestSiteId", + Keywords: "fpdConfigSiteKeywords", + Name: "bidRequestSiteName", + Page: "bidRequestSitePage", + Content: &openrtb2.Content{ + ID: "bidRequestSiteContentId", + Episode: 4, + Data: []openrtb2.Data{ + {ID: "openRtbGlobalFPDSiteContentDataId1", Name: "openRtbGlobalFPDSiteContentDataName1"}, + {ID: "openRtbGlobalFPDSiteContentDataId2", Name: "openRtbGlobalFPDSiteContentDataName2"}, + }, + }, + } + + testCases := []struct { + description string + bidRequestSiteExt []byte + expectedSiteExt string + siteContentNil bool + }{ + { + description: "bid request site.ext is nil", + bidRequestSiteExt: nil, + expectedSiteExt: `{"data":{ + "data":[ + {"id":"SiteDataId1","name":"SiteDataName1"}, + {"id":"SiteDataId2","name":"SiteDataName2"} + ], + "fpdConfigSiteExt":123, + "globalFPDSiteData":"globalFPDSiteDataValue", + "id":"fpdConfigSiteId" + } + }`, + siteContentNil: false, + }, + { + description: "bid request site.ext is not nil", + bidRequestSiteExt: []byte(`{"bidRequestSiteExt": 1234}`), + expectedSiteExt: `{"data":{ + "data":[ + {"id":"SiteDataId1","name":"SiteDataName1"}, + {"id":"SiteDataId2","name":"SiteDataName2"} + ], + "fpdConfigSiteExt":123, + "globalFPDSiteData":"globalFPDSiteDataValue", + "id":"fpdConfigSiteId" + }, + "bidRequestSiteExt":1234 + }`, + siteContentNil: false, + }, + { + description: "bid request site.content.data is nil ", + bidRequestSiteExt: []byte(`{"bidRequestSiteExt": 1234}`), + expectedSiteExt: `{"data":{ + "data":[ + {"id":"SiteDataId1","name":"SiteDataName1"}, + {"id":"SiteDataId2","name":"SiteDataName2"} + ], + "fpdConfigSiteExt":123, + "globalFPDSiteData":"globalFPDSiteDataValue", + "id":"fpdConfigSiteId" + }, + "bidRequestSiteExt":1234 + }`, + siteContentNil: true, + }, + } + + for _, test := range testCases { + if test.siteContentNil { + bidRequestSite.Content = nil + expectedSite.Content = &openrtb2.Content{Data: []openrtb2.Data{ + {ID: "openRtbGlobalFPDSiteContentDataId1", Name: "openRtbGlobalFPDSiteContentDataName1"}, + {ID: "openRtbGlobalFPDSiteContentDataId2", Name: "openRtbGlobalFPDSiteContentDataName2"}, + }} + } + + bidRequestSite.Ext = test.bidRequestSiteExt + + fpdConfigSite := make(map[string]json.RawMessage, 0) + fpdConfigSite["id"] = []byte(`"fpdConfigSiteId"`) + fpdConfigSite[keywordsKey] = []byte(`"fpdConfigSiteKeywords"`) + fpdConfigSite["data"] = []byte(`[{"id":"SiteDataId1", "name":"SiteDataName1"}, {"id":"SiteDataId2", "name":"SiteDataName2"}]`) + fpdConfigSite["ext"] = []byte(`{"data":{"fpdConfigSiteExt": 123}}`) + fpdConfig := &openrtb_ext.ORTB2{Site: fpdConfigSite} + + resultSite, err := resolveSite(fpdConfig, bidRequestSite, globalFPD, openRtbGlobalFPD, "appnexus") + assert.NoError(t, err, "No error should be returned") + + assert.JSONEq(t, test.expectedSiteExt, string(resultSite.Ext), "Result site.Ext is incorrect") + resultSite.Ext = nil + assert.Equal(t, expectedSite, resultSite, "Result site is incorrect") + } + +} + +func TestResolveSiteNilValues(t *testing.T) { + resultSite, err := resolveSite(nil, nil, nil, nil, "appnexus") + assert.NoError(t, err, "No error should be returned") + assert.Nil(t, resultSite, "Result site should be nil") +} + +func TestResolveSiteBadInput(t *testing.T) { + fpdConfigSite := make(map[string]json.RawMessage, 0) + fpdConfigSite["id"] = []byte(`"fpdConfigSiteId"`) + fpdConfig := &openrtb_ext.ORTB2{Site: fpdConfigSite} + + resultSite, err := resolveSite(fpdConfig, nil, nil, nil, "appnexus") + assert.Error(t, err, "Error should be returned") + assert.Equal(t, "incorrect First Party Data for bidder appnexus: Site object is not defined in request, but defined in FPD config", err.Error(), "Incorrect error message") + assert.Nil(t, resultSite, "Result site should be nil") +} + +func TestMergeSites(t *testing.T) { + + originalSite := &openrtb2.Site{ + ID: "bidRequestSiteId", + Keywords: "bidRequestSiteKeywords", + Page: "bidRequestSitePage", + Name: "bidRequestSiteName", + Domain: "bidRequestSiteDomain", + Cat: []string{"books1", "magazines1"}, + SectionCat: []string{"books2", "magazines2"}, + PageCat: []string{"books3", "magazines3"}, + Search: "bidRequestSiteSearch", + Ref: "bidRequestSiteRef", + Content: &openrtb2.Content{ + Title: "bidRequestSiteContentTitle", + Data: []openrtb2.Data{ + {ID: "openRtbGlobalFPDSiteDataId1", Name: "openRtbGlobalFPDSiteDataName1"}, + {ID: "openRtbGlobalFPDSiteDataId2", Name: "openRtbGlobalFPDSiteDataName2"}, + }, + }, + Ext: []byte(`{"bidRequestSiteExt": 1234}`), + } + fpdConfigSite := make(map[string]json.RawMessage, 0) + fpdConfigSite["id"] = []byte(`"fpdConfigSiteId"`) + fpdConfigSite[keywordsKey] = []byte(`"fpdConfigSiteKeywords"`) + fpdConfigSite[pageKey] = []byte(`"fpdConfigSitePage"`) + fpdConfigSite[nameKey] = []byte(`"fpdConfigSiteName"`) + fpdConfigSite[domainKey] = []byte(`"fpdConfigSiteDomain"`) + fpdConfigSite[catKey] = []byte(`["cars1", "auto1"]`) + fpdConfigSite[sectionCatKey] = []byte(`["cars2", "auto2"]`) + fpdConfigSite[pageCatKey] = []byte(`["cars3", "auto3"]`) + fpdConfigSite[searchKey] = []byte(`"fpdConfigSiteSearch"`) + fpdConfigSite[refKey] = []byte(`"fpdConfigSiteRef"`) + fpdConfigSite["data"] = []byte(`[{"id":"SiteDataId1", "name":"SiteDataName1"}, {"id":"SiteDataId2", "name":"SiteDataName2"}]`) + fpdConfigSite["ext"] = []byte(`{"data":{"fpdConfigSiteExt": 123}}`) + + resultSite, err := mergeSites(originalSite, fpdConfigSite, "appnexus") + assert.NoError(t, err, "No error should be returned") + + expectedSiteExt := `{"bidRequestSiteExt":1234, + "data":{ + "data":[ + {"id":"SiteDataId1","name":"SiteDataName1"}, + {"id":"SiteDataId2","name":"SiteDataName2"}], + "fpdConfigSiteExt":123, + "id":"fpdConfigSiteId"} + }` + assert.JSONEq(t, expectedSiteExt, string(resultSite.Ext), "Result user.Ext is incorrect") + resultSite.Ext = nil + + expectedSite := openrtb2.Site{ + ID: "bidRequestSiteId", + Keywords: "fpdConfigSiteKeywords", + Page: "fpdConfigSitePage", + Name: "fpdConfigSiteName", + Domain: "fpdConfigSiteDomain", + Cat: []string{"cars1", "auto1"}, + SectionCat: []string{"cars2", "auto2"}, + PageCat: []string{"cars3", "auto3"}, + Search: "fpdConfigSiteSearch", + Ref: "fpdConfigSiteRef", + Content: &openrtb2.Content{ + Title: "bidRequestSiteContentTitle", + Data: []openrtb2.Data{ + {ID: "openRtbGlobalFPDSiteDataId1", Name: "openRtbGlobalFPDSiteDataName1"}, + {ID: "openRtbGlobalFPDSiteDataId2", Name: "openRtbGlobalFPDSiteDataName2"}, + }, + }, + Ext: nil, + } + assert.Equal(t, expectedSite, resultSite, "Result user is incorrect") +} + +func TestResolveApp(t *testing.T) { + + fpdConfigApp := make(map[string]json.RawMessage, 0) + fpdConfigApp["id"] = []byte(`"fpdConfigAppId"`) + fpdConfigApp[keywordsKey] = []byte(`"fpdConfigAppKeywords"`) + fpdConfigApp[nameKey] = []byte(`"fpdConfigAppName"`) + fpdConfigApp[bundleKey] = []byte(`"fpdConfigAppBundle"`) + fpdConfigApp["data"] = []byte(`[{"id":"AppDataId1", "name":"AppDataName1"}, {"id":"AppDataId2", "name":"AppDataName2"}]`) + fpdConfigApp["ext"] = []byte(`{"data":{"fpdConfigAppExt": 123}}`) + + bidRequestApp := &openrtb2.App{ + ID: "bidRequestAppId", + Keywords: "bidRequestAppKeywords", + Name: "bidRequestAppName", + Bundle: "bidRequestAppBundle", + Content: &openrtb2.Content{ + ID: "bidRequestAppContentId", + Episode: 4, + Data: []openrtb2.Data{ + {ID: "bidRequestAppContentDataId1", Name: "bidRequestAppContentDataName1"}, + {ID: "bidRequestAppContentDataId2", Name: "bidRequestAppContentDataName2"}, + }, + }, + } + + globalFPD := make(map[string][]byte, 0) + globalFPD[appKey] = []byte(`{"globalFPDAppData": "globalFPDAppDataValue"}`) + + openRtbGlobalFPD := make(map[string][]openrtb2.Data, 0) + openRtbGlobalFPD[appContentDataKey] = []openrtb2.Data{ + {ID: "openRtbGlobalFPDAppContentDataId1", Name: "openRtbGlobalFPDAppContentDataName1"}, + {ID: "openRtbGlobalFPDAppContentDataId2", Name: "openRtbGlobalFPDAppContentDataName2"}, + } + + expectedApp := &openrtb2.App{ + ID: "bidRequestAppId", + Keywords: "fpdConfigAppKeywords", + Name: "bidRequestAppName", + Bundle: "bidRequestAppBundle", + Content: &openrtb2.Content{ + ID: "bidRequestAppContentId", + Episode: 4, + Data: []openrtb2.Data{ + {ID: "openRtbGlobalFPDAppContentDataId1", Name: "openRtbGlobalFPDAppContentDataName1"}, + {ID: "openRtbGlobalFPDAppContentDataId2", Name: "openRtbGlobalFPDAppContentDataName2"}, + }, + }, + } + + testCases := []struct { + description string + bidRequestAppExt []byte + expectedAppExt string + appContentNil bool + }{ + { + description: "bid request app.ext is nil", + bidRequestAppExt: nil, + expectedAppExt: `{"data":{ + "data":[ + {"id":"AppDataId1","name":"AppDataName1"}, + {"id":"AppDataId2","name":"AppDataName2"} + ], + "fpdConfigAppExt":123, + "globalFPDAppData":"globalFPDAppDataValue", + "id":"fpdConfigAppId" + } + }`, + appContentNil: false, + }, + { + description: "bid request app.ext is not nil", + bidRequestAppExt: []byte(`{"bidRequestAppExt": 1234}`), + expectedAppExt: `{"data":{ + "data":[ + {"id":"AppDataId1","name":"AppDataName1"}, + {"id":"AppDataId2","name":"AppDataName2"} + ], + "fpdConfigAppExt":123, + "globalFPDAppData":"globalFPDAppDataValue", + "id":"fpdConfigAppId" + }, + "bidRequestAppExt":1234 + }`, + appContentNil: false, + }, + { + description: "bid request app.content.data is nil ", + bidRequestAppExt: []byte(`{"bidRequestAppExt": 1234}`), + expectedAppExt: `{"data":{ + "data":[ + {"id":"AppDataId1","name":"AppDataName1"}, + {"id":"AppDataId2","name":"AppDataName2"} + ], + "fpdConfigAppExt":123, + "globalFPDAppData":"globalFPDAppDataValue", + "id":"fpdConfigAppId" + }, + "bidRequestAppExt":1234 + }`, + appContentNil: true, + }, + } + + for _, test := range testCases { + if test.appContentNil { + bidRequestApp.Content = nil + expectedApp.Content = &openrtb2.Content{Data: []openrtb2.Data{ + {ID: "openRtbGlobalFPDAppContentDataId1", Name: "openRtbGlobalFPDAppContentDataName1"}, + {ID: "openRtbGlobalFPDAppContentDataId2", Name: "openRtbGlobalFPDAppContentDataName2"}, + }} + } + + bidRequestApp.Ext = test.bidRequestAppExt + + fpdConfigApp := make(map[string]json.RawMessage, 0) + fpdConfigApp["id"] = []byte(`"fpdConfigAppId"`) + fpdConfigApp[keywordsKey] = []byte(`"fpdConfigAppKeywords"`) + fpdConfigApp["data"] = []byte(`[{"id":"AppDataId1", "name":"AppDataName1"}, {"id":"AppDataId2", "name":"AppDataName2"}]`) + fpdConfigApp["ext"] = []byte(`{"data":{"fpdConfigAppExt": 123}}`) + fpdConfig := &openrtb_ext.ORTB2{App: fpdConfigApp} + + resultApp, err := resolveApp(fpdConfig, bidRequestApp, globalFPD, openRtbGlobalFPD, "appnexus") + assert.NoError(t, err, "No error should be returned") + + assert.JSONEq(t, test.expectedAppExt, string(resultApp.Ext), "Result app.Ext is incorrect") + resultApp.Ext = nil + assert.Equal(t, expectedApp, resultApp, "Result app is incorrect") + } + +} + +func TestResolveAppNilValues(t *testing.T) { + resultApp, err := resolveApp(nil, nil, nil, nil, "appnexus") + assert.NoError(t, err, "No error should be returned") + assert.Nil(t, resultApp, "Result app should be nil") +} + +func TestResolveAppBadInput(t *testing.T) { + fpdConfigApp := make(map[string]json.RawMessage, 0) + fpdConfigApp["id"] = []byte(`"fpdConfigAppId"`) + fpdConfig := &openrtb_ext.ORTB2{App: fpdConfigApp} + + resultApp, err := resolveApp(fpdConfig, nil, nil, nil, "appnexus") + assert.Error(t, err, "Error should be returned") + assert.Equal(t, "incorrect First Party Data for bidder appnexus: App object is not defined in request, but defined in FPD config", err.Error(), "Incorrect error message") + assert.Nil(t, resultApp, "Result app should be nil") +} + +func TestMergeApps(t *testing.T) { + + originalApp := &openrtb2.App{ + ID: "bidRequestAppId", + Keywords: "bidRequestAppKeywords", + Name: "bidRequestAppName", + Domain: "bidRequestAppDomain", + Bundle: "bidRequestAppBundle", + StoreURL: "bidRequestAppStoreUrl", + Ver: "bidRequestAppVer", + Cat: []string{"books1", "magazines1"}, + SectionCat: []string{"books2", "magazines2"}, + PageCat: []string{"books3", "magazines3"}, + Content: &openrtb2.Content{ + Title: "bidRequestAppContentTitle", + Data: []openrtb2.Data{ + {ID: "openRtbGlobalFPDAppDataId1", Name: "openRtbGlobalFPDAppDataName1"}, + {ID: "openRtbGlobalFPDAppDataId2", Name: "openRtbGlobalFPDAppDataName2"}, + }, + }, + Ext: []byte(`{"bidRequestAppExt": 1234}`), + } + fpdConfigApp := make(map[string]json.RawMessage, 0) + fpdConfigApp["id"] = []byte(`"fpdConfigAppId"`) + fpdConfigApp[keywordsKey] = []byte(`"fpdConfigAppKeywords"`) + fpdConfigApp[nameKey] = []byte(`"fpdConfigAppName"`) + fpdConfigApp[domainKey] = []byte(`"fpdConfigAppDomain"`) + fpdConfigApp[bundleKey] = []byte(`"fpdConfigAppBundle"`) + fpdConfigApp[storeUrlKey] = []byte(`"fpdConfigAppStoreUrl"`) + fpdConfigApp[verKey] = []byte(`"fpdConfigAppVer"`) + fpdConfigApp[catKey] = []byte(`["cars1", "auto1"]`) + fpdConfigApp[sectionCatKey] = []byte(`["cars2", "auto2"]`) + fpdConfigApp[pageCatKey] = []byte(`["cars3", "auto3"]`) + fpdConfigApp["data"] = []byte(`[{"id":"AppDataId1", "name":"AppDataName1"}, {"id":"AppDataId2", "name":"AppDataName2"}]`) + fpdConfigApp["ext"] = []byte(`{"data":{"fpdConfigAppExt": 123}}`) + + resultApp, err := mergeApps(originalApp, fpdConfigApp) + assert.NoError(t, err, "No error should be returned") + + expectedAppExt := `{"bidRequestAppExt":1234, + "data":{ + "data":[ + {"id":"AppDataId1","name":"AppDataName1"}, + {"id":"AppDataId2","name":"AppDataName2"}], + "fpdConfigAppExt":123, + "id":"fpdConfigAppId"} + }` + assert.JSONEq(t, expectedAppExt, string(resultApp.Ext), "Result user.Ext is incorrect") + resultApp.Ext = nil + + expectedApp := openrtb2.App{ + ID: "bidRequestAppId", + Keywords: "fpdConfigAppKeywords", + Name: "fpdConfigAppName", + Domain: "fpdConfigAppDomain", + Bundle: "fpdConfigAppBundle", + Ver: "fpdConfigAppVer", + StoreURL: "fpdConfigAppStoreUrl", + Cat: []string{"cars1", "auto1"}, + SectionCat: []string{"cars2", "auto2"}, + PageCat: []string{"cars3", "auto3"}, + Content: &openrtb2.Content{ + Title: "bidRequestAppContentTitle", + Data: []openrtb2.Data{ + {ID: "openRtbGlobalFPDAppDataId1", Name: "openRtbGlobalFPDAppDataName1"}, + {ID: "openRtbGlobalFPDAppDataId2", Name: "openRtbGlobalFPDAppDataName2"}, + }, + }, + Ext: nil, + } + assert.Equal(t, expectedApp, resultApp, "Result user is incorrect") +} + +func TestBuildExtData(t *testing.T) { + testCases := []struct { + description string + input []byte + expectedRes string + }{ + { + description: "Input object with int value", + input: []byte(`{"someData": 123}`), + expectedRes: `{"data": {"someData": 123}}`, + }, + { + description: "Input object with bool value", + input: []byte(`{"someData": true}`), + expectedRes: `{"data": {"someData": true}}`, + }, + { + description: "Input object with string value", + input: []byte(`{"someData": "true"}`), + expectedRes: `{"data": {"someData": "true"}}`, + }, + { + description: "No input object", + input: []byte(`{}`), + expectedRes: `{"data": {}}`, + }, + { + description: "Input object with object value", + input: []byte(`{"someData": {"moreFpdData": "fpddata"}}`), + expectedRes: `{"data": {"someData": {"moreFpdData": "fpddata"}}}`, + }, + } + + for _, test := range testCases { + actualRes := buildExtData(test.input) + assert.JSONEq(t, test.expectedRes, string(actualRes), "Incorrect result data") + } +} diff --git a/firstpartydata/tests/extractbidderconfigfpd/bidder-config-config-null.json b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-config-null.json new file mode 100644 index 00000000000..6d2d54f6508 --- /dev/null +++ b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-config-null.json @@ -0,0 +1,20 @@ +{ + "description": "Bidder config for bidder is null - expected bidder fpd with no data", + "inputRequestData": { + "data": {}, + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": null + } + ] + }, + "outputRequestData": { + }, + "bidderConfigFPD": { + "appnexus": {} + } +} + diff --git a/firstpartydata/tests/extractbidderconfigfpd/bidder-config-config-ortb-null.json b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-config-ortb-null.json new file mode 100644 index 00000000000..3bafae6dc87 --- /dev/null +++ b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-config-ortb-null.json @@ -0,0 +1,22 @@ +{ + "description": "Bidder config.ortb2 for bidder is null - expected bidder fpd with no data", + "inputRequestData": { + "data": {}, + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": { + "ortb2": null + } + } + ] + }, + "outputRequestData": { + }, + "bidderConfigFPD": { + "appnexus": { + } + } +} diff --git a/firstpartydata/tests/extractbidderconfigfpd/bidder-config-duplicated.json b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-duplicated.json new file mode 100644 index 00000000000..356b84aac35 --- /dev/null +++ b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-duplicated.json @@ -0,0 +1,63 @@ +{ + "description": "Verifies error presence in case more than one bidder config specified for the same bidder", + "inputRequestData": { + "data": {}, + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": { + "ortb2": { + "site": { + "id": "apnSiteId", + "ext": { + "data": { + "sitefpddata": "sitefpddata", + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata" + } + } + } + } + } + }, + { + "bidders": [ + "appnexus", + "telaria", + "testBidder2" + ], + "config": { + "ortb2": { + "user": { + "id": "telariaUserData", + "ext": { + "data": { + "userdata": "fpduserdata" + } + } + }, + "app": { + "id": "telariaAppData", + "ext": { + "data": { + "appdata": "fpdappdata" + } + } + } + } + } + } + ] + }, + "outputRequestData": {}, + "bidderConfigFPD": {}, + "validationErrors": [ + {"Message": "multiple First Party Data bidder configs provided for bidder: appnexus"} + ] +} + + diff --git a/firstpartydata/tests/extractbidderconfigfpd/bidder-config-empty.json b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-empty.json new file mode 100644 index 00000000000..d127e5df15f --- /dev/null +++ b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-empty.json @@ -0,0 +1,11 @@ +{ + "description": "Bidder config is empty - no bidder fpd expected for any bidders", + "inputRequestData": { + "data": {}, + "bidderconfig": [] + }, + "outputRequestData": {}, + "bidderConfigFPD": {} +} + + diff --git a/firstpartydata/tests/extractbidderconfigfpd/bidder-config-multiple-bidder.json b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-multiple-bidder.json new file mode 100644 index 00000000000..48ea27a7c42 --- /dev/null +++ b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-multiple-bidder.json @@ -0,0 +1,109 @@ +{ + "description": "Extracts bidder configs for multiple bidders, one config has two bidders; expect to have bidder FPD configs for all bidders", + "inputRequestData": { + "data": {}, + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": { + "ortb2": { + "site": { + "id": "apnSiteId", + "ext": { + "data": { + "sitefpddata": "sitefpddata", + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata" + } + } + } + } + } + }, + { + "bidders": [ + "telaria", + "testBidder2" + ], + "config": { + "ortb2": { + "user": { + "id": "telariaUserData", + "ext": { + "data": { + "userdata": "fpduserdata" + } + } + }, + "app": { + "id": "telariaAppData", + "ext": { + "data": { + "appdata": "fpdappdata" + } + } + } + } + } + } + ] + }, + "outputRequestData": { + }, + "bidderConfigFPD": { + "appnexus": { + "site": { + "id": "apnSiteId", + "ext": { + "data": { + "sitefpddata": "sitefpddata", + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata" + } + } + } + }, + "telaria": { + "user": { + "id": "telariaUserData", + "ext": { + "data": { + "userdata": "fpduserdata" + } + } + }, + "app": { + "id": "telariaAppData", + "ext": { + "data": { + "appdata": "fpdappdata" + } + } + } + }, + "testBidder2": { + "user": { + "id": "telariaUserData", + "ext": { + "data": { + "userdata": "fpduserdata" + } + } + }, + "app": { + "id": "telariaAppData", + "ext": { + "data": { + "appdata": "fpdappdata" + } + } + } + } + } +} diff --git a/firstpartydata/tests/extractbidderconfigfpd/bidder-config-not-specified.json b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-not-specified.json new file mode 100644 index 00000000000..d3dfc5b0943 --- /dev/null +++ b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-not-specified.json @@ -0,0 +1,7 @@ +{ + "description": "Bidder config not specified", + "inputRequestData": {}, + "outputRequestData": {}, + "bidderConfigFPD": {} +} + diff --git a/firstpartydata/tests/extractbidderconfigfpd/bidder-config-null.json b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-null.json new file mode 100644 index 00000000000..1063f7ecf40 --- /dev/null +++ b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-null.json @@ -0,0 +1,11 @@ +{ + "description": "Bidder config is null - no bidder fpd expected", + "inputRequestData": { + "data": {}, + "bidderconfig": null + }, + "outputRequestData": {}, + "bidderConfigFPD": {} +} + + diff --git a/firstpartydata/tests/extractbidderconfigfpd/bidder-list-specified-only.json b/firstpartydata/tests/extractbidderconfigfpd/bidder-list-specified-only.json new file mode 100644 index 00000000000..6c1938bc7c0 --- /dev/null +++ b/firstpartydata/tests/extractbidderconfigfpd/bidder-list-specified-only.json @@ -0,0 +1,15 @@ +{ + "description": "Only global bidder list specified - no bidder fpds expected", + "inputRequestData": { + "data": { + "bidders": [ + "appnexus", + "telaria", + "testBidder" + ] + } + }, + "outputRequestData": {}, + "bidderConfigFPD": {} +} + diff --git a/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-app-content-data.json b/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-app-content-data.json new file mode 100644 index 00000000000..797846ee7a6 --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-app-content-data.json @@ -0,0 +1,87 @@ +{ + "description": "req.app.content.data defined; req.ext.prebid.data.bidders contains appnexus; expect req.app.content to be in the resolved FPD for appnexus but extracted from the original request", + "inputRequestData": { + "id": "bid_id", + "app": { + "id": "reqAppId", + "domain": "http://www.foobar.com/1234.html", + "name": "reqAppName", + "cat": [ + "electronics", + "phone" + ], + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason", + "data": [ + { + "id": "reqContentDataId1", + "name": "reqContentDataName1" + }, + { + "id": "reqContentDataId2", + "name": "reqContentDataName2" + } + ] + } + }, + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": [ + "appnexus" + ] + } + } + } + }, + "outputRequestData": { + "id": "bid_id", + "app": { + "id": "reqAppId", + "domain": "http://www.foobar.com/1234.html", + "name": "reqAppName", + "cat": [ + "electronics", + "phone" + ], + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason" + } + }, + "test": 1 + }, + "biddersFPDResolved": { + "appnexus": { + "app": { + "id": "reqAppId", + "domain": "http://www.foobar.com/1234.html", + "name": "reqAppName", + "cat": [ + "electronics", + "phone" + ], + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason", + "data": [ + { + "id": "reqContentDataId1", + "name": "reqContentDataName1" + }, + { + "id": "reqContentDataId2", + "name": "reqContentDataName2" + } + ] + } + } + } + }, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-app.json b/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-app.json new file mode 100644 index 00000000000..4f312b3dc01 --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-app.json @@ -0,0 +1,80 @@ +{ + "description": "req.app and req.ext.prebid.data.bidders contains appnexus; expect bidder config with app data to be included in the resolved FPD for appnexus", + "inputRequestData": { + "id": "bid_id", + "app": { + "id": "reqAppId", + "publisher": { + "id": "1" + } + }, + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": [ + "appnexus" + ] + }, + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": { + "ortb2": { + "app": { + "id": "apnAppId", + "name": "fpdAppName", + "bundle": "fpdAppBundle", + "domain": "fpdAppDomain", + "storeurl": "fpdAppstoreUrl", + "cat": ["books"], + "sectioncat": ["books"], + "pagecat": ["magazines"], + "keywords": "apnKeywords", + "ver": "1.2" + } + } + } + } + ] + } + } + }, + "outputRequestData": { + "id": "bid_id", + "app": { + "id": "reqAppId", + "publisher": { + "id": "1" + } + }, + "test": 1 + }, + "biddersFPDResolved": { + "appnexus": { + "app": { + "id": "reqAppId", + "name": "fpdAppName", + "bundle": "fpdAppBundle", + "domain": "fpdAppDomain", + "storeurl": "fpdAppstoreUrl", + "cat": ["books"], + "sectioncat": ["books"], + "pagecat": ["magazines"], + "keywords": "apnKeywords", + "ver": "1.2", + "publisher": { + "id": "1" + }, + "ext": { + "data": { + "id": "apnAppId" + } + } + } + } + }, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-data-user.json b/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-data-user.json new file mode 100644 index 00000000000..368be1e591a --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-data-user.json @@ -0,0 +1,63 @@ +{ + "description": "req.user.data defined; req.ext.prebid.data.bidders contains appnexus; expect req.user.data to be included in the appnexus resolved FPD but extracted from the original request", + "inputRequestData": { + "id": "bid_id", + "user": { + "id": "reqUserID", + "keywords": "userKeyword", + "yob": 1982, + "gender": "M", + "data": [ + { + "id": "userDataId1", + "name": "userDataName1" + }, + { + "id": "userDataId2", + "name": "userDataName2" + } + ] + }, + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": [ + "appnexus" + ] + } + } + } + }, + "outputRequestData": { + "id": "bid_id", + "user": { + "id": "reqUserID", + "keywords": "userKeyword", + "yob": 1982, + "gender": "M" + }, + "test": 1 + }, + "biddersFPDResolved": { + "appnexus": { + "user": { + "id": "reqUserID", + "keywords": "userKeyword", + "yob": 1982, + "gender": "M", + "data": [ + { + "id": "userDataId1", + "name": "userDataName1" + }, + { + "id": "userDataId2", + "name": "userDataName2" + } + ] + } + } + }, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-site-content-data.json b/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-site-content-data.json new file mode 100644 index 00000000000..ea20afb5cd0 --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-site-content-data.json @@ -0,0 +1,96 @@ +{ + "description": "req.site.content.data defined; req.ext.prebid.data.bidders contains appnexus; expect req.site.content.data to be included in the appnexus resolved FPD but extracted from the original request", + "inputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "name": "reqSiteName", + "cat": [ + "electronics", + "phone" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason", + "data": [ + { + "id": "reqContentDataId1", + "name": "reqContentDataName1" + }, + { + "id": "reqContentDataId2", + "name": "reqContentDataName2" + } + ] + } + }, + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": [ + "appnexus" + ] + } + } + } + }, + "outputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "name": "reqSiteName", + "cat": [ + "electronics", + "phone" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason" + } + }, + "test": 1 + }, + "biddersFPDResolved": { + "appnexus": { + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "name": "reqSiteName", + "cat": [ + "electronics", + "phone" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason", + "data": [ + { + "id": "reqContentDataId1", + "name": "reqContentDataName1" + }, + { + "id": "reqContentDataId2", + "name": "reqContentDataName2" + } + ] + } + } + } + }, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-site.json b/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-site.json new file mode 100644 index 00000000000..5fc7c0d7cda --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-site.json @@ -0,0 +1,74 @@ +{ + "description": "req.site defined; req.ext.prebid.data.bidders contains appnexus and contains site configuration; expect bidder config with site data to be included in the resolved FPD for appnexus and to have been extracted from the original request", + "inputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + } + }, + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": [ + "appnexus" + ] + }, + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": { + "ortb2": { + "site": { + "id": "apnSiteId", + "keywords": "apnKeywords", + "sectioncat": ["books"], + "search": "books", + "pagecat": ["magazines"], + "page": "http://www.foobar.com/testurl.html" + } + } + } + } + ] + } + } + }, + "outputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + } + }, + "test": 1 + }, + "biddersFPDResolved": { + "appnexus": { + "site": { + "id": "reqSiteId", + "keywords": "apnKeywords", + "sectioncat": ["books"], + "pagecat": ["magazines"], + "search": "books", + "page": "http://www.foobar.com/testurl.html", + "publisher": { + "id": "1" + }, + "ext": { + "data": { + "id": "apnSiteId" + } + } + } + } + }, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/global-fpd-empty-app-content-data.json b/firstpartydata/tests/extractfpdforbidders/global-fpd-empty-app-content-data.json new file mode 100644 index 00000000000..57d79210376 --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/global-fpd-empty-app-content-data.json @@ -0,0 +1,58 @@ +{ + "description": "req.app.content.data defined; req.ext.prebid.data.bidders is empty; expect req.app.content.data to not be included in any of the resolved bidder FPD and extracted from the original request", + "inputRequestData": { + "id": "bid_id", + "app": { + "id": "reqAppId", + "domain": "http://www.foobar.com/1234.html", + "name": "reqAppName", + "cat": [ + "electronics", + "phone" + ], + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason", + "data": [ + { + "id": "reqContentDataId1", + "name": "reqContentDataName1" + }, + { + "id": "reqContentDataId2", + "name": "reqContentDataName2" + } + ] + } + }, + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": [] + } + } + } + }, + "outputRequestData": { + "id": "bid_id", + "app": { + "id": "reqAppId", + "domain": "http://www.foobar.com/1234.html", + "name": "reqAppName", + "cat": [ + "electronics", + "phone" + ], + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason" + } + }, + "test": 1 + }, + "biddersFPDResolved": {}, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/global-fpd-empty-app.json b/firstpartydata/tests/extractfpdforbidders/global-fpd-empty-app.json new file mode 100644 index 00000000000..e37edf1214f --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/global-fpd-empty-app.json @@ -0,0 +1,43 @@ +{ + "description": "req.app.ext.data is present but req.ext.prebid.data.bidders is empty; expect req.app.ext.data to not be included in any of the resolved bidder FPD and to have been extracted from the original request", + "inputRequestData": { + "id": "bid_id", + "app": { + "id": "reqAppId", + "keywords": "appKeywords", + "publisher": { + "id": "1" + }, + "ext": { + "amp": 1, + "data": { + "someappfpd": "appfpdDataTest" + } + } + }, + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": [] + } + } + } + }, + "outputRequestData": { + "id": "bid_id", + "app": { + "id": "reqAppId", + "keywords": "appKeywords", + "publisher": { + "id": "1" + }, + "ext": { + "amp": 1 + } + }, + "test": 1 + }, + "biddersFPDResolved": {}, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/global-fpd-empty-site-content-data.json b/firstpartydata/tests/extractfpdforbidders/global-fpd-empty-site-content-data.json new file mode 100644 index 00000000000..5ff8fb6bebf --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/global-fpd-empty-site-content-data.json @@ -0,0 +1,64 @@ +{ + "description": "req.site.content.data defined; req.ext.prebid.data.bidders is empty; expect req.site.content.data not to be included in the resolved FPD for any bidders and to have been extracted from the original request", + "inputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "name": "reqSiteName", + "cat": [ + "electronics", + "phone" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason", + "data": [ + { + "id": "reqContentDataId1", + "name": "reqContentDataName1" + }, + { + "id": "reqContentDataId2", + "name": "reqContentDataName2" + } + ] + } + }, + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": [] + } + } + } + }, + "outputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "name": "reqSiteName", + "cat": [ + "electronics", + "phone" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason" + } + }, + "test": 1 + }, + "biddersFPDResolved": {}, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/global-fpd-empty-site.json b/firstpartydata/tests/extractfpdforbidders/global-fpd-empty-site.json new file mode 100644 index 00000000000..985edd34254 --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/global-fpd-empty-site.json @@ -0,0 +1,43 @@ +{ + "description": "req.site.ext.data is present but req.ext.prebid.data.bidders is empty; expect req.site.ext.data to not be included in any of the resolved bidder FPD and to have been extracted from the original request", + "inputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "ext": { + "amp": 1, + "data": { + "somesitefpd": "sitefpdDataTest" + } + } + }, + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": [] + } + } + } + }, + "outputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "ext": { + "amp": 1 + } + }, + "test": 1 + }, + "biddersFPDResolved": {}, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/global-fpd-empty-user-data.json b/firstpartydata/tests/extractfpdforbidders/global-fpd-empty-user-data.json new file mode 100644 index 00000000000..f7bc1d818b9 --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/global-fpd-empty-user-data.json @@ -0,0 +1,42 @@ +{ + "description": "req.user.data defined; req.ext.prebid.data.bidders is empty; expect req.site.content.data do not contain req.app.content.data from the original request", + "inputRequestData": { + "id": "bid_id", + "user": { + "id": "reqUserID", + "keywords": "userKeyword", + "yob": 1982, + "gender": "M", + "data": [ + { + "id": "userDataId1", + "name": "userDataName1" + }, + { + "id": "userDataId2", + "name": "userDataName2" + } + ] + }, + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": [] + } + } + } + }, + "outputRequestData": { + "id": "bid_id", + "user": { + "id": "reqUserID", + "keywords": "userKeyword", + "yob": 1982, + "gender": "M" + }, + "test": 1 + }, + "biddersFPDResolved": {}, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/global-fpd-empty-user.json b/firstpartydata/tests/extractfpdforbidders/global-fpd-empty-user.json new file mode 100644 index 00000000000..cd40d84ddea --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/global-fpd-empty-user.json @@ -0,0 +1,43 @@ +{ + "description": "req.user.ext.data is present but req.ext.prebid.data.bidders is empty; expect req.user.ext.data to not be included in any of the resolved bidders FPD and to have been extracted from the original request", + "inputRequestData": { + "id": "bid_id", + "user": { + "id": "reqUserId", + "keywords": "userKeywords", + "publisher": { + "id": "1" + }, + "ext": { + "amp": 1, + "data": { + "someuserfpd": "userfpdDataTest" + } + } + }, + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": [] + } + } + } + }, + "outputRequestData": { + "id": "bid_id", + "user": { + "id": "reqUserId", + "keywords": "userKeywords", + "publisher": { + "id": "1" + }, + "ext": { + "amp": 1 + } + }, + "test": 1 + }, + "biddersFPDResolved": {}, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-app-content-data.json b/firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-app-content-data.json new file mode 100644 index 00000000000..2f753ad5b24 --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-app-content-data.json @@ -0,0 +1,61 @@ +{ + "description": "req.app.content.data is present but req.ext.prebid is not defined; expect req.app.content.data remains the same and no FPD data for bidders", + "inputRequestData": { + "id": "bid_id", + "app": { + "id": "reqAppId", + "domain": "http://www.foobar.com/1234.html", + "name": "reqAppName", + "cat": [ + "electronics", + "phone" + ], + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason", + "data": [ + { + "id": "reqContentDataId1", + "name": "reqContentDataName1" + }, + { + "id": "reqContentDataId2", + "name": "reqContentDataName2" + } + ] + } + }, + "test": 1 + }, + "outputRequestData": { + "id": "bid_id", + "app": { + "id": "reqAppId", + "domain": "http://www.foobar.com/1234.html", + "name": "reqAppName", + "cat": [ + "electronics", + "phone" + ], + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason", + "data": [ + { + "id": "reqContentDataId1", + "name": "reqContentDataName1" + }, + { + "id": "reqContentDataId2", + "name": "reqContentDataName2" + } + ] + } + }, + "test": 1 + }, + "biddersFPDResolved": {}, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-app.json b/firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-app.json new file mode 100644 index 00000000000..c60c9a86004 --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-app.json @@ -0,0 +1,39 @@ +{ + "description": "req.app.ext.data is present but req.ext.prebid is not defined; expect req.app.ext.data remains the same and no FPD data for bidders", + "inputRequestData": { + "id": "bid_id", + "app": { + "id": "reqAppId", + "keywords": "appKeywords", + "publisher": { + "id": "1" + }, + "ext": { + "amp": 1, + "data": { + "someappfpd": "appfpdDataTest" + } + } + }, + "test": 1 + }, + "outputRequestData": { + "id": "bid_id", + "app": { + "id": "reqAppId", + "keywords": "appKeywords", + "publisher": { + "id": "1" + }, + "ext": { + "amp":1, + "data": { + "someappfpd": "appfpdDataTest" + } + } + }, + "test": 1 + }, + "biddersFPDResolved": {}, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-site-content-data.json b/firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-site-content-data.json new file mode 100644 index 00000000000..594c8e772f5 --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-site-content-data.json @@ -0,0 +1,67 @@ +{ + "description": "req.site.content.data is present but req.ext.prebid is not defined; expect req.site.content.data remains the same and no FPD data for bidders", + "inputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "name": "reqSiteName", + "cat": [ + "electronics", + "phone" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason", + "data": [ + { + "id": "reqContentDataId1", + "name": "reqContentDataName1" + }, + { + "id": "reqContentDataId2", + "name": "reqContentDataName2" + } + ] + } + }, + "test": 1 + }, + "outputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "name": "reqSiteName", + "cat": [ + "electronics", + "phone" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason", + "data": [ + { + "id": "reqContentDataId1", + "name": "reqContentDataName1" + }, + { + "id": "reqContentDataId2", + "name": "reqContentDataName2" + } + ] + } + }, + "test": 1 + }, + "biddersFPDResolved": {}, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-site.json b/firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-site.json new file mode 100644 index 00000000000..1eb04ff20eb --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-site.json @@ -0,0 +1,39 @@ +{ + "description": "req.site.ext.data is present but req.ext.prebid is not defined; expect req.site.ext.data remains the same and no FPD data for bidders", + "inputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "ext": { + "amp": 1, + "data": { + "somesitefpd": "sitefpdDataTest" + } + } + }, + "test": 1 + }, + "outputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "ext": { + "amp":1, + "data": { + "somesitefpd": "sitefpdDataTest" + } + } + }, + "test": 1 + }, + "biddersFPDResolved": {}, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-user-data.json b/firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-user-data.json new file mode 100644 index 00000000000..4bb013b1108 --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-user-data.json @@ -0,0 +1,45 @@ +{ + "description": "req.user.data is present but req.ext.prebid is not defined; expect req.user.data remains the same and no FPD data for bidders", + "inputRequestData": { + "id": "bid_id", + "user": { + "id": "reqUserID", + "keywords": "userKeyword", + "yob": 1982, + "gender": "M", + "data": [ + { + "id": "userDataId1", + "name": "userDataName1" + }, + { + "id": "userDataId2", + "name": "userDataName2" + } + ] + }, + "test": 1 + }, + "outputRequestData": { + "id": "bid_id", + "user": { + "id": "reqUserID", + "keywords": "userKeyword", + "yob": 1982, + "gender": "M", + "data": [ + { + "id": "userDataId1", + "name": "userDataName1" + }, + { + "id": "userDataId2", + "name": "userDataName2" + } + ] + }, + "test": 1 + }, + "biddersFPDResolved": {}, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-user.json b/firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-user.json new file mode 100644 index 00000000000..91ebcab4d93 --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-user.json @@ -0,0 +1,39 @@ +{ + "description": "req.user.ext.data is present but req.ext.prebid is not defined; expect req.user.ext.data remains the same and no FPD data for bidders", + "inputRequestData": { + "id": "bid_id", + "user": { + "id": "reqUserId", + "keywords": "userKeywords", + "publisher": { + "id": "1" + }, + "ext": { + "amp": 1, + "data": { + "someuserfpd": "userfpdDataTest" + } + } + }, + "test": 1 + }, + "outputRequestData": { + "id": "bid_id", + "user": { + "id": "reqUserId", + "keywords": "userKeywords", + "publisher": { + "id": "1" + }, + "ext": { + "amp":1, + "data": { + "someuserfpd": "userfpdDataTest" + } + } + }, + "test": 1 + }, + "biddersFPDResolved": {}, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/two-bidders-correct-fpd.json b/firstpartydata/tests/extractfpdforbidders/two-bidders-correct-fpd.json new file mode 100644 index 00000000000..62b2a44eead --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/two-bidders-correct-fpd.json @@ -0,0 +1,98 @@ +{ + "description": "req.site is present; req.ext.prebid.data.bidders has two bidders; req.ext.prebid.bidderconfig is defined for two bidders from req.ext.prebid.data.bidders; expect to see both bidders in resolved bidder FPD", + "inputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + } + }, + "user": { + "id": "reqUserId" + }, + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": [ + "appnexus", + "telaria" + ] + }, + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": { + "ortb2": { + "site": { + "id": "apnSiteId" + }, + "user": { + "id": "apnUserId" + } + } + } + }, + { + "bidders": [ + "telaria" + ], + "config": { + "ortb2": { + "user": { + "keywords": "telariaUserKeywords" + } + } + } + } + ] + } + } + }, + "outputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + } + }, + "test": 1 + }, + "biddersFPDResolved": { + "appnexus": { + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "ext": { + "data": { + "id": "apnSiteId" + } + } + }, + "user": { + "id": "reqUserId", + "ext": { + "data": { + "id": "apnUserId" + } + } + } + }, + "telaria": { + "user": { + "id": "reqUserId", + "keywords": "telariaUserKeywords" + } + } + }, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/two-bidders-global-and-non-global-app-user.json b/firstpartydata/tests/extractfpdforbidders/two-bidders-global-and-non-global-app-user.json new file mode 100644 index 00000000000..6fed14e9059 --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/two-bidders-global-and-non-global-app-user.json @@ -0,0 +1,184 @@ +{ + "description": "req.app.content.data defined; bidder config defined for appnexus and telaria but only appnexus listed in req.ext.prebid.data.bidders; expect all FPD data to be included only in appnexus resolved FPD and extracted from the original request", + "inputRequestData": { + "id": "bid_id", + "app": { + "id": "reqAppId", + "page": "http://www.foobar.com/1234.html", + "name": "reqAppName", + "cat": [ + "electronics", + "phone" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason", + "data": [ + { + "id": "reqContentDataId1", + "name": "reqContentDataName1" + }, + { + "id": "reqContentDataId2", + "name": "reqContentDataName2" + } + ] + } + }, + "user": { + "id": "reqUserId", + "keywords": "reqUserKeyword" + }, + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": [ + "appnexus" + ] + }, + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": { + "ortb2": { + "app": { + "id": "apnFpdAppId", + "name": "apnFpdAppIdAppName", + "cat": [ + "books", + "news" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "apnFpdAppIdContentTitle", + "season": "apnFpdAppIdContentSeason", + "data": [ + { + "id": "apnFpdAppIdContentDataId1", + "name": "apnFpdAppIdContentDataName1" + }, + { + "id": "apnFpdAppIdContentDataId2", + "name": "apnFpdAppIdContentDataName2" + } + ] + } + }, + "user": { + "id": "apnUserId", + "keywords": "apnFpdUserKeyword" + } + } + } + }, + { + "bidders": [ + "telaria" + ], + "config": { + "ortb2": { + "user": { + "keywords": "telariaFpdUserKeywords" + } + } + } + } + ] + } + } + }, + "outputRequestData": { + "id": "bid_id", + "app": { + "id": "reqAppId", + "page": "http://www.foobar.com/1234.html", + "name": "reqAppName", + "cat": [ + "electronics", + "phone" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason" + } + }, + "test": 1 + }, + "biddersFPDResolved": { + "appnexus": { + "app": { + "id": "reqAppId", + "name": "apnFpdAppIdAppName", + "cat": [ + "books", + "news" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason", + "data": [ + { + "id": "reqContentDataId1", + "name": "reqContentDataName1" + }, + { + "id": "reqContentDataId2", + "name": "reqContentDataName2" + } + ] + }, + "ext": { + "data": { + "content": { + "len": 900, + "title": "apnFpdAppIdContentTitle", + "season": "apnFpdAppIdContentSeason", + "data": [ + { + "id": "apnFpdAppIdContentDataId1", + "name": "apnFpdAppIdContentDataName1" + }, + { + "id": "apnFpdAppIdContentDataId2", + "name": "apnFpdAppIdContentDataName2" + } + ] + }, + "id": "apnFpdAppId", + "publisher": { + "id": "1" + } + } + } + }, + "user": { + "id": "reqUserId", + "keywords": "apnFpdUserKeyword", + "ext": { + "data": { + "id":"apnUserId" + } + } + } + } + }, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/two-bidders-global-and-non-global-site-user.json b/firstpartydata/tests/extractfpdforbidders/two-bidders-global-and-non-global-site-user.json new file mode 100644 index 00000000000..129087444a3 --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/two-bidders-global-and-non-global-site-user.json @@ -0,0 +1,186 @@ +{ + "description": "req.site is present; req.ext.prebid.data.bidders has appnexus bidder only; req.ext.prebid.bidderconfig is defined for two bidders appnexus and telaria; expect to see only appnexus in resolved bidder FPD and have extra data in site.ext.data", + "inputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "name": "reqSiteName", + "cat": [ + "electronics", + "phone" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason", + "data": [ + { + "id": "reqContentDataId1", + "name": "reqContentDataName1" + }, + { + "id": "reqContentDataId2", + "name": "reqContentDataName2" + } + ] + } + }, + "user": { + "id": "reqUserId", + "keywords": "reqUserKeyword" + }, + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": [ + "appnexus" + ] + }, + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": { + "ortb2": { + "site": { + "id": "apnFpdSiteId", + "page": "http://www.foobar.com/4321.html", + "name": "apnFpdSiteIdSiteName", + "cat": [ + "books", + "news" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "apnFpdSiteIdContentTitle", + "season": "apnFpdSiteIdContentSeason", + "data": [ + { + "id": "apnFpdSiteIdContentDataId1", + "name": "apnFpdSiteIdContentDataName1" + }, + { + "id": "apnFpdSiteIdContentDataId2", + "name": "apnFpdSiteIdContentDataName2" + } + ] + } + }, + "user": { + "id": "apnUserId", + "keywords": "apnFpdUserKeyword" + } + } + } + }, + { + "bidders": [ + "telaria" + ], + "config": { + "ortb2": { + "user": { + "keywords": "telariaFpdUserKeywords" + } + } + } + } + ] + } + } + }, + "outputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "name": "reqSiteName", + "cat": [ + "electronics", + "phone" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason" + } + }, + "test": 1 + }, + "biddersFPDResolved": { + "appnexus": { + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/4321.html", + "name": "apnFpdSiteIdSiteName", + "cat": [ + "books", + "news" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason", + "data": [ + { + "id": "reqContentDataId1", + "name": "reqContentDataName1" + }, + { + "id": "reqContentDataId2", + "name": "reqContentDataName2" + } + ] + }, + "ext": { + "data": { + "content": { + "len": 900, + "title": "apnFpdSiteIdContentTitle", + "season": "apnFpdSiteIdContentSeason", + "data": [ + { + "id": "apnFpdSiteIdContentDataId1", + "name": "apnFpdSiteIdContentDataName1" + }, + { + "id": "apnFpdSiteIdContentDataId2", + "name": "apnFpdSiteIdContentDataName2" + } + ] + }, + "id": "apnFpdSiteId", + "publisher": { + "id": "1" + } + } + } + }, + "user": { + "id": "reqUserId", + "keywords": "apnFpdUserKeyword", + "ext": { + "data": { + "id":"apnUserId" + } + } + } + } + }, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/two-bidders-global-and-non-global-user.json b/firstpartydata/tests/extractfpdforbidders/two-bidders-global-and-non-global-user.json new file mode 100644 index 00000000000..c04838c756c --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/two-bidders-global-and-non-global-user.json @@ -0,0 +1,222 @@ +{ + "description": "req.site and req.user is present; req.ext.prebid.data.bidders has appnexus bidder only; req.ext.prebid.bidderconfig is defined for two bidders; expect to see only appnexus in bidders FPD and contain site and user data", + "inputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "name": "reqSiteName", + "cat": [ + "electronics", + "phone" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason", + "data": [ + { + "id": "reqContentDataId1", + "name": "reqContentDataName1" + }, + { + "id": "reqContentDataId2", + "name": "reqContentDataName2" + } + ] + } + }, + "user": { + "id": "reqUserId" + }, + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": [ + "appnexus" + ] + }, + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": { + "ortb2": { + "site": { + "id": "apnFpdSiteId", + "page": "http://www.foobar.com/4321.html", + "name": "apnFpdSiteIdSiteName", + "cat": [ + "books", + "news" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "apnFpdSiteIdContentTitle", + "season": "apnFpdSiteIdContentSeason", + "data": [ + { + "id": "apnFpdSiteIdContentDataId1", + "name": "apnFpdSiteIdContentDataName1" + }, + { + "id": "apnFpdSiteIdContentDataId2", + "name": "apnFpdSiteIdContentDataName2" + } + ] + } + }, + "user": { + "id": "apnUserId", + "gender": "F", + "yob": 2000, + "keywords": "apnFpdUserKeyword", + "data": [ + { + "id": "apnFpdUserContentDataId1", + "name": "apnFpdUserContentDataName1" + }, + { + "id": "apnFpdUserContentDataId2", + "name": "apnFpdUserContentDataName2" + } + ] + } + } + } + }, + { + "bidders": [ + "telaria" + ], + "config": { + "ortb2": { + "user": { + "id": "telariaFpdUserId", + "gender": "M", + "yob": 2001, + "keywords": "telariaFpdUserKeyword", + "data": [ + { + "id": "telariaFpdUserContentDataId1", + "name": "telariaFpdUserContentDataName1" + }, + { + "id": "telariaFpdUserContentDataId2", + "name": "telariaFpdUserContentDataName2" + } + ] + } + } + } + } + ] + } + } + }, + "outputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "name": "reqSiteName", + "cat": [ + "electronics", + "phone" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason" + } + }, + "test": 1 + }, + "biddersFPDResolved": { + "appnexus": { + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/4321.html", + "name": "apnFpdSiteIdSiteName", + "cat": [ + "books", + "news" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason", + "data": [ + { + "id": "reqContentDataId1", + "name": "reqContentDataName1" + }, + { + "id": "reqContentDataId2", + "name": "reqContentDataName2" + } + ] + }, + "ext": { + "data": { + "id": "apnFpdSiteId", + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "apnFpdSiteIdContentTitle", + "season": "apnFpdSiteIdContentSeason", + "data": [ + { + "id": "apnFpdSiteIdContentDataId1", + "name": "apnFpdSiteIdContentDataName1" + }, + { + "id": "apnFpdSiteIdContentDataId2", + "name": "apnFpdSiteIdContentDataName2" + } + ] + } + } + } + }, + "user": { + "id": "reqUserId", + "gender": "F", + "yob": 2000, + "keywords": "apnFpdUserKeyword", + "ext": { + "data": { + "id": "apnUserId", + "data": [ + { + "id": "apnFpdUserContentDataId1", + "name": "apnFpdUserContentDataName1" + }, + { + "id": "apnFpdUserContentDataId2", + "name": "apnFpdUserContentDataName2" + } + ] + } + } + } + } + }, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/two-bidders-no-global-bidder-list.json b/firstpartydata/tests/extractfpdforbidders/two-bidders-no-global-bidder-list.json new file mode 100644 index 00000000000..d86db6dc4e4 --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/two-bidders-no-global-bidder-list.json @@ -0,0 +1,98 @@ +{ + "description": "req.site is present; req.ext.prebid.data.bidders is not defined; req.ext.prebid.bidderconfig is defined for two bidders. Expect to have both bidders in resoved FPD", + "inputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "ext": { + "data": "fpdGlobalSiteData" + } + }, + "user": { + "id": "reqUserId" + }, + "test": 1, + "ext": { + "prebid": { + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": { + "ortb2": { + "site": { + "id": "apnSiteId" + }, + "user": { + "id": "apnUserId" + } + } + } + }, + { + "bidders": [ + "telaria" + ], + "config": { + "ortb2": { + "user": { + "keywords": "telariaUserKeywords" + } + } + } + } + ] + } + } + }, + "outputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "ext": { + "data": "fpdGlobalSiteData" + } + }, + "test": 1 + }, + "biddersFPDResolved": { + "appnexus": { + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "ext": { + "data":{ + "id":"apnSiteId" + } + } + }, + "user": { + "id": "reqUserId", + "ext": { + "data":{ + "id":"apnUserId" + } + } + } + }, + "telaria": { + "user": { + "id": "reqUserId", + "keywords": "telariaUserKeywords" + } + } + }, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/two-bidders-one-with-incorrect-fpd.json b/firstpartydata/tests/extractfpdforbidders/two-bidders-one-with-incorrect-fpd.json new file mode 100644 index 00000000000..a473fb9c0f3 --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/two-bidders-one-with-incorrect-fpd.json @@ -0,0 +1,54 @@ +{ + "description": "req.site is present; req.ext.prebid.data.bidders has appnexus and telaria bidders; req.ext.prebid.bidderconfig is defined for both bidders from req.ext.prebid.data.bidders; req.ext.prebid.bidderconfig for telaria has app config in it; expect to have validation error about app object is not defined in request", + "inputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + } + }, + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": [ + "appnexus", "telaria" + ] + }, + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": { + "ortb2": { + "site": { + "id": "apnSiteId" + } + } + } + }, + { + "bidders": [ + "telaria" + ], + "config": { + "ortb2": { + "app": { + "id": "telariaAppId" + } + } + } + } + ] + } + } + }, + "outputRequestData": {}, + "biddersFPDResolved": {}, + "validationErrors": [ + {"Message": "incorrect First Party Data for bidder telaria: App object is not defined in request, but defined in FPD config"} + ] +} diff --git a/firstpartydata/tests/extractfpdforbidders/two-bidders-with-incorrect-fpd.json b/firstpartydata/tests/extractfpdforbidders/two-bidders-with-incorrect-fpd.json new file mode 100644 index 00000000000..a23b45397f4 --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/two-bidders-with-incorrect-fpd.json @@ -0,0 +1,59 @@ +{ + "description": "req.site is present; req.ext.prebid.data.bidders has appnexus and telaria bidders; req.ext.prebid.bidderconfig is defined for both of them; req.ext.prebid.bidderconfig for appnexus contains app and user; req.ext.prebid.bidderconfig for telaria contains app; expected to have error for appnexus: app is not present in request, user is not present in request; expect to have error for telaria: app is not present in request", + "inputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + } + }, + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": [ + "appnexus", "telaria" + ] + }, + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": { + "ortb2": { + "app": { + "id": "apnAppId" + }, + "user": { + "id": "apnUserId" + } + } + } + }, + { + "bidders": [ + "telaria" + ], + "config": { + "ortb2": { + "app": { + "id": "telariaAppId" + } + } + } + } + ] + } + } + }, + "outputRequestData":{}, + "biddersFPDResolved": {}, + "validationErrors": [ + {"Message": "incorrect First Party Data for bidder appnexus: User object is not defined in request, but defined in FPD config"}, + {"Message": "incorrect First Party Data for bidder appnexus: App object is not defined in request, but defined in FPD config"}, + {"Message": "incorrect First Party Data for bidder telaria: App object is not defined in request, but defined in FPD config"} + ] +} diff --git a/firstpartydata/tests/resolvefpd/bidder-fpd-only-app.json b/firstpartydata/tests/resolvefpd/bidder-fpd-only-app.json new file mode 100644 index 00000000000..db579fb2a41 --- /dev/null +++ b/firstpartydata/tests/resolvefpd/bidder-fpd-only-app.json @@ -0,0 +1,44 @@ +{ + "description": "Bidder FPD defined only for app", + "inputRequestData": { + "app": { + "id": "reqUserID" + } + }, + "bidderConfigFPD": { + "appnexus": { + "app": { + "id": "apnAppId", + "ext": { + "data": { + "morefpdData": "morefpddata", + "appFpddata": "appFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + } + }, + "outputRequestData": { + "app": { + "id": "reqUserID", + "ext": { + "data": { + "ext": { + "data": { + "morefpdData": "morefpddata", + "appFpddata": "appFpddata", + "moreFpd": { + "fpd": 123 + } + } + }, + "id": "apnAppId" + } + } + } + } +} + diff --git a/firstpartydata/tests/resolvefpd/bidder-fpd-only-site.json b/firstpartydata/tests/resolvefpd/bidder-fpd-only-site.json new file mode 100644 index 00000000000..86a40f29d75 --- /dev/null +++ b/firstpartydata/tests/resolvefpd/bidder-fpd-only-site.json @@ -0,0 +1,44 @@ +{ + "description": "Bidder FPD defined only for site", + "inputRequestData": { + "site": { + "id": "reqUserID" + } + }, + "bidderConfigFPD": { + "appnexus": { + "site": { + "id": "apnSiteId", + "ext": { + "data": { + "morefpdData": "morefpddata", + "siteFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + } + }, + "outputRequestData": { + "site": { + "id": "reqUserID", + "ext": { + "data": { + "ext": { + "data": { + "morefpdData": "morefpddata", + "siteFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } + } + }, + "id": "apnSiteId" + } + } + } + } +} + diff --git a/firstpartydata/tests/resolvefpd/bidder-fpd-only-user.json b/firstpartydata/tests/resolvefpd/bidder-fpd-only-user.json new file mode 100644 index 00000000000..a1fb4b4190f --- /dev/null +++ b/firstpartydata/tests/resolvefpd/bidder-fpd-only-user.json @@ -0,0 +1,49 @@ +{ + "description": "Bidder FPD defined only for user", + "inputRequestData": { + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M" + } + }, + "bidderConfigFPD": { + "appnexus": { + "user": { + "id": "apnUserId", + "yob": 1982, + "ext": { + "data": { + "morefpdData": "morefpddata", + "userFpddata": "userFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + } + }, + "outputRequestData": { + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M", + "ext": { + "data": { + "ext": { + "data": { + "morefpdData": "morefpddata", + "userFpddata": "userFpddata", + "moreFpd": { + "fpd": 123 + } + } + }, + "id": "apnUserId" + } + } + } + } +} + diff --git a/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-app-content-data-user-data.json b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-app-content-data-user-data.json new file mode 100644 index 00000000000..ebba9041788 --- /dev/null +++ b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-app-content-data-user-data.json @@ -0,0 +1,114 @@ +{ + "description": "Global FPD defined for app and user. Bidder FPD defined for app. Global FPD has app.content.data and user.data", + "inputRequestData": { + "app": { + "id": "reqAppId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + } + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M" + } + }, + "bidderConfigFPD": { + "appnexus": { + "app": { + "id": "apnAppId", + "ext": { + "data": { + "morefpdData": "morefpddata", + "appFpddata": "appFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + } + }, + "globalFPD": { + "app": { + "appFpd": 123 + }, + "user": { + "testUserFpd": "testuser" + }, + "appContentData": [ + { + "id": "appData1", + "name": "appName1" + }, + { + "id": "appData2", + "name": "appName2" + } + ], + "userData": [ + { + "id": "userData1", + "name": "userName1" + }, + { + "id": "userData2", + "name": "userName2" + } + ] + }, + "outputRequestData": { + "app": { + "id": "reqAppId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "content": { + "data": [ + { + "id": "appData1", + "name": "appName1" + }, + { + "id": "appData2", + "name": "appName2" + } + ] + }, + "ext": { + "data": { + "moreFpd": { + "fpd": 123 + }, + "id": "apnAppId", + "morefpdData": "morefpddata", + "appFpd": 123, + "appFpddata": "appFpddata" + } + } + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M", + "data": [ + { + "id": "userData1", + "name": "userName1" + }, + { + "id": "userData2", + "name": "userName2" + } + ], + "ext": { + "data": { + "testUserFpd": "testuser" + } + } + } + } +} + diff --git a/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-app.json b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-app.json new file mode 100644 index 00000000000..dea874147ab --- /dev/null +++ b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-app.json @@ -0,0 +1,80 @@ +{ + "description": "Global and bidder FPD defined for app", + "inputRequestData": { + "app": { + "id": "reqAppId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900 + } + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + } + }, + "bidderConfigFPD": { + "appnexus": { + "app": { + "id": "apnAppId", + "ext": { + "data": { + "morefpdData": "morefpddata", + "appFpddata": "appFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + } + }, + "globalFPD": { + "app": { + "appFpd": 123 + } + }, + "outputRequestData": { + "app": { + "id": "reqAppId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900 + }, + "ext": { + "data": { + "id": "apnAppId", + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata", + "appFpd": 123, + "appFpddata": "appFpddata" + } + } + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + } + } +} + diff --git a/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site-app-user.json b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site-app-user.json new file mode 100644 index 00000000000..73f827888ed --- /dev/null +++ b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site-app-user.json @@ -0,0 +1,130 @@ +{ + "description": "Global and bidder FPD defined for site and user. Global FPD has site.content.data", + "inputRequestData": { + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900 + }, + "ext": { + "testSiteExt": 123 + } + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M" + } + }, + "bidderConfigFPD": { + "appnexus": { + "site": { + "id": "apnSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "ext": { + "data": { + "morefpdData": "morefpddata", + "siteFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + } + }, + "globalFPD": { + "site": { + "siteFpd": 123 + }, + "app": { + "appFpd": { + "testValue": true + } + }, + "user": { + "testUserFpd": "testuser" + }, + "siteContentData": [ + { + "id": "siteData1", + "name": "siteName1" + }, + { + "id": "siteData2", + "name": "siteName2" + } + ] + }, + "outputRequestData": { + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900, + "data": [ + { + "id": "siteData1", + "name": "siteName1" + }, + { + "id": "siteData2", + "name": "siteName2" + } + ] + }, + "ext": { + "testSiteExt": 123, + "data": { + "id": "apnSiteId", + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata", + "siteFpd": 123, + "siteFpddata": "siteFpddata" + } + } + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M", + "ext": { + "data": { + "testUserFpd": "testuser" + } + } + } + } +} + diff --git a/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site-content-data.json b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site-content-data.json new file mode 100644 index 00000000000..2843834966e --- /dev/null +++ b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site-content-data.json @@ -0,0 +1,144 @@ +{ + "description": "Global and bidder FPD defined for site. Global FPD has site.content.data", + "inputRequestData": { + "site": { + "id": "reqSiteId", + "name": "reqSiteName", + "domain": "reqSiteDomain", + "cat": [ + "books", + "novels" + ], + "search": "book search", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900, + "data": [ + { + "id": "reqSiteData", + "name": "reqSiteName" + } + ] + } + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + } + }, + "bidderConfigFPD": { + "appnexus": { + "site": { + "id": "apnSiteId", + "name": "apnSiteName", + "domain": "apnSiteDomain", + "page": "http://www.foobar.com/1234.html", + "content": { + "episode": 7, + "title": "apnEpisodeName", + "len": 600, + "data": [ + { + "id": "siteData3", + "name": "siteName3" + } + ] + }, + "ext": { + "data": { + "morefpdData": "morefpddata", + "siteFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + } + }, + "globalFPD": { + "site": { + "testSiteFpd": "testSite" + }, + "siteContentData": [ + { + "id": "siteData1", + "name": "siteName1" + }, + { + "id": "siteData2", + "name": "siteName2" + } + ] + }, + "outputRequestData": { + "site": { + "id": "reqSiteId", + "name": "apnSiteName", + "page": "http://www.foobar.com/1234.html", + "domain": "apnSiteDomain", + "cat": [ + "books", + "novels" + ], + "search": "book search", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900, + "data": [ + { + "id": "siteData1", + "name": "siteName1" + }, + { + "id": "siteData2", + "name": "siteName2" + } + ] + }, + "ext": { + "data": { + "id": "apnSiteId", + "content": { + "episode": 7, + "title": "apnEpisodeName", + "len": 600, + "data": [ + { + "id": "siteData3", + "name": "siteName3" + } + ] + }, + "morefpdData": "morefpddata", + "siteFpddata": "siteFpddata", + "testSiteFpd": "testSite", + "moreFpd": { + "fpd": 123 + } + } + } + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + } + } +} + diff --git a/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site-user.json b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site-user.json new file mode 100644 index 00000000000..fc65518dcd3 --- /dev/null +++ b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site-user.json @@ -0,0 +1,96 @@ +{ + "description": "Global FPD defined for user and bidder FPD defined for site and user", + "inputRequestData": { + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900 + } + }, + "user": { + "id": "apnUserId" + } + }, + "bidderConfigFPD": { + "appnexus": { + "site": { + "id": "apnSiteId", + "ext": { + "data": { + "morefpdData": "morefpddata", + "siteFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + }, + "user": { + "id": "apnUserId", + "ext": { + "data": { + "moreFpd": { + "fpd": 567 + } + } + } + } + } + }, + "globalFPD": { + "user": { + "testUserFpd": "testuser" + } + }, + "outputRequestData": { + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900 + }, + "ext": { + "data": { + "ext": { + "data": { + "morefpdData": "morefpddata", + "siteFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } + } + }, + "id": "apnSiteId" + } + } + }, + "user": { + "id": "apnUserId", + "ext": { + "data": { + "id": "apnUserId", + "moreFpd": { + "fpd": 567 + }, + "testUserFpd": "testuser" + } + } + } + } +} + diff --git a/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site.json b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site.json new file mode 100644 index 00000000000..f3d3fb74df2 --- /dev/null +++ b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site.json @@ -0,0 +1,71 @@ +{ + "description": "Global and bidder FPD defined for site", + "inputRequestData": { + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900 + } + } + }, + "bidderConfigFPD": { + "appnexus": { + "site": { + "id": "apnSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "ext": { + "data": { + "morefpdData": "morefpddata", + "siteFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + } + }, + "globalFPD": { + "site": { + "siteFpd": 123 + } + }, + "outputRequestData": { + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900 + }, + "ext": { + "data": { + "id": "apnSiteId", + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata", + "siteFpd": 123, + "siteFpddata": "siteFpddata" + } + } + } + } +} + diff --git a/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-user.json b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-user.json new file mode 100644 index 00000000000..612518655d9 --- /dev/null +++ b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-user.json @@ -0,0 +1,51 @@ +{ + "description": "Global and bidder FPD defined for user", + "inputRequestData": { + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M" + } + }, + "bidderConfigFPD": { + "appnexus": { + "user": { + "id": "apnUserId", + "yob": 1982, + "ext": { + "data": { + "morefpdData": "morefpddata", + "userFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + } + }, + "globalFPD": { + "user": { + "testUserFpd": "testuser" + } + }, + "outputRequestData": { + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M", + "ext": { + "data": { + "id": "apnUserId", + "testUserFpd": "testuser", + "morefpdData": "morefpddata", + "userFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + } +} + diff --git a/firstpartydata/tests/resolvefpd/global-and-bidder-site-content-data.json b/firstpartydata/tests/resolvefpd/global-and-bidder-site-content-data.json new file mode 100644 index 00000000000..d0154d1bae6 --- /dev/null +++ b/firstpartydata/tests/resolvefpd/global-and-bidder-site-content-data.json @@ -0,0 +1,114 @@ +{ + "description": "Global FPD defined for site, app, and user and bidder FPD defined for site. Global FPD has site.content.data", + "inputRequestData": { + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + } + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M" + } + }, + "bidderConfigFPD": { + "appnexus": { + "site": { + "id": "apnSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "ext": { + "data": { + "morefpdData": "morefpddata", + "siteFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + } + }, + "globalFPD": { + "site": { + "siteFpd": 123 + }, + "app": { + "appFpd": { + "testValue": true + } + }, + "user": { + "testUserFpd": "testuser" + }, + "siteContentData": [ + { + "id": "siteData1", + "name": "siteName1" + }, + { + "id": "siteData2", + "name": "siteName2" + } + ] + }, + "outputRequestData": { + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "publisher": { + "id": "1" + }, + "content": { + "data": [ + { + "id": "siteData1", + "name": "siteName1" + }, + { + "id": "siteData2", + "name": "siteName2" + } + ] + }, + "ext": { + "data": { + "id": "apnSiteId", + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata", + "siteFpd": 123, + "siteFpddata": "siteFpddata" + } + } + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M", + "ext": { + "data": { + "testUserFpd": "testuser" + } + } + } + } +} + diff --git a/firstpartydata/tests/resolvefpd/req-app-not-defined.json b/firstpartydata/tests/resolvefpd/req-app-not-defined.json new file mode 100644 index 00000000000..f24a4d27db1 --- /dev/null +++ b/firstpartydata/tests/resolvefpd/req-app-not-defined.json @@ -0,0 +1,46 @@ +{ + "description": "Incorrect bidder specific FPD: app not defined in request", + "inputRequestData": { + "site": { + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900 + } + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M" + } + }, + "bidderConfigFPD": { + "appnexus": { + "app": { + "page": "", + "ext": { + "data": { + "morefpdData": "morefpddata", + "siteFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + } + }, + "outputRequestData": {}, + "validationErrors": [ + { + "Message": "incorrect First Party Data for bidder appnexus: App object is not defined in request, but defined in FPD config" + } + ] +} + diff --git a/firstpartydata/tests/resolvefpd/req-site-not-defined.json b/firstpartydata/tests/resolvefpd/req-site-not-defined.json new file mode 100644 index 00000000000..d078ad7804f --- /dev/null +++ b/firstpartydata/tests/resolvefpd/req-site-not-defined.json @@ -0,0 +1,33 @@ +{ + "description": "Incorrect bidder specific FPD: site not defined in request", + "inputRequestData": { + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M" + } + }, + "bidderConfigFPD": { + "appnexus": { + "site": { + "page": "", + "ext": { + "data": { + "morefpdData": "morefpddata", + "siteFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + } + }, + "outputRequestData": {}, + "validationErrors": [ + { + "Message": "incorrect First Party Data for bidder appnexus: Site object is not defined in request, but defined in FPD config" + } + ] +} + diff --git a/firstpartydata/tests/resolvefpd/req-user-not-defined.json b/firstpartydata/tests/resolvefpd/req-user-not-defined.json new file mode 100644 index 00000000000..76ae3f827ca --- /dev/null +++ b/firstpartydata/tests/resolvefpd/req-user-not-defined.json @@ -0,0 +1,31 @@ +{ + "description": "Incorrect bidder specific FPD: user not defined in request", + "inputRequestData": { + "test": 1, + "at": 1, + "tmax": 5000 + }, + "bidderConfigFPD": { + "appnexus": { + "user": { + "keywords": "test user keywords", + "ext": { + "data": { + "morefpdData": "morefpddata", + "siteFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + } + }, + "outputRequestData": {}, + "validationErrors": [ + { + "Message": "incorrect First Party Data for bidder appnexus: User object is not defined in request, but defined in FPD config" + } + ] +} + diff --git a/firstpartydata/tests/resolvefpd/site-page-empty-conflict.json b/firstpartydata/tests/resolvefpd/site-page-empty-conflict.json new file mode 100644 index 00000000000..8da3dc69329 --- /dev/null +++ b/firstpartydata/tests/resolvefpd/site-page-empty-conflict.json @@ -0,0 +1,45 @@ +{ + "description": "Incorrect bidder specific FPD: site has no id and attempts to overwrite page to empty", + "inputRequestData": { + "site": { + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + } + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M" + } + }, + "bidderConfigFPD": { + "appnexus": { + "site": { + "page": "", + "ext": { + "data": { + "morefpdData": "morefpddata", + "siteFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + } + }, + "outputRequestData": {}, + "validationErrors": [ + { + "Message": "incorrect First Party Data for bidder appnexus: Site object cannot set empty page if req.site.id is empty" + } + ] +} + diff --git a/gdpr/impl.go b/gdpr/impl.go index 9fb55462725..07aad36d765 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -96,7 +96,7 @@ func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, consen return false, nil } - if !p.cfg.TCF2.Purpose1.Enabled { + if p.cfg.TCF2.Purpose1.EnforcePurpose == config.TCF2NoEnforcement { return true, nil } consentMeta, ok := parsedConsent.(tcf2.ConsentMetadata) @@ -138,7 +138,7 @@ func (p *permissionsImpl) allowActivities(ctx context.Context, vendorID uint16, } else { passGeo = true } - if p.cfg.TCF2.Purpose2.Enabled { + if p.cfg.TCF2.Purpose2.EnforcePurpose == config.TCF2FullEnforcement { vendorException := p.isVendorException(consentconstants.Purpose(2), bidder) allowBidRequest = p.checkPurpose(consentMeta, vendor, vendorID, consentconstants.Purpose(2), vendorException, weakVendorEnforcement) } else { diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index f9da82a80a5..3064a9fda89 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -65,7 +65,7 @@ func TestAllowedSyncs(t *testing.T) { HostVendorID: 2, TCF2: config.TCF2{ Purpose1: config.TCF2Purpose{ - Enabled: true, + EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true, }, }, @@ -109,7 +109,7 @@ func TestProhibitedPurposes(t *testing.T) { HostVendorID: 2, TCF2: config.TCF2{ Purpose1: config.TCF2Purpose{ - Enabled: true, + EnforcePurpose: config.TCF2FullEnforcement, }, }, }, @@ -148,7 +148,7 @@ func TestProhibitedVendors(t *testing.T) { HostVendorID: 2, TCF2: config.TCF2{ Purpose1: config.TCF2Purpose{ - Enabled: true, + EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true, }, }, @@ -296,7 +296,7 @@ func TestAllowActivities(t *testing.T) { TCF2: config.TCF2{ Enabled: true, Purpose2: config.TCF2Purpose{ - Enabled: true, + EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true, }, }, @@ -374,16 +374,16 @@ func allPurposesEnabledPermissions() (perms permissionsImpl) { HostVendorID: 2, TCF2: config.TCF2{ Enabled: true, - Purpose1: config.TCF2Purpose{Enabled: true, EnforceVendors: true}, - Purpose2: config.TCF2Purpose{Enabled: true, EnforceVendors: true}, - Purpose3: config.TCF2Purpose{Enabled: true, EnforceVendors: true}, - Purpose4: config.TCF2Purpose{Enabled: true, EnforceVendors: true}, - Purpose5: config.TCF2Purpose{Enabled: true, EnforceVendors: true}, - Purpose6: config.TCF2Purpose{Enabled: true, EnforceVendors: true}, - Purpose7: config.TCF2Purpose{Enabled: true, EnforceVendors: true}, - Purpose8: config.TCF2Purpose{Enabled: true, EnforceVendors: true}, - Purpose9: config.TCF2Purpose{Enabled: true, EnforceVendors: true}, - Purpose10: config.TCF2Purpose{Enabled: true, EnforceVendors: true}, + Purpose1: config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true}, + Purpose2: config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true}, + Purpose3: config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true}, + Purpose4: config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true}, + Purpose5: config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true}, + Purpose6: config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true}, + Purpose7: config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true}, + Purpose8: config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true}, + Purpose9: config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true}, + Purpose10: config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true}, SpecialPurpose1: config.TCF2Purpose{Enabled: true, EnforceVendors: true}, }, }, @@ -707,7 +707,7 @@ func TestAllowActivitiesBidRequests(t *testing.T) { testDefs := []struct { description string - purpose2Enabled bool + purpose2EnforcePurpose string purpose2EnforceVendors bool bidder openrtb_ext.BidderName consent string @@ -718,7 +718,7 @@ func TestAllowActivitiesBidRequests(t *testing.T) { }{ { description: "Bid blocked - p2 enabled, user consents to p2 but not vendor, vendor consents to p2", - purpose2Enabled: true, + purpose2EnforcePurpose: config.TCF2FullEnforcement, purpose2EnforceVendors: true, bidder: openrtb_ext.BidderPubmatic, consent: purpose2ConsentWithoutVendorConsent, @@ -728,7 +728,7 @@ func TestAllowActivitiesBidRequests(t *testing.T) { }, { description: "Bid allowed - p2 enabled not enforcing vendors, user consents to p2 but not vendor, vendor consents to p2", - purpose2Enabled: true, + purpose2EnforcePurpose: config.TCF2FullEnforcement, purpose2EnforceVendors: false, bidder: openrtb_ext.BidderPubmatic, consent: purpose2ConsentWithoutVendorConsent, @@ -738,7 +738,7 @@ func TestAllowActivitiesBidRequests(t *testing.T) { }, { description: "Bid allowed - p2 disabled, user consents to p2 but not vendor, vendor consents to p2", - purpose2Enabled: false, + purpose2EnforcePurpose: config.TCF2NoEnforcement, purpose2EnforceVendors: true, bidder: openrtb_ext.BidderPubmatic, consent: purpose2ConsentWithoutVendorConsent, @@ -748,7 +748,7 @@ func TestAllowActivitiesBidRequests(t *testing.T) { }, { description: "Bid allowed - p2 enabled, user consents to p2 and vendor, vendor consents to p2", - purpose2Enabled: true, + purpose2EnforcePurpose: config.TCF2FullEnforcement, purpose2EnforceVendors: true, bidder: openrtb_ext.BidderPubmatic, consent: purpose2AndVendorConsent, @@ -758,7 +758,7 @@ func TestAllowActivitiesBidRequests(t *testing.T) { }, { description: "Bid blocked - p2 enabled, user consents to p2 LI but not vendor, vendor consents to p2", - purpose2Enabled: true, + purpose2EnforcePurpose: config.TCF2FullEnforcement, purpose2EnforceVendors: true, bidder: openrtb_ext.BidderRubicon, consent: purpose2LIWithoutVendorLI, @@ -768,7 +768,7 @@ func TestAllowActivitiesBidRequests(t *testing.T) { }, { description: "Bid allowed - p2 enabled, user consents to p2 LI and vendor, vendor consents to p2", - purpose2Enabled: true, + purpose2EnforcePurpose: config.TCF2FullEnforcement, purpose2EnforceVendors: true, bidder: openrtb_ext.BidderRubicon, consent: purpose2AndVendorLI, @@ -778,7 +778,7 @@ func TestAllowActivitiesBidRequests(t *testing.T) { }, { description: "Bid allowed - p2 enabled not enforcing vendors, user consents to p2 LI but not vendor, vendor consents to p2", - purpose2Enabled: true, + purpose2EnforcePurpose: config.TCF2FullEnforcement, purpose2EnforceVendors: false, bidder: openrtb_ext.BidderPubmatic, consent: purpose2AndVendorLI, @@ -801,9 +801,9 @@ func TestAllowActivitiesBidRequests(t *testing.T) { 34: parseVendorListDataV2(t, vendorListData), }), } - perms.cfg.TCF2.Purpose2.Enabled = td.purpose2Enabled + perms.cfg.TCF2.Purpose2.EnforcePurpose = td.purpose2EnforcePurpose p2Config := perms.purposeConfigs[consentconstants.Purpose(2)] - p2Config.Enabled = td.purpose2Enabled + p2Config.EnforcePurpose = td.purpose2EnforcePurpose p2Config.EnforceVendors = td.purpose2EnforceVendors perms.purposeConfigs[consentconstants.Purpose(2)] = p2Config @@ -895,7 +895,7 @@ func TestAllowActivitiesVendorException(t *testing.T) { HostVendorID: 2, TCF2: config.TCF2{ Enabled: true, - Purpose2: config.TCF2Purpose{Enabled: true, VendorExceptionMap: td.p2VendorExceptionMap}, + Purpose2: config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, VendorExceptionMap: td.p2VendorExceptionMap}, SpecialPurpose1: config.TCF2Purpose{Enabled: true, VendorExceptionMap: td.sp1VendorExceptionMap}, }, }, @@ -962,7 +962,7 @@ func TestBidderSyncAllowedVendorException(t *testing.T) { HostVendorID: 2, TCF2: config.TCF2{ Enabled: true, - Purpose1: config.TCF2Purpose{Enabled: true, VendorExceptionMap: td.p1VendorExceptionMap}, + Purpose1: config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, VendorExceptionMap: td.p1VendorExceptionMap}, }, }, vendorIDs: map[openrtb_ext.BidderName]uint16{ diff --git a/go.mod b/go.mod index 347f36bcf86..1dad3f3e9de 100644 --- a/go.mod +++ b/go.mod @@ -7,36 +7,28 @@ require ( github.com/NYTimes/gziphandler v1.1.1 github.com/OneOfOne/xxhash v1.2.5 // indirect github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf - github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect - github.com/blang/semver v3.5.1+incompatible github.com/buger/jsonparser v1.1.1 github.com/cespare/xxhash v1.0.0 // indirect github.com/chasex/glog v0.0.0-20160217080310-c62392af379c github.com/coocood/freecache v1.0.1 github.com/docker/go-units v0.4.0 - github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 github.com/evanphx/json-patch v0.0.0-20180720181644-f195058310bd github.com/gofrs/uuid v3.2.0+incompatible github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b github.com/influxdata/influxdb v1.6.1 - github.com/julienschmidt/httprouter v1.1.0 + github.com/julienschmidt/httprouter v1.3.0 github.com/lib/pq v1.0.0 - github.com/magiconair/properties v1.8.5 github.com/mattn/go-colorable v0.1.2 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mitchellh/copystructure v1.1.2 github.com/mxmCherry/openrtb/v15 v15.0.0 github.com/prebid/go-gdpr v1.10.0 - github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed - github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 - github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e // indirect - github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273 // indirect + github.com/prometheus/client_golang v1.11.0 + github.com/prometheus/client_model v0.2.0 github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165 github.com/rs/cors v1.5.0 github.com/sergi/go-diff v1.0.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/viper v1.8.1 - github.com/stretchr/objx v0.1.1 // indirect github.com/stretchr/testify v1.7.0 github.com/vrischmann/go-metrics-influxdb v0.0.0-20160917065939-43af8332c303 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect diff --git a/go.sum b/go.sum index 31e6b8fc93f..fc3dbb34025 100644 --- a/go.sum +++ b/go.sum @@ -45,23 +45,30 @@ github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cq github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI= github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= -github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= -github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.0.0 h1:naDmySfoNg0nKS62/ujM6e71ZgM2AoVdaqGwMG0w18A= github.com/cespare/xxhash v1.0.0/go.mod h1:fX/lfQBkSCDXZSUgv6jVIu/EVA3/JNseAX5asI4c4T4= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chasex/glog v0.0.0-20160217080310-c62392af379c h1:eXqCBUHfmjbeDqcuvzjsd+bM6A+bnwo5N9FVbV6m5/s= github.com/chasex/glog v0.0.0-20160217080310-c62392af379c/go.mod h1:omJZNg0Qu76bxJd+ExohVo8uXzNcGOk2bv7vel460xk= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -87,8 +94,6 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= -github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/evanphx/json-patch v0.0.0-20180720181644-f195058310bd h1:biTJQdqouE5by89AAffXG8++TY+9Fsdrg5rinbt3tHk= github.com/evanphx/json-patch v0.0.0-20180720181644-f195058310bd/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -99,10 +104,18 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -197,16 +210,23 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/influxdata/influxdb v1.6.1 h1:OseoBlzI5ftNI/bczyxSWq6PKRCNEeiXvyWP/wS5fB0= github.com/influxdata/influxdb v1.6.1/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.1.0 h1:7wLdtIiIpzOkC9u6sXOozpBauPdskj3ru4EI5MABq68= -github.com/julienschmidt/httprouter v1.1.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -239,8 +259,11 @@ github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxmCherry/openrtb/v15 v15.0.0 h1:inLuQ3Bsima9HLB2v6WjbtEFF69SWOT5Dux4QZtYdrw= github.com/mxmCherry/openrtb/v15 v15.0.0/go.mod h1:TVgncsz6MOzbL7lhun1lNuUBzVBlVDbxf9Fyy1TyhZA= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= @@ -257,21 +280,34 @@ github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prebid/go-gdpr v1.10.0 h1:f9Ua+PAIK97j82QkJtIsohlbyU8961mFphw23hTsoMo= github.com/prebid/go-gdpr v1.10.0/go.mod h1:mPZAdkRxn+iuSjaUuJAi9+0SppBOdM1PCzv/55UH3pY= -github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed h1:0dloFFFNNDG7c+8qtkYw2FdADrWy9s5cI8wHp6tK3Mg= -github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e h1:n/3MEhJQjQxrOUCzh1Y3Re6aJUUWRp2M9+Oc3eVn/54= -github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273 h1:agujYaXJSxSo18YNX3jzl+4G6Bstwt+kqv47GS12uL0= -github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165 h1:nkcn14uNmFEuGCb2mBZbBb24RdNRL08b/wb+xBOYpuk= github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= @@ -282,6 +318,9 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= @@ -342,6 +381,7 @@ go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -388,6 +428,7 @@ golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -396,6 +437,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -449,12 +491,15 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -466,6 +511,7 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -478,6 +524,8 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -486,14 +534,16 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40 h1:JWgyZ1qgdTaF3N3oxC+MdTV7qvEEgHo3otj+HB5CM7Q= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -667,18 +717,22 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/main.go b/main.go index 6087c3d69dd..c103863107d 100644 --- a/main.go +++ b/main.go @@ -4,11 +4,11 @@ import ( "flag" "math/rand" "net/http" + "runtime" "time" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" - pbc "github.com/prebid/prebid-server/prebid_cache_client" "github.com/prebid/prebid-server/router" "github.com/prebid/prebid-server/server" "github.com/prebid/prebid-server/util/task" @@ -29,6 +29,14 @@ func main() { glog.Exitf("Configuration could not be loaded or did not pass validation: %v", err) } + // Create a soft memory limit on the total amount of memory that PBS uses to tune the behavior + // of the Go garbage collector. In summary, `cfg.GarbageCollectorThreshold` serves as a fixed cost + // of memory that is going to be held garbage before a garbage collection cycle is triggered. + // This amount of virtual memory won’t translate into physical memory allocation unless we attempt + // to read or write to the slice below, which PBS will not do. + garbageCollectionThreshold := make([]byte, cfg.GarbageCollectorThreshold) + defer runtime.KeepAlive(garbageCollectionThreshold) + err = serve(cfg) if err != nil { glog.Exitf("prebid-server failed: %v", err) @@ -56,8 +64,6 @@ func serve(cfg *config.Configuration) error { return err } - pbc.InitPrebidCache(cfg.CacheURL.GetBaseURL()) - corsRouter := router.SupportCORS(r) server.Listen(cfg, router.NoCache{Handler: corsRouter}, router.Admin(currencyConverter, fetchingInterval), r.MetricsEngine) diff --git a/metrics/config/metrics.go b/metrics/config/metrics.go index 51ba8cafe2f..66849db5864 100644 --- a/metrics/config/metrics.go +++ b/metrics/config/metrics.go @@ -90,13 +90,6 @@ func (me *MultiMetricsEngine) RecordImps(implabels metrics.ImpLabels) { } } -// RecordImps for the legacy endpoint -func (me *MultiMetricsEngine) RecordLegacyImps(labels metrics.Labels, numImps int) { - for _, thisME := range *me { - thisME.RecordLegacyImps(labels, numImps) - } -} - // RecordRequestTime across all engines func (me *MultiMetricsEngine) RecordRequestTime(labels metrics.Labels, length time.Duration) { for _, thisME := range *me { @@ -278,10 +271,6 @@ func (me *NilMetricsEngine) RecordConnectionClose(success bool) { func (me *NilMetricsEngine) RecordImps(implabels metrics.ImpLabels) { } -// RecordLegacyImps as a noop -func (me *NilMetricsEngine) RecordLegacyImps(labels metrics.Labels, numImps int) { -} - // RecordRequestTime as a noop func (me *NilMetricsEngine) RecordRequestTime(labels metrics.Labels, length time.Duration) { } diff --git a/metrics/config/metrics_test.go b/metrics/config/metrics_test.go index 0d6bcdb922d..e4227afda77 100644 --- a/metrics/config/metrics_test.go +++ b/metrics/config/metrics_test.go @@ -78,7 +78,6 @@ func TestMultiMetricsEngine(t *testing.T) { for i := 0; i < 5; i++ { metricsEngine.RecordRequest(labels) metricsEngine.RecordImps(impTypeLabels) - metricsEngine.RecordLegacyImps(labels, 2) metricsEngine.RecordRequestTime(labels, time.Millisecond*20) metricsEngine.RecordAdapterRequest(pubLabels) metricsEngine.RecordAdapterRequest(apnLabels) @@ -147,7 +146,6 @@ func TestMultiMetricsEngine(t *testing.T) { VerifyMetrics(t, "Request", goEngine.RequestStatuses[metrics.ReqTypeORTB2Web][metrics.RequestStatusOK].Count(), 5) VerifyMetrics(t, "ImpMeter", goEngine.ImpMeter.Count(), 8) - VerifyMetrics(t, "LegacyImpMeter", goEngine.LegacyImpMeter.Count(), 10) VerifyMetrics(t, "NoCookieMeter", goEngine.NoCookieMeter.Count(), 0) VerifyMetrics(t, "AdapterMetrics.Pubmatic.GotBidsMeter", goEngine.AdapterMetrics[openrtb_ext.BidderPubmatic].GotBidsMeter.Count(), 5) VerifyMetrics(t, "AdapterMetrics.Pubmatic.NoBidMeter", goEngine.AdapterMetrics[openrtb_ext.BidderPubmatic].NoBidMeter.Count(), 0) diff --git a/metrics/go_metrics.go b/metrics/go_metrics.go index 615b83b8be9..c93f10602c7 100644 --- a/metrics/go_metrics.go +++ b/metrics/go_metrics.go @@ -18,7 +18,6 @@ type Metrics struct { ConnectionAcceptErrorMeter metrics.Meter ConnectionCloseErrorMeter metrics.Meter ImpMeter metrics.Meter - LegacyImpMeter metrics.Meter AppRequestMeter metrics.Meter NoCookieMeter metrics.Meter RequestTimer metrics.Timer @@ -101,9 +100,6 @@ type accountMetrics struct { adapterMetrics map[openrtb_ext.BidderName]*AdapterMetrics } -// Defining an "unknown" bidder -const unknownBidder openrtb_ext.BidderName = "unknown" - // NewBlankMetrics creates a new Metrics object with all blank metrics object. This may also be useful for // testing routines to ensure that no metrics are written anywhere. // @@ -122,7 +118,6 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa ConnectionAcceptErrorMeter: blankMeter, ConnectionCloseErrorMeter: blankMeter, ImpMeter: blankMeter, - LegacyImpMeter: blankMeter, AppRequestMeter: blankMeter, NoCookieMeter: blankMeter, RequestTimer: blankTimer, @@ -216,7 +211,6 @@ func NewMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, d newMetrics.ConnectionAcceptErrorMeter = metrics.GetOrRegisterMeter("connection_accept_errors", registry) newMetrics.ConnectionCloseErrorMeter = metrics.GetOrRegisterMeter("connection_close_errors", registry) newMetrics.ImpMeter = metrics.GetOrRegisterMeter("imps_requested", registry) - newMetrics.LegacyImpMeter = metrics.GetOrRegisterMeter("legacy_imps_requested", registry) newMetrics.ImpsTypeBanner = metrics.GetOrRegisterMeter("imp_banner", registry) newMetrics.ImpsTypeVideo = metrics.GetOrRegisterMeter("imp_video", registry) @@ -452,10 +446,6 @@ func (me *Metrics) RecordImps(labels ImpLabels) { } } -func (me *Metrics) RecordLegacyImps(labels Labels, numImps int) { - me.LegacyImpMeter.Mark(int64(numImps)) -} - func (me *Metrics) RecordConnectionAccept(success bool) { if success { me.ConnectionCounter.Inc(1) diff --git a/metrics/go_metrics_test.go b/metrics/go_metrics_test.go index 346a64a737f..dd2430d6b74 100644 --- a/metrics/go_metrics_test.go +++ b/metrics/go_metrics_test.go @@ -36,10 +36,6 @@ func TestNewMetrics(t *testing.T) { ensureContains(t, registry, "prebid_cache_request_time.ok", m.PrebidCacheRequestTimerSuccess) ensureContains(t, registry, "prebid_cache_request_time.err", m.PrebidCacheRequestTimerError) - ensureContains(t, registry, "requests.ok.legacy", m.RequestStatuses[ReqTypeLegacy][RequestStatusOK]) - ensureContains(t, registry, "requests.badinput.legacy", m.RequestStatuses[ReqTypeLegacy][RequestStatusBadInput]) - ensureContains(t, registry, "requests.err.legacy", m.RequestStatuses[ReqTypeLegacy][RequestStatusErr]) - ensureContains(t, registry, "requests.networkerr.legacy", m.RequestStatuses[ReqTypeLegacy][RequestStatusNetworkErr]) ensureContains(t, registry, "requests.ok.openrtb2-web", m.RequestStatuses[ReqTypeORTB2Web][RequestStatusOK]) ensureContains(t, registry, "requests.badinput.openrtb2-web", m.RequestStatuses[ReqTypeORTB2Web][RequestStatusBadInput]) ensureContains(t, registry, "requests.err.openrtb2-web", m.RequestStatuses[ReqTypeORTB2Web][RequestStatusErr]) diff --git a/metrics/metrics.go b/metrics/metrics.go index af45f9b4f5a..9d16143f0d4 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -382,7 +382,6 @@ type MetricsEngine interface { RecordConnectionClose(success bool) RecordRequest(labels Labels) // ignores adapter. only statusOk and statusErr fom status RecordImps(labels ImpLabels) // RecordImps across openRTB2 engines that support the 'Native' Imp Type - RecordLegacyImps(labels Labels, numImps int) // RecordImps for the legacy engine RecordRequestTime(labels Labels, length time.Duration) // ignores adapter. only statusOk and statusErr fom status RecordAdapterRequest(labels AdapterLabels) RecordAdapterConnections(adapterName openrtb_ext.BidderName, connWasReused bool, connWaitTime time.Duration) diff --git a/metrics/metrics_mock.go b/metrics/metrics_mock.go index b8ab23b768a..c8d5311c3a4 100644 --- a/metrics/metrics_mock.go +++ b/metrics/metrics_mock.go @@ -32,11 +32,6 @@ func (me *MetricsEngineMock) RecordImps(labels ImpLabels) { me.Called(labels) } -// RecordLegacyImps mock -func (me *MetricsEngineMock) RecordLegacyImps(labels Labels, numImps int) { - me.Called(labels, numImps) -} - // RecordRequestTime mock func (me *MetricsEngineMock) RecordRequestTime(labels Labels, length time.Duration) { me.Called(labels, length) diff --git a/metrics/prometheus/prometheus.go b/metrics/prometheus/prometheus.go index 52470369094..368424c594c 100644 --- a/metrics/prometheus/prometheus.go +++ b/metrics/prometheus/prometheus.go @@ -1,6 +1,7 @@ package prometheusmetrics import ( + "fmt" "strconv" "time" @@ -8,11 +9,13 @@ import ( "github.com/prebid/prebid-server/metrics" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prometheus/client_golang/prometheus" + promCollector "github.com/prometheus/client_golang/prometheus/collectors" ) // Metrics defines the Prometheus metrics backing the MetricsEngine implementation. type Metrics struct { - Registry *prometheus.Registry + Registerer prometheus.Registerer + Gatherer *prometheus.Registry // General Metrics connectionsClosed prometheus.Counter @@ -133,246 +136,253 @@ func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMet queuedRequestTimeBuckets := []float64{0, 1, 5, 30, 60, 120, 180, 240, 300} metrics := Metrics{} - metrics.Registry = prometheus.NewRegistry() + reg := prometheus.NewRegistry() metrics.metricsDisabled = disabledMetrics - metrics.connectionsClosed = newCounterWithoutLabels(cfg, metrics.Registry, + metrics.connectionsClosed = newCounterWithoutLabels(cfg, reg, "connections_closed", "Count of successful connections closed to Prebid Server.") - metrics.connectionsError = newCounter(cfg, metrics.Registry, + metrics.connectionsError = newCounter(cfg, reg, "connections_error", "Count of errors for connection open and close attempts to Prebid Server labeled by type.", []string{connectionErrorLabel}) - metrics.connectionsOpened = newCounterWithoutLabels(cfg, metrics.Registry, + metrics.connectionsOpened = newCounterWithoutLabels(cfg, reg, "connections_opened", "Count of successful connections opened to Prebid Server.") - metrics.cookieSync = newCounter(cfg, metrics.Registry, + metrics.cookieSync = newCounter(cfg, reg, "cookie_sync_requests", "Count of cookie sync requests to Prebid Server.", []string{statusLabel}) - metrics.setUid = newCounter(cfg, metrics.Registry, + metrics.setUid = newCounter(cfg, reg, "setuid_requests", "Count of set uid requests to Prebid Server.", []string{statusLabel}) - metrics.impressions = newCounter(cfg, metrics.Registry, + metrics.impressions = newCounter(cfg, reg, "impressions_requests", "Count of requested impressions to Prebid Server labeled by type.", []string{isBannerLabel, isVideoLabel, isAudioLabel, isNativeLabel}) - metrics.impressionsLegacy = newCounterWithoutLabels(cfg, metrics.Registry, + metrics.impressionsLegacy = newCounterWithoutLabels(cfg, reg, "impressions_requests_legacy", "Count of requested impressions to Prebid Server using the legacy endpoint.") - metrics.prebidCacheWriteTimer = newHistogramVec(cfg, metrics.Registry, + metrics.prebidCacheWriteTimer = newHistogramVec(cfg, reg, "prebidcache_write_time_seconds", "Seconds to write to Prebid Cache labeled by success or failure. Failure timing is limited by Prebid Server enforced timeouts.", []string{successLabel}, cacheWriteTimeBuckets) - metrics.requests = newCounter(cfg, metrics.Registry, + metrics.requests = newCounter(cfg, reg, "requests", "Count of total requests to Prebid Server labeled by type and status.", []string{requestTypeLabel, requestStatusLabel}) - metrics.requestsTimer = newHistogramVec(cfg, metrics.Registry, + metrics.requestsTimer = newHistogramVec(cfg, reg, "request_time_seconds", "Seconds to resolve successful Prebid Server requests labeled by type.", []string{requestTypeLabel}, standardTimeBuckets) - metrics.requestsWithoutCookie = newCounter(cfg, metrics.Registry, + metrics.requestsWithoutCookie = newCounter(cfg, reg, "requests_without_cookie", "Count of total requests to Prebid Server without a cookie labeled by type.", []string{requestTypeLabel}) - metrics.storedImpressionsCacheResult = newCounter(cfg, metrics.Registry, + metrics.storedImpressionsCacheResult = newCounter(cfg, reg, "stored_impressions_cache_performance", "Count of stored impression cache requests attempts by hits or miss.", []string{cacheResultLabel}) - metrics.storedRequestCacheResult = newCounter(cfg, metrics.Registry, + metrics.storedRequestCacheResult = newCounter(cfg, reg, "stored_request_cache_performance", "Count of stored request cache requests attempts by hits or miss.", []string{cacheResultLabel}) - metrics.accountCacheResult = newCounter(cfg, metrics.Registry, + metrics.accountCacheResult = newCounter(cfg, reg, "account_cache_performance", "Count of account cache lookups by hits or miss.", []string{cacheResultLabel}) - metrics.storedAccountFetchTimer = newHistogramVec(cfg, metrics.Registry, + metrics.storedAccountFetchTimer = newHistogramVec(cfg, reg, "stored_account_fetch_time_seconds", "Seconds to fetch stored accounts labeled by fetch type", []string{storedDataFetchTypeLabel}, standardTimeBuckets) - metrics.storedAccountErrors = newCounter(cfg, metrics.Registry, + metrics.storedAccountErrors = newCounter(cfg, reg, "stored_account_errors", "Count of stored account errors by error type", []string{storedDataErrorLabel}) - metrics.storedAMPFetchTimer = newHistogramVec(cfg, metrics.Registry, + metrics.storedAMPFetchTimer = newHistogramVec(cfg, reg, "stored_amp_fetch_time_seconds", "Seconds to fetch stored AMP requests labeled by fetch type", []string{storedDataFetchTypeLabel}, standardTimeBuckets) - metrics.storedAMPErrors = newCounter(cfg, metrics.Registry, + metrics.storedAMPErrors = newCounter(cfg, reg, "stored_amp_errors", "Count of stored AMP errors by error type", []string{storedDataErrorLabel}) - metrics.storedCategoryFetchTimer = newHistogramVec(cfg, metrics.Registry, + metrics.storedCategoryFetchTimer = newHistogramVec(cfg, reg, "stored_category_fetch_time_seconds", "Seconds to fetch stored categories labeled by fetch type", []string{storedDataFetchTypeLabel}, standardTimeBuckets) - metrics.storedCategoryErrors = newCounter(cfg, metrics.Registry, + metrics.storedCategoryErrors = newCounter(cfg, reg, "stored_category_errors", "Count of stored category errors by error type", []string{storedDataErrorLabel}) - metrics.storedRequestFetchTimer = newHistogramVec(cfg, metrics.Registry, + metrics.storedRequestFetchTimer = newHistogramVec(cfg, reg, "stored_request_fetch_time_seconds", "Seconds to fetch stored requests labeled by fetch type", []string{storedDataFetchTypeLabel}, standardTimeBuckets) - metrics.storedRequestErrors = newCounter(cfg, metrics.Registry, + metrics.storedRequestErrors = newCounter(cfg, reg, "stored_request_errors", "Count of stored request errors by error type", []string{storedDataErrorLabel}) - metrics.storedVideoFetchTimer = newHistogramVec(cfg, metrics.Registry, + metrics.storedVideoFetchTimer = newHistogramVec(cfg, reg, "stored_video_fetch_time_seconds", "Seconds to fetch stored video labeled by fetch type", []string{storedDataFetchTypeLabel}, standardTimeBuckets) - metrics.storedVideoErrors = newCounter(cfg, metrics.Registry, + metrics.storedVideoErrors = newCounter(cfg, reg, "stored_video_errors", "Count of stored video errors by error type", []string{storedDataErrorLabel}) - metrics.timeoutNotifications = newCounter(cfg, metrics.Registry, + metrics.timeoutNotifications = newCounter(cfg, reg, "timeout_notification", "Count of timeout notifications triggered, and if they were successfully sent.", []string{successLabel}) - metrics.dnsLookupTimer = newHistogram(cfg, metrics.Registry, + metrics.dnsLookupTimer = newHistogram(cfg, reg, "dns_lookup_time", "Seconds to resolve DNS", standardTimeBuckets) - metrics.tlsHandhakeTimer = newHistogram(cfg, metrics.Registry, + metrics.tlsHandhakeTimer = newHistogram(cfg, reg, "tls_handshake_time", "Seconds to perform TLS Handshake", standardTimeBuckets) - metrics.privacyCCPA = newCounter(cfg, metrics.Registry, + metrics.privacyCCPA = newCounter(cfg, reg, "privacy_ccpa", "Count of total requests to Prebid Server where CCPA was provided by source and opt-out .", []string{sourceLabel, optOutLabel}) - metrics.privacyCOPPA = newCounter(cfg, metrics.Registry, + metrics.privacyCOPPA = newCounter(cfg, reg, "privacy_coppa", "Count of total requests to Prebid Server where the COPPA flag was set by source", []string{sourceLabel}) - metrics.privacyTCF = newCounter(cfg, metrics.Registry, + metrics.privacyTCF = newCounter(cfg, reg, "privacy_tcf", "Count of TCF versions for requests where GDPR was enforced by source and version.", []string{versionLabel, sourceLabel}) - metrics.privacyLMT = newCounter(cfg, metrics.Registry, + metrics.privacyLMT = newCounter(cfg, reg, "privacy_lmt", "Count of total requests to Prebid Server where the LMT flag was set by source", []string{sourceLabel}) if !metrics.metricsDisabled.AdapterGDPRRequestBlocked { - metrics.adapterGDPRBlockedRequests = newCounter(cfg, metrics.Registry, + metrics.adapterGDPRBlockedRequests = newCounter(cfg, reg, "adapter_gdpr_requests_blocked", "Count of total bidder requests blocked due to unsatisfied GDPR purpose 2 legal basis", []string{adapterLabel}) } - metrics.adapterBids = newCounter(cfg, metrics.Registry, + metrics.adapterBids = newCounter(cfg, reg, "adapter_bids", "Count of bids labeled by adapter and markup delivery type (adm or nurl).", []string{adapterLabel, markupDeliveryLabel}) - metrics.adapterErrors = newCounter(cfg, metrics.Registry, + metrics.adapterErrors = newCounter(cfg, reg, "adapter_errors", "Count of errors labeled by adapter and error type.", []string{adapterLabel, adapterErrorLabel}) - metrics.adapterPanics = newCounter(cfg, metrics.Registry, + metrics.adapterPanics = newCounter(cfg, reg, "adapter_panics", "Count of panics labeled by adapter.", []string{adapterLabel}) - metrics.adapterPrices = newHistogramVec(cfg, metrics.Registry, + metrics.adapterPrices = newHistogramVec(cfg, reg, "adapter_prices", "Monetary value of the bids labeled by adapter.", []string{adapterLabel}, priceBuckets) - metrics.adapterRequests = newCounter(cfg, metrics.Registry, + metrics.adapterRequests = newCounter(cfg, reg, "adapter_requests", "Count of requests labeled by adapter, if has a cookie, and if it resulted in bids.", []string{adapterLabel, cookieLabel, hasBidsLabel}) if !metrics.metricsDisabled.AdapterConnectionMetrics { - metrics.adapterCreatedConnections = newCounter(cfg, metrics.Registry, + metrics.adapterCreatedConnections = newCounter(cfg, reg, "adapter_connection_created", "Count that keeps track of new connections when contacting adapter bidder endpoints.", []string{adapterLabel}) - metrics.adapterReusedConnections = newCounter(cfg, metrics.Registry, + metrics.adapterReusedConnections = newCounter(cfg, reg, "adapter_connection_reused", "Count that keeps track of reused connections when contacting adapter bidder endpoints.", []string{adapterLabel}) - metrics.adapterConnectionWaitTime = newHistogramVec(cfg, metrics.Registry, + metrics.adapterConnectionWaitTime = newHistogramVec(cfg, reg, "adapter_connection_wait", "Seconds from when the connection was requested until it is either created or reused", []string{adapterLabel}, standardTimeBuckets) } - metrics.adapterRequestsTimer = newHistogramVec(cfg, metrics.Registry, + metrics.adapterRequestsTimer = newHistogramVec(cfg, reg, "adapter_request_time_seconds", "Seconds to resolve each successful request labeled by adapter.", []string{adapterLabel}, standardTimeBuckets) - metrics.syncerRequests = newCounter(cfg, metrics.Registry, + metrics.syncerRequests = newCounter(cfg, reg, "syncer_requests", "Count of cookie sync requests where a syncer is a candidate to be synced labeled by syncer key and status.", []string{syncerLabel, statusLabel}) - metrics.syncerSets = newCounter(cfg, metrics.Registry, + metrics.syncerSets = newCounter(cfg, reg, "syncer_sets", "Count of setuid set requests for a syncer labeled by syncer key and status.", []string{syncerLabel, statusLabel}) - metrics.accountRequests = newCounter(cfg, metrics.Registry, + metrics.accountRequests = newCounter(cfg, reg, "account_requests", "Count of total requests to Prebid Server labeled by account.", []string{accountLabel}) - metrics.requestsQueueTimer = newHistogramVec(cfg, metrics.Registry, + metrics.requestsQueueTimer = newHistogramVec(cfg, reg, "request_queue_time", "Seconds request was waiting in queue", []string{requestTypeLabel, requestStatusLabel}, queuedRequestTimeBuckets) + metrics.Gatherer = reg + + metricsPrefix := fmt.Sprintf("%s_%s_", cfg.Namespace, cfg.Subsystem) + + metrics.Registerer = prometheus.WrapRegistererWithPrefix(metricsPrefix, reg) + metrics.Registerer.MustRegister(promCollector.NewGoCollector()) + preloadLabelValues(&metrics, syncerKeys) return &metrics @@ -476,10 +486,6 @@ func (m *Metrics) RecordImps(labels metrics.ImpLabels) { }).Inc() } -func (m *Metrics) RecordLegacyImps(labels metrics.Labels, numImps int) { - m.impressionsLegacy.Add(float64(numImps)) -} - func (m *Metrics) RecordRequestTime(labels metrics.Labels, length time.Duration) { if labels.RequestStatus == metrics.RequestStatusOK { m.requestsTimer.With(prometheus.Labels{ diff --git a/metrics/prometheus/prometheus_test.go b/metrics/prometheus/prometheus_test.go index 0fe852b81df..c7b293dad5b 100644 --- a/metrics/prometheus/prometheus_test.go +++ b/metrics/prometheus/prometheus_test.go @@ -26,7 +26,7 @@ func TestMetricCountGatekeeping(t *testing.T) { m := createMetricsForTesting() // Gather All Metrics - metricFamilies, err := m.Registry.Gather() + metricFamilies, err := m.Gatherer.Gather() assert.NoError(t, err, "gather metics") // Summarize By Adapter Cardinality @@ -355,16 +355,6 @@ func TestImpressionsMetric(t *testing.T) { } } -func TestLegacyImpressionsMetric(t *testing.T) { - m := createMetricsForTesting() - - m.RecordLegacyImps(metrics.Labels{}, 42) - - expectedCount := float64(42) - assertCounterValue(t, "", "impressionsLegacy", m.impressionsLegacy, - expectedCount) -} - func TestRequestTimeMetric(t *testing.T) { requestType := metrics.ReqTypeORTB2Web performTest := func(m *Metrics, requestStatus metrics.RequestStatus, timeInMs float64) { @@ -1162,18 +1152,6 @@ func TestPrebidCacheRequestTimeMetric(t *testing.T) { assertHistogram(t, "Error", errorResult, errorExpectedCount, errorExpectedSum) } -func TestMetricAccumulationSpotCheck(t *testing.T) { - m := createMetricsForTesting() - - m.RecordLegacyImps(metrics.Labels{}, 1) - m.RecordLegacyImps(metrics.Labels{}, 2) - m.RecordLegacyImps(metrics.Labels{}, 3) - - expectedValue := float64(1 + 2 + 3) - assertCounterValue(t, "", "impressionsLegacy", m.impressionsLegacy, - expectedValue) -} - func TestRecordRequestQueueTimeMetric(t *testing.T) { performTest := func(m *Metrics, requestStatus bool, requestType metrics.RequestType, timeInSec float64) { m.RecordRequestQueueTime(requestStatus, requestType, time.Duration(timeInSec*float64(time.Second))) diff --git a/metrics/prometheus/type_conversion.go b/metrics/prometheus/type_conversion.go index 4e2d1ff5ba1..d99f2f6f39b 100644 --- a/metrics/prometheus/type_conversion.go +++ b/metrics/prometheus/type_conversion.go @@ -104,15 +104,6 @@ func setUidStatusesAsString() []string { return valuesAsString } -func storedDataTypesAsString() []string { - values := metrics.StoredDataTypes() - valuesAsString := make([]string, len(values)) - for i, v := range values { - valuesAsString[i] = string(v) - } - return valuesAsString -} - func storedDataFetchTypesAsString() []string { values := metrics.StoredDataFetchTypes() valuesAsString := make([]string, len(values)) diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 7976451877c..e8b6ce4cfa5 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -12,8 +12,6 @@ import ( "github.com/xeipuuv/gojsonschema" ) -const schemaDirectory = "static/bidder-params" - // BidderName refers to a core bidder id or an alias id. type BidderName string @@ -36,6 +34,7 @@ const ( BidderReservedGeneral BidderName = "general" // Reserved for non-bidder specific messages when using a map keyed on the bidder name. BidderReservedPrebid BidderName = "prebid" // Reserved for Prebid Server configuration. BidderReservedSKAdN BidderName = "skadn" // Reserved for Apple's SKAdNetwork OpenRTB extension. + BidderReservedBidder BidderName = "bidder" // Reserved for passing bidder parameters. ) // IsBidderNameReserved returns true if the specified name is a case insensitive match for a reserved bidder name. @@ -64,6 +63,10 @@ func IsBidderNameReserved(name string) bool { return true } + if strings.EqualFold(name, string(BidderReservedBidder)) { + return true + } + return false } @@ -85,6 +88,7 @@ const ( BidderAdkernelAdn BidderName = "adkernelAdn" BidderAdman BidderName = "adman" BidderAdmixer BidderName = "admixer" + BidderAdnuntius BidderName = "adnuntius" BidderAdOcean BidderName = "adocean" BidderAdoppler BidderName = "adoppler" BidderAdot BidderName = "adot" @@ -112,6 +116,7 @@ const ( BidderBidsCube BidderName = "bidscube" BidderBmtm BidderName = "bmtm" BidderBrightroll BidderName = "brightroll" + BidderCoinzilla BidderName = "coinzilla" BidderColossus BidderName = "colossus" BidderConnectAd BidderName = "connectad" BidderConsumable BidderName = "consumable" @@ -130,6 +135,7 @@ const ( BidderGamma BidderName = "gamma" BidderGamoshi BidderName = "gamoshi" BidderGrid BidderName = "grid" + BidderGroupm BidderName = "groupm" BidderGumGum BidderName = "gumgum" BidderHuaweiAds BidderName = "huaweiads" BidderImpactify BidderName = "impactify" @@ -170,6 +176,7 @@ const ( BidderPulsepoint BidderName = "pulsepoint" BidderRevcontent BidderName = "revcontent" BidderRhythmone BidderName = "rhythmone" + BidderRichaudience BidderName = "richaudience" BidderRTBHouse BidderName = "rtbhouse" BidderRubicon BidderName = "rubicon" BidderSharethrough BidderName = "sharethrough" @@ -183,6 +190,7 @@ const ( BidderSomoaudience BidderName = "somoaudience" BidderSonobi BidderName = "sonobi" BidderSovrn BidderName = "sovrn" + BidderStreamkey BidderName = "streamkey" BidderSynacormedia BidderName = "synacormedia" BidderTappx BidderName = "tappx" BidderTelaria BidderName = "telaria" @@ -194,6 +202,7 @@ const ( BidderUnruly BidderName = "unruly" BidderValueImpression BidderName = "valueimpression" BidderVerizonMedia BidderName = "verizonmedia" + BidderVideoByte BidderName = "videobyte" BidderVisx BidderName = "visx" BidderViewdeos BidderName = "viewdeos" BidderVrtcal BidderName = "vrtcal" @@ -221,6 +230,7 @@ func CoreBidderNames() []BidderName { BidderAdkernelAdn, BidderAdman, BidderAdmixer, + BidderAdnuntius, BidderAdOcean, BidderAdoppler, BidderAdot, @@ -248,6 +258,7 @@ func CoreBidderNames() []BidderName { BidderBidsCube, BidderBmtm, BidderBrightroll, + BidderCoinzilla, BidderColossus, BidderConnectAd, BidderConsumable, @@ -266,6 +277,7 @@ func CoreBidderNames() []BidderName { BidderGamma, BidderGamoshi, BidderGrid, + BidderGroupm, BidderGumGum, BidderHuaweiAds, BidderImpactify, @@ -306,6 +318,7 @@ func CoreBidderNames() []BidderName { BidderPulsepoint, BidderRevcontent, BidderRhythmone, + BidderRichaudience, BidderRTBHouse, BidderRubicon, BidderSharethrough, @@ -319,6 +332,7 @@ func CoreBidderNames() []BidderName { BidderSomoaudience, BidderSonobi, BidderSovrn, + BidderStreamkey, BidderSynacormedia, BidderTappx, BidderTelaria, @@ -330,6 +344,7 @@ func CoreBidderNames() []BidderName { BidderUnruly, BidderValueImpression, BidderVerizonMedia, + BidderVideoByte, BidderViewdeos, BidderVisx, BidderVrtcal, diff --git a/openrtb_ext/bidders_test.go b/openrtb_ext/bidders_test.go index 26ebf7dc74b..8bd25d9e14f 100644 --- a/openrtb_ext/bidders_test.go +++ b/openrtb_ext/bidders_test.go @@ -110,6 +110,9 @@ func TestIsBidderNameReserved(t *testing.T) { {"prebid", true}, {"PREbid", true}, {"PREBID", true}, + {"bidder", true}, + {"BIDDER", true}, + {"BidDer", true}, {"notreserved", false}, } diff --git a/openrtb_ext/imp_adf.go b/openrtb_ext/imp_adf.go index 6a8f9afaa33..e4f5283b7c2 100644 --- a/openrtb_ext/imp_adf.go +++ b/openrtb_ext/imp_adf.go @@ -8,4 +8,5 @@ type ExtImpAdf struct { MasterTagID json.Number `json:"mid,omitempty"` InventorySourceID int `json:"inv,omitempty"` PlacementName string `json:"mname,omitempty"` + PriceType string `json:"priceType,omitempty"` } diff --git a/openrtb_ext/imp_adform.go b/openrtb_ext/imp_adform.go deleted file mode 100644 index 3206ece7c9b..00000000000 --- a/openrtb_ext/imp_adform.go +++ /dev/null @@ -1,11 +0,0 @@ -package openrtb_ext - -type ExtImpAdform struct { - MasterTagId string `json:"mid"` - PriceType string `json:"priceType,omitempty"` - KeyValues string `json:"mkv,omitempty"` - KeyWords string `json:"mkw,omitempty"` - CDims string `json:"cdims,omitempty"` - MinPrice float64 `json:"minp,omitempty"` - Url string `json:"url,omitempty"` -} diff --git a/openrtb_ext/imp_adnuntius.go b/openrtb_ext/imp_adnuntius.go new file mode 100644 index 00000000000..bde30f0518f --- /dev/null +++ b/openrtb_ext/imp_adnuntius.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ImpExtAdnunitus struct { + Auid string `json:"auId"` + Network string `json:"network"` +} diff --git a/openrtb_ext/imp_beachfront.go b/openrtb_ext/imp_beachfront.go index 104ca8f9fb7..9e65d54b82b 100644 --- a/openrtb_ext/imp_beachfront.go +++ b/openrtb_ext/imp_beachfront.go @@ -4,10 +4,10 @@ type ExtImpBeachfront struct { AppId string `json:"appId"` AppIds ExtImpBeachfrontAppIds `json:"appIds"` BidFloor float64 `json:"bidfloor"` - VideoResponseType string `json:"videoResponseType, omitempty"` + VideoResponseType string `json:"videoResponseType,omitempty"` } type ExtImpBeachfrontAppIds struct { - Video string `json:"video, omitempty"` - Banner string `json:"banner, omitempty"` + Video string `json:"video,omitempty"` + Banner string `json:"banner,omitempty"` } diff --git a/openrtb_ext/imp_nanointeractive.go b/openrtb_ext/imp_nanointeractive.go index 28db5be0d07..77e386237ac 100644 --- a/openrtb_ext/imp_nanointeractive.go +++ b/openrtb_ext/imp_nanointeractive.go @@ -3,8 +3,8 @@ package openrtb_ext // ExtImpNanoInteractive defines the contract for bidrequest.imp[i].ext.nanointeractive type ExtImpNanoInteractive struct { Pid string `json:"pid"` - Nq []string `json:"nq, omitempty"` - Category string `json:"category, omitempty"` - SubId string `json:"subId, omitempty"` - Ref string `json:"ref, omitempty"` + Nq []string `json:"nq,omitempty"` + Category string `json:"category,omitempty"` + SubId string `json:"subId,omitempty"` + Ref string `json:"ref,omitempty"` } diff --git a/openrtb_ext/imp_richaudience.go b/openrtb_ext/imp_richaudience.go new file mode 100644 index 00000000000..bdcf7a96db9 --- /dev/null +++ b/openrtb_ext/imp_richaudience.go @@ -0,0 +1,9 @@ +package openrtb_ext + +type ExtImpRichaudience struct { + Pid string `json:"pid"` + SupplyType string `json:"supplyType"` + BidFloor float64 `json:"bidfloor"` + BidFloorCur string `json:"bidfloorcur"` + Test bool `json:"test"` +} diff --git a/openrtb_ext/imp_videobyte.go b/openrtb_ext/imp_videobyte.go new file mode 100644 index 00000000000..9101e9f2d4a --- /dev/null +++ b/openrtb_ext/imp_videobyte.go @@ -0,0 +1,8 @@ +package openrtb_ext + +// ExtImpVideoByte defines the contract for bidrequest.imp[i].ext.videobyte +type ExtImpVideoByte struct { + PublisherId string `json:"pubId"` + PlacementId string `json:"placementId"` + NetworkId string `json:"nid"` +} diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index bf875123ea1..a65e8d3c7b1 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -37,6 +37,7 @@ type ExtRequestPrebid struct { StoredRequest *ExtStoredRequest `json:"storedrequest,omitempty"` SupportDeals bool `json:"supportdeals,omitempty"` Targeting *ExtRequestTargeting `json:"targeting,omitempty"` + BidderParams json.RawMessage `json:"bidderparams,omitempty"` // NoSale specifies bidders with whom the publisher has a legal relationship where the // passing of personally identifiable information doesn't constitute a sale per CCPA law. @@ -44,6 +45,22 @@ type ExtRequestPrebid struct { NoSale []string `json:"nosale,omitempty"` CurrencyConversions *ExtRequestCurrency `json:"currency,omitempty"` + BidderConfigs []BidderConfig `json:"bidderconfig,omitempty"` +} + +type BidderConfig struct { + Bidders []string `json:"bidders,omitempty"` + Config *Config `json:"config,omitempty"` +} + +type Config struct { + ORTB2 *ORTB2 `json:"ortb2,omitempty"` +} + +type ORTB2 struct { //First party data + Site map[string]json.RawMessage `json:"site,omitempty"` + App map[string]json.RawMessage `json:"app,omitempty"` + User map[string]json.RawMessage `json:"user,omitempty"` } type ExtRequestCurrency struct { @@ -318,6 +335,7 @@ var priceGranularityAuto = PriceGranularity{ // ExtRequestPrebidData defines Prebid's First Party Data (FPD) and related bid request options. type ExtRequestPrebidData struct { EidPermissions []ExtRequestPrebidDataEidPermission `json:"eidpermissions"` + Bidders []string `json:"bidders,omitempty"` } // ExtRequestPrebidDataEidPermission defines a filter rule for filter user.ext.eids diff --git a/openrtb_ext/request_wrapper.go b/openrtb_ext/request_wrapper.go index 276f8f5eebe..9219bbdbc9e 100644 --- a/openrtb_ext/request_wrapper.go +++ b/openrtb_ext/request_wrapper.go @@ -210,7 +210,7 @@ func (rw *RequestWrapper) rebuildSiteExt() error { if err != nil { return err } - rw.Regs.Ext = siteJson + rw.Site.Ext = siteJson } return nil } diff --git a/pbs/pbsrequest.go b/pbs/pbsrequest.go deleted file mode 100644 index c05b9a8c00d..00000000000 --- a/pbs/pbsrequest.go +++ /dev/null @@ -1,403 +0,0 @@ -package pbs - -import ( - "encoding/json" - "fmt" - "math/rand" - "net/http" - "net/url" - "strconv" - "strings" - "time" - - "github.com/prebid/prebid-server/cache" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/usersync" - "github.com/prebid/prebid-server/util/httputil" - "github.com/prebid/prebid-server/util/iputil" - - "github.com/blang/semver" - "github.com/buger/jsonparser" - "github.com/golang/glog" - "github.com/mxmCherry/openrtb/v15/openrtb2" - "golang.org/x/net/publicsuffix" -) - -const MAX_BIDDERS = 8 - -type MediaType byte - -const ( - MEDIA_TYPE_BANNER MediaType = iota - MEDIA_TYPE_VIDEO -) - -type ConfigCache interface { - LoadConfig(string) ([]Bids, error) -} - -type Bids struct { - BidderCode string `json:"bidder"` - BidID string `json:"bid_id"` - Params json.RawMessage `json:"params"` -} - -// Structure for holding video-specific information -type PBSVideo struct { - //Content MIME types supported. Popular MIME types may include “video/x-ms-wmv” for Windows Media and “video/x-flv” for Flash Video. - Mimes []string `json:"mimes,omitempty"` - - //Minimum video ad duration in seconds. - Minduration int64 `json:"minduration,omitempty"` - - // Maximum video ad duration in seconds. - Maxduration int64 `json:"maxduration,omitempty"` - - //Indicates the start delay in seconds for pre-roll, mid-roll, or post-roll ad placements. - Startdelay int64 `json:"startdelay,omitempty"` - - // Indicates if the player will allow the video to be skipped ( 0 = no, 1 = yes). - Skippable int `json:"skippable,omitempty"` - - // Playback method code Description - // 1 - Initiates on Page Load with Sound On - // 2 - Initiates on Page Load with Sound Off by Default - // 3 - Initiates on Click with Sound On - // 4 - Initiates on Mouse-Over with Sound On - // 5 - Initiates on Entering Viewport with Sound On - // 6 - Initiates on Entering Viewport with Sound Off by Default - PlaybackMethod int8 `json:"playback_method,omitempty"` - - //protocols as specified in ORTB 5.8 - // 1 VAST 1.0 - // 2 VAST 2.0 - // 3 VAST 3.0 - // 4 VAST 1.0 Wrapper - // 5 VAST 2.0 Wrapper - // 6 VAST 3.0 Wrapper - // 7 VAST 4.0 - // 8 VAST 4.0 Wrapper - // 9 DAAST 1.0 - // 10 DAAST 1.0 Wrapper - Protocols []int8 `json:"protocols,omitempty"` -} - -type AdUnit struct { - Code string `json:"code"` - TopFrame int8 `json:"is_top_frame"` - Sizes []openrtb2.Format `json:"sizes"` - Bids []Bids `json:"bids"` - ConfigID string `json:"config_id"` - MediaTypes []string `json:"media_types"` - Instl int8 `json:"instl"` - Video PBSVideo `json:"video"` -} - -type PBSAdUnit struct { - Sizes []openrtb2.Format - TopFrame int8 - Code string - BidID string - Params json.RawMessage - Video PBSVideo - MediaTypes []MediaType - Instl int8 -} - -func ParseMediaType(s string) (MediaType, error) { - mediaTypes := map[string]MediaType{"BANNER": MEDIA_TYPE_BANNER, "VIDEO": MEDIA_TYPE_VIDEO} - t, ok := mediaTypes[strings.ToUpper(s)] - if !ok { - return 0, fmt.Errorf("Invalid MediaType %s", s) - } - return t, nil -} - -type SDK struct { - Version string `json:"version"` - Source string `json:"source"` - Platform string `json:"platform"` -} - -type PBSBidder struct { - BidderCode string `json:"bidder"` - AdUnitCode string `json:"ad_unit,omitempty"` // for index to dedup responses - ResponseTime int `json:"response_time_ms,omitempty"` - NumBids int `json:"num_bids,omitempty"` - Error string `json:"error,omitempty"` - NoCookie bool `json:"no_cookie,omitempty"` - NoBid bool `json:"no_bid,omitempty"` - UsersyncInfo *UsersyncInfo `json:"usersync,omitempty"` - Debug []*BidderDebug `json:"debug,omitempty"` - - AdUnits []PBSAdUnit `json:"-"` -} - -type UsersyncInfo struct { - URL string `json:"url,omitempty"` - Type string `json:"type,omitempty"` - SupportCORS bool `json:"supportCORS,omitempty"` -} - -func (bidder *PBSBidder) LookupBidID(Code string) string { - for _, unit := range bidder.AdUnits { - if unit.Code == Code { - return unit.BidID - } - } - return "" -} - -func (bidder *PBSBidder) LookupAdUnit(Code string) (unit *PBSAdUnit) { - for _, unit := range bidder.AdUnits { - if unit.Code == Code { - return &unit - } - } - return nil -} - -type PBSRequest struct { - AccountID string `json:"account_id"` - Tid string `json:"tid"` - CacheMarkup int8 `json:"cache_markup"` - SortBids int8 `json:"sort_bids"` - MaxKeyLength int8 `json:"max_key_length"` - Secure int8 `json:"secure"` - TimeoutMillis int64 `json:"timeout_millis"` - AdUnits []AdUnit `json:"ad_units"` - IsDebug bool `json:"is_debug"` - App *openrtb2.App `json:"app"` - Device *openrtb2.Device `json:"device"` - PBSUser json.RawMessage `json:"user"` - SDK *SDK `json:"sdk"` - - // internal - Bidders []*PBSBidder `json:"-"` - User *openrtb2.User `json:"-"` - Cookie *usersync.Cookie `json:"-"` - Url string `json:"-"` - Domain string `json:"-"` - Regs *openrtb2.Regs `json:"-"` - Start time.Time -} - -func ConfigGet(cache cache.Cache, id string) ([]Bids, error) { - conf, err := cache.Config().Get(id) - if err != nil { - return nil, err - } - - bids := make([]Bids, 0) - err = json.Unmarshal([]byte(conf), &bids) - if err != nil { - return nil, err - } - - return bids, nil -} - -func ParseMediaTypes(types []string) []MediaType { - var mtypes []MediaType - mtmap := make(map[MediaType]bool) - - if types == nil { - mtypes = append(mtypes, MEDIA_TYPE_BANNER) - } else { - for _, t := range types { - mt, er := ParseMediaType(t) - if er != nil { - glog.Infof("Invalid media type: %s", er) - } else { - if !mtmap[mt] { - mtypes = append(mtypes, mt) - mtmap[mt] = true - } - } - } - if len(mtypes) == 0 { - mtypes = append(mtypes, MEDIA_TYPE_BANNER) - } - } - return mtypes -} - -var ipv4Validator iputil.IPValidator = iputil.VersionIPValidator{Version: iputil.IPv4} - -func ParsePBSRequest(r *http.Request, cfg *config.AuctionTimeouts, cache cache.Cache, hostCookieConfig *config.HostCookie) (*PBSRequest, error) { - defer r.Body.Close() - - pbsReq := &PBSRequest{} - err := json.NewDecoder(r.Body).Decode(&pbsReq) - if err != nil { - return nil, err - } - pbsReq.Start = time.Now() - - if len(pbsReq.AdUnits) == 0 { - return nil, fmt.Errorf("No ad units specified") - } - - pbsReq.TimeoutMillis = int64(cfg.LimitAuctionTimeout(time.Duration(pbsReq.TimeoutMillis)*time.Millisecond) / time.Millisecond) - - if pbsReq.Device == nil { - pbsReq.Device = &openrtb2.Device{} - } - if ip, _ := httputil.FindIP(r, ipv4Validator); ip != nil { - pbsReq.Device.IP = ip.String() - } - - if pbsReq.SDK == nil { - pbsReq.SDK = &SDK{} - } - - // Early versions of prebid mobile are sending requests with gender indicated by numbers, - // those traffic can't be parsed by latest Prebid Server after the change of gender to use string so clients using early versions can't be monetized. - // To handle those traffic, adding a check here to ignore the sent gender for versions lower than 0.0.2. - v1, err := semver.Make(pbsReq.SDK.Version) - v2, err := semver.Make("0.0.2") - if v1.Compare(v2) >= 0 && pbsReq.PBSUser != nil { - err = json.Unmarshal([]byte(pbsReq.PBSUser), &pbsReq.User) - if err != nil { - return nil, err - } - } - - if pbsReq.User == nil { - pbsReq.User = &openrtb2.User{} - } - - // use client-side data for web requests - if pbsReq.App == nil { - pbsReq.Cookie = usersync.ParseCookieFromRequest(r, hostCookieConfig) - - pbsReq.Device.UA = r.Header.Get("User-Agent") - - pbsReq.Url = r.Header.Get("Referer") // must be specified in the header - // TODO: this should explicitly put us in test mode - if r.FormValue("url_override") != "" { - pbsReq.Url = r.FormValue("url_override") - } - if strings.Index(pbsReq.Url, "http") == -1 { - pbsReq.Url = fmt.Sprintf("http://%s", pbsReq.Url) - } - - url, err := url.Parse(pbsReq.Url) - if err != nil { - return nil, fmt.Errorf("Invalid URL '%s': %v", pbsReq.Url, err) - } - - if url.Host == "" { - return nil, fmt.Errorf("Host not found from URL '%v'", url) - } - - pbsReq.Domain, err = publicsuffix.EffectiveTLDPlusOne(url.Host) - if err != nil { - return nil, fmt.Errorf("Invalid URL '%s': %v", url.Host, err) - } - } - - if r.FormValue("debug") == "1" { - pbsReq.IsDebug = true - } - - if httputil.IsSecure(r) { - pbsReq.Secure = 1 - } - - pbsReq.Bidders = make([]*PBSBidder, 0, MAX_BIDDERS) - - for _, unit := range pbsReq.AdUnits { - bidders := unit.Bids - if unit.ConfigID != "" { - bidders, err = ConfigGet(cache, unit.ConfigID) - if err != nil { - if _, notFound := err.(*stored_requests.NotFoundError); !notFound { - glog.Warningf("Failed to load config '%s' from cache: %v", unit.ConfigID, err) - } - // proceed with other ad units - continue - } - } - - if glog.V(2) { - glog.Infof("Ad unit %s has %d bidders for %d sizes", unit.Code, len(bidders), len(unit.Sizes)) - } - - mtypes := ParseMediaTypes(unit.MediaTypes) - for _, b := range bidders { - var bidder *PBSBidder - for _, pb := range pbsReq.Bidders { - if pb.BidderCode == b.BidderCode { - bidder = pb - } - } - - if bidder == nil { - bidder = &PBSBidder{BidderCode: b.BidderCode} - pbsReq.Bidders = append(pbsReq.Bidders, bidder) - } - if b.BidID == "" { - b.BidID = fmt.Sprintf("%d", rand.Int63()) - } - - pau := PBSAdUnit{ - Sizes: unit.Sizes, - TopFrame: unit.TopFrame, - Code: unit.Code, - Instl: unit.Instl, - Params: b.Params, - BidID: b.BidID, - MediaTypes: mtypes, - Video: unit.Video, - } - - bidder.AdUnits = append(bidder.AdUnits, pau) - } - } - - return pbsReq, nil -} - -func (req PBSRequest) Elapsed() int { - return int(time.Since(req.Start) / 1000000) -} - -func (p PBSRequest) String() string { - b, _ := json.MarshalIndent(p, "", " ") - return string(b) -} - -// parses the "Regs.ext.gdpr" from the request, if it exists. Otherwise returns an empty string. -func (req *PBSRequest) ParseGDPR() string { - if req == nil || req.Regs == nil || len(req.Regs.Ext) == 0 { - return "" - } - val, err := jsonparser.GetInt(req.Regs.Ext, "gdpr") - if err != nil { - return "" - } - gdpr := strconv.Itoa(int(val)) - - return gdpr -} - -// parses the "User.ext.consent" from the request, if it exists. Otherwise returns an empty string. -func (req *PBSRequest) ParseConsent() string { - if req == nil || req.User == nil { - return "" - } - return parseString(req.User.Ext, "consent") -} - -func parseString(data []byte, key string) string { - if len(data) == 0 { - return "" - } - val, err := jsonparser.GetString(data, key) - if err != nil { - return "" - } - return val -} diff --git a/pbs/pbsrequest_test.go b/pbs/pbsrequest_test.go deleted file mode 100644 index 52cd6153323..00000000000 --- a/pbs/pbsrequest_test.go +++ /dev/null @@ -1,735 +0,0 @@ -package pbs - -import ( - "bytes" - "fmt" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/magiconair/properties/assert" - "github.com/prebid/prebid-server/cache/dummycache" - "github.com/prebid/prebid-server/config" -) - -const mimeVideoMp4 = "video/mp4" -const mimeVideoFlv = "video/x-flv" - -func TestParseMediaTypes(t *testing.T) { - types1 := []string{"Banner"} - t1 := ParseMediaTypes(types1) - assert.Equal(t, len(t1), 1) - assert.Equal(t, t1[0], MEDIA_TYPE_BANNER) - - types2 := []string{"Banner", "Video"} - t2 := ParseMediaTypes(types2) - assert.Equal(t, len(t2), 2) - assert.Equal(t, t2[0], MEDIA_TYPE_BANNER) - assert.Equal(t, t2[1], MEDIA_TYPE_VIDEO) - - types3 := []string{"Banner", "Vo"} - t3 := ParseMediaTypes(types3) - assert.Equal(t, len(t3), 1) - assert.Equal(t, t3[0], MEDIA_TYPE_BANNER) -} - -func TestParseSimpleRequest(t *testing.T) { - body := []byte(`{ - "tid": "abcd", - "ad_units": [ - { - "code": "first", - "sizes": [{"w": 300, "h": 250}], - "bids": [ - { - "bidder": "ix" - }, - { - "bidder": "appnexus" - } - ] - }, - { - "code": "second", - "sizes": [{"w": 728, "h": 90}], - "media_types" :["banner", "video"], - "video" : { - "mimes" : ["video/mp4", "video/x-flv"] - }, - "bids": [ - { - "bidder": "ix" - }, - { - "bidder": "appnexus" - } - ] - } - - ] - } - `) - r := httptest.NewRequest("POST", "/auction", bytes.NewBuffer(body)) - r.Header.Add("Referer", "http://nytimes.com/cool.html") - d, _ := dummycache.New() - hcc := config.HostCookie{} - - pbs_req, err := ParsePBSRequest(r, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, d, &hcc) - if err != nil { - t.Fatalf("Parse simple request failed: %v", err) - } - if pbs_req.Tid != "abcd" { - t.Errorf("Parse TID failed") - } - if len(pbs_req.AdUnits) != 2 { - t.Errorf("Parse ad units failed") - } - - // see if our internal representation is intact - if len(pbs_req.Bidders) != 2 { - t.Fatalf("Should have two bidders not %d", len(pbs_req.Bidders)) - } - if pbs_req.Bidders[0].BidderCode != "ix" { - t.Errorf("First bidder not index") - } - if len(pbs_req.Bidders[0].AdUnits) != 2 { - t.Errorf("Index bidder should have 2 ad unit") - } - if pbs_req.Bidders[1].BidderCode != "appnexus" { - t.Errorf("Second bidder not appnexus") - } - if len(pbs_req.Bidders[1].AdUnits) != 2 { - t.Errorf("AppNexus bidder should have 2 ad unit") - } - if pbs_req.Bidders[1].AdUnits[0].BidID == "" { - t.Errorf("ID should have been generated for empty BidID") - } - if pbs_req.AdUnits[1].MediaTypes[0] != "banner" { - t.Errorf("Instead of banner MediaType received %s", pbs_req.AdUnits[1].MediaTypes[0]) - } - if pbs_req.AdUnits[1].MediaTypes[1] != "video" { - t.Errorf("Instead of video MediaType received %s", pbs_req.AdUnits[1].MediaTypes[0]) - } - if pbs_req.AdUnits[1].Video.Mimes[0] != mimeVideoMp4 { - t.Errorf("Instead of video/mp4 mimes received %s", pbs_req.AdUnits[1].Video.Mimes) - } - if pbs_req.AdUnits[1].Video.Mimes[1] != mimeVideoFlv { - t.Errorf("Instead of video/flv mimes received %s", pbs_req.AdUnits[1].Video.Mimes) - } - -} - -func TestHeaderParsing(t *testing.T) { - body := []byte(`{ - "tid": "abcd", - "ad_units": [ - { - "code": "first", - "sizes": [{"w": 300, "h": 250}], - "bidders": [ - { - "bidder": "ix", - "params": { - "id": "417", - "siteID": "test-site" - } - } - ] - } - ] - } - `) - r := httptest.NewRequest("POST", "/auction", bytes.NewBuffer(body)) - r.Header.Add("Referer", "http://nytimes.com/cool.html") - r.Header.Add("User-Agent", "Mozilla/") - d, _ := dummycache.New() - hcc := config.HostCookie{} - - d.Config().Set("dummy", dummyConfig) - - pbs_req, err := ParsePBSRequest(r, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, d, &hcc) - if err != nil { - t.Fatalf("Parse simple request failed") - } - if pbs_req.Url != "http://nytimes.com/cool.html" { - t.Errorf("Failed to pull URL from referrer") - } - if pbs_req.Domain != "nytimes.com" { - t.Errorf("Failed to parse TLD from referrer: %s not nytimes.com", pbs_req.Domain) - } - if pbs_req.Device.UA != "Mozilla/" { - t.Errorf("Failed to pull User-Agent from referrer") - } -} - -var dummyConfig = ` -[ - { - "bidder": "ix", - "bid_id": "22222222", - "params": { - "id": "4", - "siteID": "186774", - "timeout": "10000" - } - - }, - { - "bidder": "audienceNetwork", - "bid_id": "22222225", - "params": { - } - }, - { - "bidder": "pubmatic", - "bid_id": "22222223", - "params": { - "publisherId": "156009", - "adSlot": "39620189@728x90" - } - }, - { - "bidder": "appnexus", - "bid_id": "22222224", - "params": { - "placementId": "1" - } - } - ] - ` - -func TestParseConfig(t *testing.T) { - body := []byte(`{ - "tid": "abcd", - "ad_units": [ - { - "code": "first", - "sizes": [{"w": 300, "h": 250}], - "bids": [ - { - "bidder": "ix" - }, - { - "bidder": "appnexus" - } - ] - }, - { - "code": "second", - "sizes": [{"w": 728, "h": 90}], - "config_id": "abcd" - } - ] - } - `) - r := httptest.NewRequest("POST", "/auction", bytes.NewBuffer(body)) - r.Header.Add("Referer", "http://nytimes.com/cool.html") - d, _ := dummycache.New() - hcc := config.HostCookie{} - - d.Config().Set("dummy", dummyConfig) - - pbs_req, err := ParsePBSRequest(r, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, d, &hcc) - if err != nil { - t.Fatalf("Parse simple request failed: %v", err) - } - if pbs_req.Tid != "abcd" { - t.Errorf("Parse TID failed") - } - if len(pbs_req.AdUnits) != 2 { - t.Errorf("Parse ad units failed") - } - - // see if our internal representation is intact - if len(pbs_req.Bidders) != 4 { - t.Fatalf("Should have 4 bidders not %d", len(pbs_req.Bidders)) - } - if pbs_req.Bidders[0].BidderCode != "ix" { - t.Errorf("First bidder not index") - } - if len(pbs_req.Bidders[0].AdUnits) != 2 { - t.Errorf("Index bidder should have 1 ad unit") - } - if pbs_req.Bidders[1].BidderCode != "appnexus" { - t.Errorf("Second bidder not appnexus") - } - if len(pbs_req.Bidders[1].AdUnits) != 2 { - t.Errorf("AppNexus bidder should have 2 ad unit") - } -} - -func TestParseMobileRequestFirstVersion(t *testing.T) { - body := []byte(`{ - "max_key_length":20, - "user":{ - "gender":0, - "buyeruid":"test_buyeruid" - }, - "prebid_version":"0.21.0-pre", - "sort_bids":1, - "ad_units":[ - { - "sizes":[ - { - "w":300, - "h":250 - } - ], - "config_id":"ad5ffb41-3492-40f3-9c25-ade093eb4e5f", - "code":"5d748364ee9c46a2b112892fc3551b6f" - } - ], - "cache_markup":1, - "app":{ - "bundle":"AppNexus.PrebidMobileDemo", - "ver":"0.0.1" - }, - "sdk":{ - "version":"0.0.1", - "platform":"iOS", - "source":"prebid-mobile" - }, - "device":{ - "ifa":"test_device_ifa", - "osv":"9.3.5", - "os":"iOS", - "make":"Apple", - "model":"iPhone6,1" - }, - "tid":"abcd", - "account_id":"aecd6ef7-b992-4e99-9bb8-65e2d984e1dd" - } - `) - r := httptest.NewRequest("POST", "/auction", bytes.NewBuffer(body)) - d, _ := dummycache.New() - hcc := config.HostCookie{} - - pbs_req, err := ParsePBSRequest(r, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, d, &hcc) - if err != nil { - t.Fatalf("Parse simple request failed: %v", err) - } - if pbs_req.Tid != "abcd" { - t.Errorf("Parse TID failed") - } - if len(pbs_req.AdUnits) != 1 { - t.Errorf("Parse ad units failed") - } - // We are expecting all user fields to be nil. We don't parse user on v0.0.1 of prebid mobile - if pbs_req.User.BuyerUID != "" { - t.Errorf("Parse user buyeruid failed %s", pbs_req.User.BuyerUID) - } - if pbs_req.User.Gender != "" { - t.Errorf("Parse user gender failed %s", pbs_req.User.Gender) - } - if pbs_req.User.Yob != 0 { - t.Errorf("Parse user year of birth failed %d", pbs_req.User.Yob) - } - if pbs_req.User.ID != "" { - t.Errorf("Parse user id failed %s", pbs_req.User.ID) - } - - if pbs_req.App.Bundle != "AppNexus.PrebidMobileDemo" { - t.Errorf("Parse app bundle failed") - } - if pbs_req.App.Ver != "0.0.1" { - t.Errorf("Parse app version failed") - } - - if pbs_req.Device.IFA != "test_device_ifa" { - t.Errorf("Parse device ifa failed") - } - if pbs_req.Device.OSV != "9.3.5" { - t.Errorf("Parse device osv failed") - } - if pbs_req.Device.OS != "iOS" { - t.Errorf("Parse device os failed") - } - if pbs_req.Device.Make != "Apple" { - t.Errorf("Parse device make failed") - } - if pbs_req.Device.Model != "iPhone6,1" { - t.Errorf("Parse device model failed") - } -} - -func TestParseMobileRequest(t *testing.T) { - body := []byte(`{ - "max_key_length":20, - "user":{ - "gender":"F", - "buyeruid":"test_buyeruid", - "yob":2000, - "id":"testid" - }, - "prebid_version":"0.21.0-pre", - "sort_bids":1, - "ad_units":[ - { - "sizes":[ - { - "w":300, - "h":250 - } - ], - "config_id":"ad5ffb41-3492-40f3-9c25-ade093eb4e5f", - "code":"5d748364ee9c46a2b112892fc3551b6f" - } - ], - "cache_markup":1, - "app":{ - "bundle":"AppNexus.PrebidMobileDemo", - "ver":"0.0.2" - }, - "sdk":{ - "version":"0.0.2", - "platform":"iOS", - "source":"prebid-mobile" - }, - "device":{ - "ifa":"test_device_ifa", - "osv":"9.3.5", - "os":"iOS", - "make":"Apple", - "model":"iPhone6,1" - }, - "tid":"abcd", - "account_id":"aecd6ef7-b992-4e99-9bb8-65e2d984e1dd" - } - `) - r := httptest.NewRequest("POST", "/auction", bytes.NewBuffer(body)) - d, _ := dummycache.New() - hcc := config.HostCookie{} - - pbs_req, err := ParsePBSRequest(r, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, d, &hcc) - if err != nil { - t.Fatalf("Parse simple request failed: %v", err) - } - if pbs_req.Tid != "abcd" { - t.Errorf("Parse TID failed") - } - if len(pbs_req.AdUnits) != 1 { - t.Errorf("Parse ad units failed") - } - - if pbs_req.User.BuyerUID != "test_buyeruid" { - t.Errorf("Parse user buyeruid failed") - } - if pbs_req.User.Gender != "F" { - t.Errorf("Parse user gender failed") - } - if pbs_req.User.Yob != 2000 { - t.Errorf("Parse user year of birth failed") - } - if pbs_req.User.ID != "testid" { - t.Errorf("Parse user id failed") - } - if pbs_req.App.Bundle != "AppNexus.PrebidMobileDemo" { - t.Errorf("Parse app bundle failed") - } - if pbs_req.App.Ver != "0.0.2" { - t.Errorf("Parse app version failed") - } - - if pbs_req.Device.IFA != "test_device_ifa" { - t.Errorf("Parse device ifa failed") - } - if pbs_req.Device.OSV != "9.3.5" { - t.Errorf("Parse device osv failed") - } - if pbs_req.Device.OS != "iOS" { - t.Errorf("Parse device os failed") - } - if pbs_req.Device.Make != "Apple" { - t.Errorf("Parse device make failed") - } - if pbs_req.Device.Model != "iPhone6,1" { - t.Errorf("Parse device model failed") - } - if pbs_req.SDK.Version != "0.0.2" { - t.Errorf("Parse sdk version failed") - } - if pbs_req.SDK.Source != "prebid-mobile" { - t.Errorf("Parse sdk source failed") - } - if pbs_req.SDK.Platform != "iOS" { - t.Errorf("Parse sdk platform failed") - } - if pbs_req.Device.IP == "" { - t.Errorf("Parse device ip failed %s", pbs_req.Device.IP) - } -} - -func TestParseMalformedMobileRequest(t *testing.T) { - body := []byte(`{ - "max_key_length":20, - "user":{ - "gender":0, - "buyeruid":"test_buyeruid" - }, - "prebid_version":"0.21.0-pre", - "sort_bids":1, - "ad_units":[ - { - "sizes":[ - { - "w":300, - "h":250 - } - ], - "config_id":"ad5ffb41-3492-40f3-9c25-ade093eb4e5f", - "code":"5d748364ee9c46a2b112892fc3551b6f" - } - ], - "cache_markup":1, - "app":{ - "bundle":"AppNexus.PrebidMobileDemo", - "ver":"0.0.1" - }, - "device":{ - "ifa":"test_device_ifa", - "osv":"9.3.5", - "os":"iOS", - "make":"Apple", - "model":"iPhone6,1" - }, - "tid":"abcd", - "account_id":"aecd6ef7-b992-4e99-9bb8-65e2d984e1dd" - } - `) - r := httptest.NewRequest("POST", "/auction", bytes.NewBuffer(body)) - d, _ := dummycache.New() - hcc := config.HostCookie{} - - pbs_req, err := ParsePBSRequest(r, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, d, &hcc) - if err != nil { - t.Fatalf("Parse simple request failed: %v", err) - } - if pbs_req.Tid != "abcd" { - t.Errorf("Parse TID failed") - } - if len(pbs_req.AdUnits) != 1 { - t.Errorf("Parse ad units failed") - } - // We are expecting all user fields to be nil. Since no SDK version is passed in - if pbs_req.User.BuyerUID != "" { - t.Errorf("Parse user buyeruid failed %s", pbs_req.User.BuyerUID) - } - if pbs_req.User.Gender != "" { - t.Errorf("Parse user gender failed %s", pbs_req.User.Gender) - } - if pbs_req.User.Yob != 0 { - t.Errorf("Parse user year of birth failed %d", pbs_req.User.Yob) - } - if pbs_req.User.ID != "" { - t.Errorf("Parse user id failed %s", pbs_req.User.ID) - } - - if pbs_req.App.Bundle != "AppNexus.PrebidMobileDemo" { - t.Errorf("Parse app bundle failed") - } - if pbs_req.App.Ver != "0.0.1" { - t.Errorf("Parse app version failed") - } - - if pbs_req.Device.IFA != "test_device_ifa" { - t.Errorf("Parse device ifa failed") - } - if pbs_req.Device.OSV != "9.3.5" { - t.Errorf("Parse device osv failed") - } - if pbs_req.Device.OS != "iOS" { - t.Errorf("Parse device os failed") - } - if pbs_req.Device.Make != "Apple" { - t.Errorf("Parse device make failed") - } - if pbs_req.Device.Model != "iPhone6,1" { - t.Errorf("Parse device model failed") - } -} - -func TestParseRequestWithInstl(t *testing.T) { - body := []byte(`{ - "max_key_length":20, - "user":{ - "gender":"F", - "buyeruid":"test_buyeruid", - "yob":2000, - "id":"testid" - }, - "prebid_version":"0.21.0-pre", - "sort_bids":1, - "ad_units":[ - { - "sizes":[ - { - "w":300, - "h":250 - } - ], - "bids": [ - { - "bidder": "ix" - }, - { - "bidder": "appnexus" - } - ], - "code":"5d748364ee9c46a2b112892fc3551b6f", - "instl": 1 - } - ], - "cache_markup":1, - "app":{ - "bundle":"AppNexus.PrebidMobileDemo", - "ver":"0.0.2" - }, - "sdk":{ - "version":"0.0.2", - "platform":"iOS", - "source":"prebid-mobile" - }, - "device":{ - "ifa":"test_device_ifa", - "osv":"9.3.5", - "os":"iOS", - "make":"Apple", - "model":"iPhone6,1" - }, - "tid":"abcd", - "account_id":"aecd6ef7-b992-4e99-9bb8-65e2d984e1dd" - } - `) - r := httptest.NewRequest("POST", "/auction", bytes.NewBuffer(body)) - d, _ := dummycache.New() - hcc := config.HostCookie{} - - pbs_req, err := ParsePBSRequest(r, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, d, &hcc) - if err != nil { - t.Fatalf("Parse simple request failed: %v", err) - } - if len(pbs_req.Bidders) != 2 { - t.Errorf("Should have 2 bidders. ") - } - if pbs_req.Bidders[0].AdUnits[0].Instl != 1 { - t.Errorf("Parse instl failed.") - } - if pbs_req.Bidders[1].AdUnits[0].Instl != 1 { - t.Errorf("Parse instl failed.") - } - -} - -func TestTimeouts(t *testing.T) { - doTimeoutTest(t, 10, 15, 10, 0) - doTimeoutTest(t, 10, 0, 10, 0) - doTimeoutTest(t, 5, 5, 10, 0) - doTimeoutTest(t, 15, 15, 0, 0) - doTimeoutTest(t, 15, 0, 20, 15) -} - -func doTimeoutTest(t *testing.T, expected int, requested int, max uint64, def uint64) { - t.Helper() - cfg := &config.AuctionTimeouts{ - Default: def, - Max: max, - } - body := fmt.Sprintf(`{ - "tid": "abcd", - "timeout_millis": %d, - "app":{ - "bundle":"AppNexus.PrebidMobileDemo", - "ver":"0.0.2" - }, - "ad_units": [ - { - "code": "first", - "sizes": [{"w": 300, "h": 250}], - "bids": [ - { - "bidder": "ix" - } - ] - } - ] -}`, requested) - r := httptest.NewRequest("POST", "/auction", strings.NewReader(body)) - d, _ := dummycache.New() - parsed, err := ParsePBSRequest(r, cfg, d, &config.HostCookie{}) - if err != nil { - t.Fatalf("Unexpected err: %v", err) - } - if parsed.TimeoutMillis != int64(expected) { - t.Errorf("Expected %dms timeout, got %dms", expected, parsed.TimeoutMillis) - } -} - -func TestParsePBSRequestUsesHostCookie(t *testing.T) { - body := []byte(`{ - "tid": "abcd", - "ad_units": [ - { - "code": "first", - "sizes": [{"w": 300, "h": 250}], - "bidders": [ - { - "bidder": "bidder1", - "params": { - "id": "417", - "siteID": "test-site" - } - } - ] - } - ] - } - `) - r, err := http.NewRequest("POST", "/auction", bytes.NewBuffer(body)) - r.Header.Add("Referer", "http://nytimes.com/cool.html") - if err != nil { - t.Fatalf("new request failed") - } - r.AddCookie(&http.Cookie{Name: "key", Value: "testcookie"}) - d, _ := dummycache.New() - hcc := config.HostCookie{ - CookieName: "key", - Family: "family", - OptOutCookie: config.Cookie{ - Name: "trp_optout", - Value: "true", - }, - } - - pbs_req, err2 := ParsePBSRequest(r, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, d, &hcc) - if err2 != nil { - t.Fatalf("Parse simple request failed %v", err2) - } - if uid, _, _ := pbs_req.Cookie.GetUID("family"); uid != "testcookie" { - t.Errorf("Failed to leverage host cookie space for user identifier") - } -} diff --git a/pbs/pbsresponse.go b/pbs/pbsresponse.go deleted file mode 100644 index b8cf2c19ff7..00000000000 --- a/pbs/pbsresponse.go +++ /dev/null @@ -1,84 +0,0 @@ -package pbs - -// PBSBid is a bid from the auction. These are produced by Adapters, and target a particular Ad Unit. -// -// This JSON format is a contract with both Prebid.js and Prebid-mobile. -// All changes *must* be backwards compatible, since clients cannot be forced to update their code. -type PBSBid struct { - // BidID identifies the Bid Request within the Ad Unit which this Bid targets. It should match one of - // the values inside PBSRequest.AdUnits[i].Bids[j].BidID. - BidID string `json:"bid_id"` - // AdUnitCode identifies the AdUnit which this Bid targets. - // It should match one of PBSRequest.AdUnits[i].Code, where "i" matches the AdUnit used in - // as BidID. - AdUnitCode string `json:"code"` - // Creative_id uniquely identifies the creative being served. It is not used by prebid-server, but - // it helps publishers and bidders identify and communicate about malicious or inappropriate ads. - // This project simply passes it along with the bid. - Creative_id string `json:"creative_id,omitempty"` - // CreativeMediaType shows whether the creative is a video or banner. - CreativeMediaType string `json:"media_type,omitempty"` - // BidderCode is the PBSBidder.BidderCode of the PBSBidder who made this bid. - BidderCode string `json:"bidder"` - // BidHash is the hash of the bidder's unique bid identifier for blockchain. It should not be sent to browser. - BidHash string `json:"-"` - // Price is the cpm, in US Dollars, which the bidder is willing to pay if this bid is chosen. - // TODO: Add support for other currencies someday. - Price float64 `json:"price"` - // NURL is a URL which returns ad markup, and should be called if the bid wins. - // If NURL and Adm are both defined, then Adm takes precedence. - NURL string `json:"nurl,omitempty"` - // Adm is the ad markup which should be used to deliver the ad, if this bid is chosen. - // If NURL and Adm are both defined, then Adm takes precedence. - Adm string `json:"adm,omitempty"` - // Width is the intended width which Adm should be shown, in pixels. - Width int64 `json:"width,omitempty"` - // Height is the intended width which Adm should be shown, in pixels. - Height int64 `json:"height,omitempty"` - // DealId is not used by prebid-server, but may be used by buyers and sellers who make special - // deals with each other. We simply pass this information along with the bid. - DealId string `json:"deal_id,omitempty"` - // CacheId is an ID in prebid-cache which can be used to fetch this ad's content. - // This supports prebid-mobile, which requires that the content be available from a URL. - CacheID string `json:"cache_id,omitempty"` - // Complete cache url returned from the prebid-cache. - // more flexible than a design that assumes the UUID is always appended to the end of the URL. - CacheURL string `json:"cache_url,omitempty"` - // ResponseTime is the number of milliseconds it took for the adapter to return a bid. - ResponseTime int `json:"response_time_ms,omitempty"` - AdServerTargeting map[string]string `json:"ad_server_targeting,omitempty"` -} - -// PBSBidSlice attaches the methods of sort.Interface to []PBSBid, ordering them by price. -// If two prices are equal, then the response time will be used as a tiebreaker. -// For more information, see https://golang.org/pkg/sort/#Interface -type PBSBidSlice []*PBSBid - -func (bids PBSBidSlice) Len() int { - return len(bids) -} - -func (bids PBSBidSlice) Less(i, j int) bool { - bidiResponseTimeInTerras := (float64(bids[i].ResponseTime) / 1000000000.0) - bidjResponseTimeInTerras := (float64(bids[j].ResponseTime) / 1000000000.0) - return bids[i].Price-bidiResponseTimeInTerras > bids[j].Price-bidjResponseTimeInTerras -} - -func (bids PBSBidSlice) Swap(i, j int) { - bids[i], bids[j] = bids[j], bids[i] -} - -type BidderDebug struct { - RequestURI string `json:"request_uri,omitempty"` - RequestBody string `json:"request_body,omitempty"` - ResponseBody string `json:"response_body,omitempty"` - StatusCode int `json:"status_code,omitempty"` -} - -type PBSResponse struct { - TID string `json:"tid,omitempty"` - Status string `json:"status,omitempty"` - BidderStatus []*PBSBidder `json:"bidder_status,omitempty"` - Bids PBSBidSlice `json:"bids,omitempty"` - BUrl string `json:"burl,omitempty"` -} diff --git a/pbs/pbsresponse_test.go b/pbs/pbsresponse_test.go deleted file mode 100644 index 0e51120cdf4..00000000000 --- a/pbs/pbsresponse_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package pbs - -import ( - "sort" - "testing" -) - -func TestSortBids(t *testing.T) { - bid1 := PBSBid{ - BidID: "testBidId", - AdUnitCode: "testAdUnitCode", - BidderCode: "testBidderCode", - Price: 0.0, - } - bid2 := PBSBid{ - BidID: "testBidId", - AdUnitCode: "testAdUnitCode", - BidderCode: "testBidderCode", - Price: 4.0, - } - bid3 := PBSBid{ - BidID: "testBidId", - AdUnitCode: "testAdUnitCode", - BidderCode: "testBidderCode", - Price: 2.0, - } - bid4 := PBSBid{ - BidID: "testBidId", - AdUnitCode: "testAdUnitCode", - BidderCode: "testBidderCode", - Price: 0.50, - } - - bids := make(PBSBidSlice, 0) - bids = append(bids, &bid1, &bid2, &bid3, &bid4) - - sort.Sort(bids) - if bids[0].Price != 4.0 { - t.Error("Expected 4.00 to be highest price") - } - if bids[1].Price != 2.0 { - t.Error("Expected 2.00 to be second highest price") - } - if bids[2].Price != 0.5 { - t.Error("Expected 0.50 to be third highest price") - } - if bids[3].Price != 0.0 { - t.Error("Expected 0.00 to be lowest price") - } -} - -func TestSortBidsWithResponseTimes(t *testing.T) { - bid1 := PBSBid{ - BidID: "testBidId", - AdUnitCode: "testAdUnitCode", - BidderCode: "testBidderCode", - Price: 1.0, - ResponseTime: 70, - } - bid2 := PBSBid{ - BidID: "testBidId", - AdUnitCode: "testAdUnitCode", - BidderCode: "testBidderCode", - Price: 1.0, - ResponseTime: 20, - } - bid3 := PBSBid{ - BidID: "testBidId", - AdUnitCode: "testAdUnitCode", - BidderCode: "testBidderCode", - Price: 1.0, - ResponseTime: 99, - } - - bids := make(PBSBidSlice, 0) - bids = append(bids, &bid1, &bid2, &bid3) - - sort.Sort(bids) - if bids[0] != &bid2 { - t.Error("Expected bid 2 to win") - } - if bids[1] != &bid1 { - t.Error("Expected bid 1 to be second") - } - if bids[2] != &bid3 { - t.Error("Expected bid 3 to be last") - } -} diff --git a/pbs/usersync.go b/pbs/usersync.go index 85b55f42aeb..d5043b8c13f 100644 --- a/pbs/usersync.go +++ b/pbs/usersync.go @@ -7,13 +7,10 @@ import ( "net/http" "net/url" "strings" - "time" "github.com/golang/glog" "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/analytics" "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" "github.com/prebid/prebid-server/server/ssl" "github.com/prebid/prebid-server/usersync" ) @@ -21,27 +18,10 @@ import ( // Recaptcha code from https://github.com/haisum/recaptcha/blob/master/recaptcha.go const RECAPTCHA_URL = "https://www.google.com/recaptcha/api/siteverify" -const ( - USERSYNC_OPT_OUT = "usersync.opt_outs" - USERSYNC_BAD_REQUEST = "usersync.bad_requests" - USERSYNC_SUCCESS = "usersync.%s.sets" -) - -// uidWithExpiry bundles the UID with an Expiration date. -// After the expiration, the UID is no longer valid. -type uidWithExpiry struct { - // UID is the ID given to a user by a particular bidder - UID string `json:"uid"` - // Expires is the time at which this UID should no longer apply. - Expires time.Time `json:"expires"` -} - type UserSyncDeps struct { ExternalUrl string RecaptchaSecret string HostCookieConfig *config.HostCookie - MetricsEngine metrics.MetricsEngine - PBSAnalytics analytics.PBSAnalyticsModule } // Struct for parsing json in google's response @@ -79,7 +59,7 @@ func (deps *UserSyncDeps) OptOut(w http.ResponseWriter, r *http.Request, _ httpr rr := r.FormValue("g-recaptcha-response") if rr == "" { - http.Redirect(w, r, fmt.Sprintf("%s/static/optout.html", deps.ExternalUrl), 301) + http.Redirect(w, r, fmt.Sprintf("%s/static/optout.html", deps.ExternalUrl), http.StatusMovedPermanently) return } @@ -98,8 +78,8 @@ func (deps *UserSyncDeps) OptOut(w http.ResponseWriter, r *http.Request, _ httpr pc.SetCookieOnResponse(w, false, deps.HostCookieConfig, deps.HostCookieConfig.TTLDuration()) if optout == "" { - http.Redirect(w, r, deps.HostCookieConfig.OptInURL, 301) + http.Redirect(w, r, deps.HostCookieConfig.OptInURL, http.StatusMovedPermanently) } else { - http.Redirect(w, r, deps.HostCookieConfig.OptOutURL, 301) + http.Redirect(w, r, deps.HostCookieConfig.OptOutURL, http.StatusMovedPermanently) } } diff --git a/prebid_cache_client/client.go b/prebid_cache_client/client.go index 730d54b0acb..a24a139ea1d 100644 --- a/prebid_cache_client/client.go +++ b/prebid_cache_client/client.go @@ -120,7 +120,7 @@ func (c *clientImpl) PutJson(ctx context.Context, values []Cacheable) (uuids []s responseBody, err := ioutil.ReadAll(anResp.Body) if anResp.StatusCode != 200 { - logError(&errs, "Prebid Cache call to %s returned %d: %s", putURL, anResp.StatusCode, responseBody) + logError(&errs, "Prebid Cache call to %s returned %d: %s", c.putUrl, anResp.StatusCode, responseBody) return uuidsToReturn, errs } diff --git a/prebid_cache_client/client_test.go b/prebid_cache_client/client_test.go index 60237bbbb27..ec390364849 100644 --- a/prebid_cache_client/client_test.go +++ b/prebid_cache_client/client_test.go @@ -280,10 +280,18 @@ func assertStringEqual(t *testing.T, expected, actual string) { } } +type handlerResponseObject struct { + UUID string `json:"uuid"` +} + +type handlerResponse struct { + Responses []handlerResponseObject `json:"responses"` +} + func newHandler(numResponses int) http.HandlerFunc { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - resp := response{ - Responses: make([]responseObject, numResponses), + resp := handlerResponse{ + Responses: make([]handlerResponseObject, numResponses), } for i := 0; i < numResponses; i++ { resp.Responses[i].UUID = strconv.Itoa(i) diff --git a/prebid_cache_client/prebid_cache.go b/prebid_cache_client/prebid_cache.go deleted file mode 100644 index cde7ec8d951..00000000000 --- a/prebid_cache_client/prebid_cache.go +++ /dev/null @@ -1,122 +0,0 @@ -package prebid_cache_client - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "net/http" - - "golang.org/x/net/context/ctxhttp" -) - -// This file is deprecated, and is only used to cache things for the legacy (/auction) endpoint. -// For /openrtb2/auction cache, see client.go in this package. - -type CacheObject struct { - Value interface{} - UUID string - IsVideo bool -} - -type BidCache struct { - Adm string `json:"adm,omitempty"` - NURL string `json:"nurl,omitempty"` - Width int64 `json:"width,omitempty"` - Height int64 `json:"height,omitempty"` -} - -// internal protocol objects -type putObject struct { - Type string `json:"type"` - Value interface{} `json:"value"` -} - -type putRequest struct { - Puts []putObject `json:"puts"` -} - -type responseObject struct { - UUID string `json:"uuid"` -} -type response struct { - Responses []responseObject `json:"responses"` -} - -var ( - client *http.Client - baseURL string - putURL string -) - -// InitPrebidCache setup the global prebid cache -func InitPrebidCache(baseurl string) { - baseURL = baseurl - putURL = fmt.Sprintf("%s/cache", baseURL) - - ts := &http.Transport{ - MaxIdleConns: 10, - IdleConnTimeout: 65, - } - - client = &http.Client{ - Transport: ts, - } -} - -// Put will send the array of objs and update each with a UUID -func Put(ctx context.Context, objs []*CacheObject) error { - // Fixes #197 - if len(objs) == 0 { - return nil - } - pr := putRequest{Puts: make([]putObject, len(objs))} - for i, obj := range objs { - if obj.IsVideo { - pr.Puts[i].Type = "xml" - } else { - pr.Puts[i].Type = "json" - } - pr.Puts[i].Value = obj.Value - } - // Don't want to escape the HTML for adm and nurl - buf := new(bytes.Buffer) - enc := json.NewEncoder(buf) - enc.SetEscapeHTML(false) - err := enc.Encode(pr) - if err != nil { - return err - } - - httpReq, err := http.NewRequest("POST", putURL, buf) - if err != nil { - return err - } - httpReq.Header.Add("Content-Type", "application/json;charset=utf-8") - httpReq.Header.Add("Accept", "application/json") - - anResp, err := ctxhttp.Do(ctx, client, httpReq) - if err != nil { - return err - } - defer anResp.Body.Close() - - if anResp.StatusCode != 200 { - return fmt.Errorf("HTTP status code %d", anResp.StatusCode) - } - - var resp response - if err := json.NewDecoder(anResp.Body).Decode(&resp); err != nil { - return err - } - - if len(resp.Responses) != len(objs) { - return fmt.Errorf("Put response length didn't match") - } - - for i, r := range resp.Responses { - objs[i].UUID = r.UUID - } - - return nil -} diff --git a/prebid_cache_client/prebid_cache_test.go b/prebid_cache_client/prebid_cache_test.go deleted file mode 100644 index 65688789fd0..00000000000 --- a/prebid_cache_client/prebid_cache_test.go +++ /dev/null @@ -1,150 +0,0 @@ -package prebid_cache_client - -import ( - "context" - "encoding/json" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - "time" - - "fmt" -) - -var delay time.Duration -var ( - MaxValueLength = 1024 * 10 - MaxNumValues = 10 -) - -type putAnyObject struct { - Type string `json:"type"` - Value json.RawMessage `json:"value"` -} - -type putAnyRequest struct { - Puts []putAnyObject `json:"puts"` -} - -func DummyPrebidCacheServer(w http.ResponseWriter, r *http.Request) { - body, err := ioutil.ReadAll(r.Body) - if err != nil { - http.Error(w, "Failed to read the request body.", http.StatusBadRequest) - return - } - defer r.Body.Close() - var put putAnyRequest - - err = json.Unmarshal(body, &put) - if err != nil { - http.Error(w, "Request body "+string(body)+" is not valid JSON.", http.StatusBadRequest) - return - } - - if len(put.Puts) > MaxNumValues { - http.Error(w, fmt.Sprintf("More keys than allowed: %d", MaxNumValues), http.StatusBadRequest) - return - } - - resp := response{ - Responses: make([]responseObject, len(put.Puts)), - } - for i, p := range put.Puts { - resp.Responses[i].UUID = fmt.Sprintf("UUID-%d", i+1) // deterministic for testing - if len(p.Value) > MaxValueLength { - http.Error(w, fmt.Sprintf("Value is larger than allowed size: %d", MaxValueLength), http.StatusBadRequest) - return - } - if len(p.Value) == 0 { - http.Error(w, "Missing value.", http.StatusBadRequest) - return - } - if p.Type != "xml" && p.Type != "json" { - http.Error(w, fmt.Sprintf("Type must be one of [\"json\", \"xml\"]. Found %v", p.Type), http.StatusBadRequest) - return - } - } - - bytes, err := json.Marshal(&resp) - if err != nil { - http.Error(w, "Failed to serialize UUIDs into JSON.", http.StatusInternalServerError) - return - } - if delay > 0 { - <-time.After(delay) - } - w.Header().Set("Content-Type", "application/json") - w.Write(bytes) -} - -func TestPrebidClient(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(DummyPrebidCacheServer)) - defer server.Close() - - cobj := make([]*CacheObject, 3) - - // example bids - cobj[0] = &CacheObject{ - IsVideo: false, - Value: &BidCache{ - Adm: "{\"type\":\"ID\",\"bid_id\":\"8255649814109237089\",\"placement_id\":\"1995257847363113_1997038003851764\",\"resolved_placement_id\":\"1995257847363113_1997038003851764\",\"sdk_version\":\"4.25.0-appnexus.bidding\",\"device_id\":\"87ECBA49-908A-428F-9DE7-4B9CED4F486C\",\"template\":7,\"payload\":\"null\"}", - NURL: "https://www.facebook.com/audiencenetwork/nurl/?partner=442648859414574&app=1995257847363113&placement=1997038003851764&auction=d3013e9e-ca55-4a86-9baa-d44e31355e1d&impression=bannerad1&request=7187783259538616534&bid=3832427901228167009&ortb_loss_code=0", - Width: 300, - Height: 250, - }, - } - cobj[1] = &CacheObject{ - IsVideo: false, - Value: &BidCache{ - Adm: "", - Width: 300, - Height: 250, - }, - } - cobj[2] = &CacheObject{ - IsVideo: true, - Value: "", - } - InitPrebidCache(server.URL) - - ctx := context.TODO() - err := Put(ctx, cobj) - if err != nil { - t.Fatalf("pbc put failed: %v", err) - } - - if cobj[0].UUID != "UUID-1" { - t.Errorf("First object UUID was '%s', should have been 'UUID-1'", cobj[0].UUID) - } - if cobj[1].UUID != "UUID-2" { - t.Errorf("Second object UUID was '%s', should have been 'UUID-2'", cobj[1].UUID) - } - if cobj[2].UUID != "UUID-3" { - t.Errorf("Third object UUID was '%s', should have been 'UUID-3'", cobj[2].UUID) - } - - delay = 5 * time.Millisecond - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) - defer cancel() - - err = Put(ctx, cobj) - if err == nil { - t.Fatalf("pbc put succeeded but should have timed out") - } -} - -// Prevents #197 -func TestEmptyBids(t *testing.T) { - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - t.Errorf("The server should not be called.") - }) - server := httptest.NewServer(handler) - defer server.Close() - - InitPrebidCache(server.URL) - - if err := Put(context.Background(), []*CacheObject{}); err != nil { - t.Errorf("Error on Put: %v", err) - } -} diff --git a/privacy/ccpa/parsedpolicy_test.go b/privacy/ccpa/parsedpolicy_test.go index 33563b50567..4bbb4cbfd0f 100644 --- a/privacy/ccpa/parsedpolicy_test.go +++ b/privacy/ccpa/parsedpolicy_test.go @@ -3,9 +3,7 @@ package ccpa import ( "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" ) func TestValidateConsent(t *testing.T) { @@ -380,12 +378,3 @@ func TestShouldEnforce(t *testing.T) { assert.Equal(t, test.expected, result, test.description) } } - -type mockPolicWriter struct { - mock.Mock -} - -func (m *mockPolicWriter) Write(req *openrtb2.BidRequest) error { - args := m.Called(req) - return args.Error(0) -} diff --git a/router/router.go b/router/router.go index 90074753a5b..81623f13838 100644 --- a/router/router.go +++ b/router/router.go @@ -3,7 +3,6 @@ package router import ( "context" "crypto/tls" - "database/sql" "encoding/json" "fmt" "io/ioutil" @@ -12,34 +11,17 @@ import ( "strings" "time" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/endpoints/events" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/util/uuidutil" - "github.com/prebid/prebid-server/version" - - "github.com/prebid/prebid-server/metrics" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adform" - "github.com/prebid/prebid-server/adapters/appnexus" - "github.com/prebid/prebid-server/adapters/conversant" - "github.com/prebid/prebid-server/adapters/ix" - "github.com/prebid/prebid-server/adapters/pubmatic" - "github.com/prebid/prebid-server/adapters/pulsepoint" - "github.com/prebid/prebid-server/adapters/rubicon" - "github.com/prebid/prebid-server/adapters/sovrn" analyticsConf "github.com/prebid/prebid-server/analytics/config" - "github.com/prebid/prebid-server/cache" - "github.com/prebid/prebid-server/cache/dummycache" - "github.com/prebid/prebid-server/cache/filecache" - "github.com/prebid/prebid-server/cache/postgrescache" "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/endpoints" + "github.com/prebid/prebid-server/endpoints/events" infoEndpoints "github.com/prebid/prebid-server/endpoints/info" "github.com/prebid/prebid-server/endpoints/openrtb2" + "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/exchange" "github.com/prebid/prebid-server/gdpr" + "github.com/prebid/prebid-server/metrics" metricsConf "github.com/prebid/prebid-server/metrics/config" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/pbs" @@ -49,6 +31,8 @@ import ( storedRequestsConf "github.com/prebid/prebid-server/stored_requests/config" "github.com/prebid/prebid-server/usersync" "github.com/prebid/prebid-server/util/sliceutil" + "github.com/prebid/prebid-server/util/uuidutil" + "github.com/prebid/prebid-server/version" "github.com/golang/glog" "github.com/julienschmidt/httprouter" @@ -56,9 +40,6 @@ import ( "github.com/rs/cors" ) -var dataCache cache.Cache -var exchanges map[string]adapters.Adapter - // NewJsonDirectoryServer is used to serve .json files from a directory as a single blob. For example, // given a directory containing the files "a.json" and "b.json", this returns a Handle which serves JSON like: // @@ -123,51 +104,6 @@ func (m NoCache) ServeHTTP(w http.ResponseWriter, r *http.Request) { m.Handler.ServeHTTP(w, r) } -func loadDataCache(cfg *config.Configuration, db *sql.DB) (err error) { - switch cfg.DataCache.Type { - case "dummy": - dataCache, err = dummycache.New() - if err != nil { - glog.Fatalf("Dummy cache not configured: %s", err.Error()) - } - - case "postgres": - if db == nil { - return fmt.Errorf("Nil db cannot connect to postgres. Did you forget to set the config.stored_requests.postgres values?") - } - dataCache = postgrescache.New(db, postgrescache.CacheConfig{ - Size: cfg.DataCache.CacheSize, - TTL: cfg.DataCache.TTLSeconds, - }) - return nil - case "filecache": - dataCache, err = filecache.New(cfg.DataCache.Filename) - if err != nil { - return fmt.Errorf("FileCache Error: %s", err.Error()) - } - - default: - return fmt.Errorf("Unknown datacache.type: %s", cfg.DataCache.Type) - } - return nil -} - -func newExchangeMap(cfg *config.Configuration) map[string]adapters.Adapter { - // These keys _must_ coincide with the bidder code in Prebid.js, if the adapter exists in both projects - return map[string]adapters.Adapter{ - "appnexus": appnexus.NewAppNexusLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderAppnexus)].Endpoint, cfg.Adapters[string(openrtb_ext.BidderAppnexus)].PlatformID), - "districtm": appnexus.NewAppNexusLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderAppnexus)].Endpoint, cfg.Adapters[string(openrtb_ext.BidderAppnexus)].PlatformID), - "ix": ix.NewIxLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderIx))].Endpoint), - "pubmatic": pubmatic.NewPubmaticLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderPubmatic)].Endpoint), - "pulsepoint": pulsepoint.NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderPulsepoint)].Endpoint), - "rubicon": rubicon.NewRubiconLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderRubicon)].Endpoint, - cfg.Adapters[string(openrtb_ext.BidderRubicon)].XAPI.Username, cfg.Adapters[string(openrtb_ext.BidderRubicon)].XAPI.Password, cfg.Adapters[string(openrtb_ext.BidderRubicon)].XAPI.Tracker), - "conversant": conversant.NewConversantLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderConversant)].Endpoint), - "adform": adform.NewAdformLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderAdform)].Endpoint), - "sovrn": sovrn.NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderSovrn)].Endpoint), - } -} - type Router struct { *httprouter.Router MetricsEngine *metricsConf.DetailedMetricsEngine @@ -211,10 +147,6 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R }, } - // Hack because of how legacy handles districtm - legacyBidderList := openrtb_ext.CoreBidderNames() - legacyBidderList = append(legacyBidderList, openrtb_ext.BidderName("districtm")) - p, _ := filepath.Abs(infoDirectory) bidderInfos, err := config.LoadBidderInfoFromDisk(p, cfg.Adapters, openrtb_ext.BuildBidderStringSlice()) if err != nil { @@ -244,13 +176,10 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R } // Metrics engine - r.MetricsEngine = metricsConf.NewMetricsEngine(cfg, legacyBidderList, syncerKeys) - db, shutdown, fetcher, ampFetcher, accounts, categoriesFetcher, videoFetcher := storedRequestsConf.NewStoredRequests(cfg, r.MetricsEngine, generalHttpClient, r.Router) + r.MetricsEngine = metricsConf.NewMetricsEngine(cfg, openrtb_ext.CoreBidderNames(), syncerKeys) + shutdown, fetcher, ampFetcher, accounts, categoriesFetcher, videoFetcher := storedRequestsConf.NewStoredRequests(cfg, r.MetricsEngine, generalHttpClient, r.Router) // todo(zachbadgett): better shutdown r.Shutdown = shutdown - if err := loadDataCache(cfg, db); err != nil { - return nil, fmt.Errorf("Prebid Server could not load data cache: %v", err) - } pbsAnalytics := analyticsConf.NewPBSAnalytics(&cfg.Analytics) @@ -270,7 +199,6 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R gvlVendorIDs := bidderInfos.ToGVLVendorIDMap() gdprPerms := gdpr.NewPermissions(context.Background(), cfg.GDPR, gvlVendorIDs, generalHttpClient) - exchanges = newExchangeMap(cfg) cacheClient := pbc.NewClient(cacheHttpClient, &cfg.CacheURL, &cfg.ExtCacheURL, r.MetricsEngine) adapters, adaptersErrs := exchange.BuildAdapters(generalHttpClient, cfg, bidderInfos, r.MetricsEngine) @@ -301,10 +229,6 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R videoEndpoint = aspects.QueuedRequestTimeout(videoEndpoint, cfg.RequestTimeoutHeaders, r.MetricsEngine, metrics.ReqTypeVideo) } - if cfg.EnableLegacyAuction { - r.POST("/auction", endpoints.Auction(cfg, syncersByBidder, gdprPerms, r.MetricsEngine, dataCache, exchanges)) - } - r.POST("/openrtb2/auction", openrtbEndpoint) r.POST("/openrtb2/video", videoEndpoint) r.GET("/openrtb2/amp", ampEndpoint) @@ -331,8 +255,6 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R HostCookieConfig: &(cfg.HostCookie), ExternalUrl: cfg.ExternalURL, RecaptchaSecret: cfg.RecaptchaSecret, - MetricsEngine: r.MetricsEngine, - PBSAnalytics: pbsAnalytics, } r.GET("/setuid", endpoints.NewSetUIDEndpoint(cfg.HostCookie, syncersByBidder, gdprPerms, pbsAnalytics, r.MetricsEngine)) diff --git a/router/router_test.go b/router/router_test.go index 24a7709c365..b4ceaff16a9 100644 --- a/router/router_test.go +++ b/router/router_test.go @@ -5,7 +5,6 @@ import ( "io/ioutil" "net/http" "net/http/httptest" - "os" "testing" "github.com/prebid/prebid-server/config" @@ -62,18 +61,6 @@ func TestNewJsonDirectoryServer(t *testing.T) { ensureHasKey(t, data, "aliastest") } -func TestExchangeMap(t *testing.T) { - exchanges := newExchangeMap(&config.Configuration{}) - bidderMap := openrtb_ext.BuildBidderMap() - for bidderName := range exchanges { - // OpenRTB doesn't support hardcoded aliases... so this test skips districtm, - // which was the only alias in the legacy adapter map. - if _, ok := bidderMap[bidderName]; bidderName != "districtm" && !ok { - t.Errorf("Bidder %s exists in exchange, but is not a part of the BidderMap.", bidderName) - } - } -} - func TestApplyBidderInfoConfigOverrides(t *testing.T) { var testCases = []struct { description string @@ -298,38 +285,6 @@ func TestNoCache(t *testing.T) { } } -func TestLoadDataCache(t *testing.T) { - // Test dummy - if err := loadDataCache(&config.Configuration{ - DataCache: config.DataCache{ - Type: "dummy", - }, - }, nil); err != nil { - t.Errorf("data cache: dummy: %s", err) - } - // Test postgres error - if err := loadDataCache(&config.Configuration{ - DataCache: config.DataCache{ - Type: "postgres", - }, - }, nil); err == nil { - t.Errorf("data cache: postgres: db nil should return error") - } - // Test file - d, _ := ioutil.TempDir("", "pbs-filecache") - defer os.RemoveAll(d) - f, _ := ioutil.TempFile(d, "file") - defer f.Close() - if err := loadDataCache(&config.Configuration{ - DataCache: config.DataCache{ - Type: "filecache", - Filename: f.Name(), - }, - }, nil); err != nil { - t.Errorf("data cache: filecache: %s", err) - } -} - var testDefReqConfig = config.DefReqConfig{ Type: "file", FileSystem: config.DefReqFiles{ diff --git a/server/prometheus.go b/server/prometheus.go index 4b9f7037d0a..33114c86a0b 100644 --- a/server/prometheus.go +++ b/server/prometheus.go @@ -9,20 +9,17 @@ import ( "github.com/prebid/prebid-server/config" metricsconfig "github.com/prebid/prebid-server/metrics/config" - prometheusMetrics "github.com/prebid/prebid-server/metrics/prometheus" ) func newPrometheusServer(cfg *config.Configuration, metrics *metricsconfig.DetailedMetricsEngine) *http.Server { - var proMetrics *prometheusMetrics.Metrics - - proMetrics = metrics.PrometheusMetrics + proMetrics := metrics.PrometheusMetrics if proMetrics == nil { glog.Fatal("Prometheus metrics configured, but a Prometheus metrics engine was not found. Cannot set up a Prometheus listener.") } return &http.Server{ Addr: cfg.Host + ":" + strconv.Itoa(cfg.Metrics.Prometheus.Port), - Handler: promhttp.HandlerFor(proMetrics.Registry, promhttp.HandlerOpts{ + Handler: promhttp.HandlerFor(proMetrics.Gatherer, promhttp.HandlerOpts{ ErrorLog: loggerForPrometheus{}, MaxRequestsInFlight: 5, Timeout: cfg.Metrics.Prometheus.Timeout(), diff --git a/static/bidder-info/33across.yaml b/static/bidder-info/33across.yaml index 1ee5366f969..bdda5a7e5a6 100644 --- a/static/bidder-info/33across.yaml +++ b/static/bidder-info/33across.yaml @@ -8,5 +8,5 @@ capabilities: - video userSync: iframe: - url: "https://ic.tynt.com/r/d?m=xch&rt=html&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&ru={{.RedirectURL}}&id=zzz000000000002zzz" + url: "https://ssc-cms.33across.com/ps/?m=xch&rt=html&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&ru={{.RedirectURL}}&id=zzz000000000002zzz" userMacro: "33XUSERID33X" \ No newline at end of file diff --git a/static/bidder-info/adform.yaml b/static/bidder-info/adform.yaml index c8c1d91565a..2e312bff788 100644 --- a/static/bidder-info/adform.yaml +++ b/static/bidder-info/adform.yaml @@ -5,10 +5,12 @@ capabilities: app: mediaTypes: - banner + - native - video site: mediaTypes: - banner + - native - video userSync: redirect: diff --git a/static/bidder-info/adnuntius.yaml b/static/bidder-info/adnuntius.yaml new file mode 100644 index 00000000000..86a0192cd62 --- /dev/null +++ b/static/bidder-info/adnuntius.yaml @@ -0,0 +1,10 @@ +maintainer: + email: hello@adnuntius.com +gvlVendorID: 855 +capabilities: + app: + mediaTypes: + - banner + site: + mediaTypes: + - banner diff --git a/static/bidder-info/adot.yaml b/static/bidder-info/adot.yaml index 6a7aca06881..cd2cd3c4f73 100644 --- a/static/bidder-info/adot.yaml +++ b/static/bidder-info/adot.yaml @@ -1,5 +1,6 @@ maintainer: email: "admin@we-are-adot.com" +gvlVendorID: 272 modifyingVastXmlAllowed: false capabilities: app: diff --git a/static/bidder-info/adyoulike.yaml b/static/bidder-info/adyoulike.yaml index a8769ddd854..0853cf1061b 100644 --- a/static/bidder-info/adyoulike.yaml +++ b/static/bidder-info/adyoulike.yaml @@ -9,6 +9,10 @@ capabilities: - video - native userSync: + default: iframe + iframe: + url: "http://visitor.omnitagjs.com/visitor/isync?uid=19340f4f097d16f41f34fc0274981ca4&name=PrebidServer&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url={{.RedirectURL}}" + userMacro: "[BUYER_USERID]" redirect: url: "http://visitor.omnitagjs.com/visitor/bsync?uid=19340f4f097d16f41f34fc0274981ca4&name=PrebidServer&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url={{.RedirectURL}}" userMacro: "[BUYER_USERID]" diff --git a/static/bidder-info/coinzilla.yaml b/static/bidder-info/coinzilla.yaml new file mode 100644 index 00000000000..dc63d32cd23 --- /dev/null +++ b/static/bidder-info/coinzilla.yaml @@ -0,0 +1,10 @@ +maintainer: + email: "technical@sevio.com" +capabilities: + app: + mediaTypes: + - banner + site: + mediaTypes: + - banner + diff --git a/static/bidder-info/groupm.yaml b/static/bidder-info/groupm.yaml new file mode 100644 index 00000000000..9e487ad50a8 --- /dev/null +++ b/static/bidder-info/groupm.yaml @@ -0,0 +1,18 @@ +maintainer: + email: "header-bidding@pubmatic.com" +gvlVendorID: 98 +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video +userSync: + iframe: + url: "https://ads.pubmatic.com/AdServer/js/user_sync.html?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&predirect={{.RedirectURL}}" + userMacro: "" + # groupm appends the user id to end of the redirect url and does not utilize a macro + diff --git a/static/bidder-info/impactify.yaml b/static/bidder-info/impactify.yaml index b0316d7acae..c0ca61ae4b6 100644 --- a/static/bidder-info/impactify.yaml +++ b/static/bidder-info/impactify.yaml @@ -1,5 +1,5 @@ maintainer: - email: "contact@impactify.io" + email: "support@impactify.io" gvlVendorID: 606 capabilities: site: diff --git a/static/bidder-info/openx.yaml b/static/bidder-info/openx.yaml index c48d7de2933..4b456014ab0 100644 --- a/static/bidder-info/openx.yaml +++ b/static/bidder-info/openx.yaml @@ -12,6 +12,10 @@ capabilities: - video modifyingVastXmlAllowed: true userSync: + default: "iframe" + iframe: + url: "https://u.openx.net/w/1.0/cm?id=891039ac-a916-42bb-a651-4be9e3b201da&ph=a3aece0c-9e80-4316-8deb-faf804779bd1&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r={{.RedirectURL}}" + userMacro: "{OPENX_ID}" redirect: url: "https://rtb.openx.net/sync/prebid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r={{.RedirectURL}}" userMacro: "${UID}" diff --git a/static/bidder-info/richaudience.yaml b/static/bidder-info/richaudience.yaml new file mode 100644 index 00000000000..6b51aaa86e3 --- /dev/null +++ b/static/bidder-info/richaudience.yaml @@ -0,0 +1,15 @@ +maintainer: + email: partnerintegrations@richaudience.com +gvlVendorID: 108 +modifyingVastXmlAllowed: true +capabilities: + site: + mediaTypes: + - banner + app: + mediaTypes: + - banner +userSync: + iframe: + url: "https://sync.richaudience.com/74889303289e27f327ad0c6de7be7264/?consentString={{.GDPRConsent}}&r={{.RedirectURL}}" + userMacro: "[PDID]" diff --git a/static/bidder-info/smaato.yaml b/static/bidder-info/smaato.yaml index b73edb23d18..a070db1d7d3 100644 --- a/static/bidder-info/smaato.yaml +++ b/static/bidder-info/smaato.yaml @@ -10,3 +10,7 @@ capabilities: mediaTypes: - banner - video +userSync: + redirect: + url: "https://s.ad.smaato.net/c/?adExInit=p&redir={{.RedirectURL}}" + userMacro: "$UID" diff --git a/static/bidder-info/streamkey.yaml b/static/bidder-info/streamkey.yaml new file mode 100644 index 00000000000..a13b71e588a --- /dev/null +++ b/static/bidder-info/streamkey.yaml @@ -0,0 +1,14 @@ +maintainer: + email: "contact@streamkey.tv" +capabilities: + app: + mediaTypes: + - video + site: + mediaTypes: + - video +userSync: + # streamkey supports user syncing, but requires configuration by the host. contact this + # bidder directly at the email address in this file to ask about enabling user sync. + supports: + - iframe diff --git a/static/bidder-info/triplelift.yaml b/static/bidder-info/triplelift.yaml index 99026b6f171..84eea8690f1 100644 --- a/static/bidder-info/triplelift.yaml +++ b/static/bidder-info/triplelift.yaml @@ -11,6 +11,13 @@ capabilities: - banner - video userSync: + # Triplelift supports user syncing but requires configuration by the host as the RedirectURL domain must be allowlisted. + # Contact this bidder directly at the email address above to ask about enabling user sync. + # + default: iframe + iframe: + url: "https://eb2.3lift.com/sync?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}" + userMacro: $UID redirect: url: "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}" - userMacro: "$UID" + userMacro: "$UID" \ No newline at end of file diff --git a/static/bidder-info/triplelift_native.yaml b/static/bidder-info/triplelift_native.yaml index 32eb07efa14..dc60d572d05 100644 --- a/static/bidder-info/triplelift_native.yaml +++ b/static/bidder-info/triplelift_native.yaml @@ -9,6 +9,13 @@ capabilities: mediaTypes: - native userSync: + # Triplelift supports user syncing but requires configuration by the host as the RedirectURL domain must be allowlisted. + # Contact this bidder directly at the email address above to ask about enabling user sync. + # + default: iframe + iframe: + url: "https://eb2.3lift.com/sync?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}" + userMacro: $UID redirect: url: "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}" userMacro: "$UID" \ No newline at end of file diff --git a/static/bidder-info/unruly.yaml b/static/bidder-info/unruly.yaml index 7e018a0a4b1..9b1ba9c6f54 100644 --- a/static/bidder-info/unruly.yaml +++ b/static/bidder-info/unruly.yaml @@ -11,6 +11,6 @@ capabilities: - banner - video userSync: - iframe: - url: "https://usermatch.targeting.unrulymedia.com/pbsync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&rurl={{.RedirectURL}}" - userMacro: "$UID" + redirect: + url: "https://sync.1rx.io/usersync2/rmphb?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}" + userMacro: "[RX_UUID]" diff --git a/static/bidder-info/videobyte.yaml b/static/bidder-info/videobyte.yaml new file mode 100644 index 00000000000..fc0e767e1af --- /dev/null +++ b/static/bidder-info/videobyte.yaml @@ -0,0 +1,16 @@ +maintainer: + email: "prebid@videobyte.com" +gvlVendorID: 1011 +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video +userSync: + redirect: + url: "https://x.videobyte.com/usync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb={{.RedirectURL}}" + userMacro: "$UID" diff --git a/static/bidder-params/adf.json b/static/bidder-params/adf.json index 4a1a11827ef..e165fa85b7e 100644 --- a/static/bidder-params/adf.json +++ b/static/bidder-params/adf.json @@ -16,6 +16,11 @@ "mname": { "type": ["string"], "description": "A Name which identifies the placement selling the impression" + }, + "priceType": { + "type": ["string"], + "description": "gross or net. Default is net.", + "pattern": "gross|net" } }, "anyOf":[ diff --git a/static/bidder-params/adform.json b/static/bidder-params/adform.json index 6c8a67732d5..e112f122e49 100644 --- a/static/bidder-params/adform.json +++ b/static/bidder-params/adform.json @@ -1,42 +1,33 @@ { "$schema": "http://json-schema.org/draft-04/schema#", "title": "Adform Adapter Params", - "description": "A schema which validates params accepted by the Adform adapter", + "description": "A schema which validates params accepted by the adf adapter", "type": "object", "properties": { "mid": { "type": ["integer", "string"], + "pattern": "^\\d+$", "description": "An ID which identifies the placement selling the impression" }, - "priceType": { - "type": "string", - "enum": ["gross", "net"], - "description": "An expected price type (net or gross) of bids." - }, - "mkv": { - "type": "string", - "description": "Comma-separated key-value pairs. Forbidden symbols: &. Example: mkv='color:blue,length:350'", - "pattern": "^(\\s*|((\\s*[^,:&\\s]+\\s*:[^,:&]*)(,\\s*[^,:&\\s]+\\s*:[^,:&]*)*))$" - }, - "mkw": { - "type": "string", - "description": "Comma-separated keywords. Forbidden symbols: &.", - "pattern": "^[^&]*$" + "inv": { + "type": ["integer"], + "description": "An ID which identifies the Adform inventory source id" }, - "cdims": { - "type": "string", - "description": "Comma-separated creative dimensions.", - "pattern": "(^\\d+x\\d+)(,\\d+x\\d+)*$" + "mname": { + "type": ["string"], + "description": "A Name which identifies the placement selling the impression" }, - "minp": { - "type": "number", - "description": "The minimum CPM price.", - "minimum": 0 - }, - "url": { - "type": "string", - "description": "Custom URL for targeting." + "priceType": { + "type": ["string"], + "description": "gross or net. Default is net.", + "pattern": "gross|net" } }, - "required": ["mid"] + "anyOf":[ + { + "required": ["mid"] + }, { + "required": ["inv", "mname"] + } + ] } diff --git a/static/bidder-params/adnuntius.json b/static/bidder-params/adnuntius.json new file mode 100644 index 00000000000..a8e65bea343 --- /dev/null +++ b/static/bidder-params/adnuntius.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Adnuntius Adapter Params", + "description": "A schema which validates params accepted by the Adnuntius adapter", + "type": "object", + + "properties": { + "auId": { + "type": "string", + "description": "Placement ID" + }, + "network": { + "type": "string", + "description": "Network if required" + } + }, + + "required": ["auId"] +} diff --git a/static/bidder-params/beachfront.json b/static/bidder-params/beachfront.json index 5b1e71a9ba8..fd3a4b674d0 100644 --- a/static/bidder-params/beachfront.json +++ b/static/bidder-params/beachfront.json @@ -30,7 +30,7 @@ }, "bidfloor": { "type": "number", - "description": "The price floor for the bid. Will override the bidfloor set for the impression." + "description": "The price floor for the bid. Will override the bidfloor set for the impression. Always in USD, regardless of the value of the currency set for the impression." }, "videoResponseType": { "type": "string", diff --git a/static/bidder-params/coinzilla.json b/static/bidder-params/coinzilla.json new file mode 100644 index 00000000000..8268999fbfa --- /dev/null +++ b/static/bidder-params/coinzilla.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Coinzilla Adapter Params", + "description": "A schema which validates params accepted by the Coinzilla adapter", + "type": "object", + "properties": { + "placementId": { + "type": "string", + "minLength": 1, + "description": "Placement Id or Website Id" + } + }, + "required": ["placementId"] +} diff --git a/static/bidder-params/groupm.json b/static/bidder-params/groupm.json new file mode 100644 index 00000000000..cfebd2adb19 --- /dev/null +++ b/static/bidder-params/groupm.json @@ -0,0 +1,61 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Groupm Adapter Params", + "description": "A schema which validates params accepted by the Groupm adapter", + "type": "object", + "properties": { + "publisherId": { + "type": "string", + "description": "An ID which identifies the publisher" + }, + "adSlot": { + "type": "string", + "description": "An ID which identifies the ad slot" + }, + "pmzoneid": { + "type": "string", + "description": "Comma separated zone id. Used im deal targeting & site section targeting. e.g drama,sport" + }, + "dctr": { + "type": "string", + "description": "Deals Custom Targeting, pipe separated key-value pairs e.g key1=V1,V2,V3|key2=v1|key3=v3,v5" + }, + "wrapper": { + "type": "object", + "description": "Specifies Groupm openwrap configuration for a publisher", + "properties": { + "profile": { + "type": "integer", + "description": "An ID which identifies the openwrap profile of publisher" + }, + "version": { + "type": "integer", + "description": "An ID which identifies version of the openwrap profile" + } + }, + "required": ["profile"] + }, + "keywords": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "description": "A key with one or more values associated with it. These are used in buy-side segment targeting.", + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + }, + "required": ["key", "value"] + } + } + }, + "required": ["publisherId"] +} diff --git a/static/bidder-params/richaudience.json b/static/bidder-params/richaudience.json new file mode 100644 index 00000000000..e10dc59bb30 --- /dev/null +++ b/static/bidder-params/richaudience.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "richaudience Adapter Params", + "description": "A schema which validates params accepted by the richaudience adapter", + "type": "object", + + "properties": { + "pid": { + "type": "string", + "description": "Placement ID" + }, + "supplyType": { + "type": "string", + "description": "Type integration" + }, + "test": { + "type": "boolean", + "description": "TestMode" + }, + "bidFloor": { + "type": "number", + "description": "floor price" + }, + "bidFloorCur": { + "type": "string", + "description": "currency floor price" + } + }, + + "required": ["pid", "supplyType"] +} diff --git a/static/bidder-params/streamkey.json b/static/bidder-params/streamkey.json new file mode 100644 index 00000000000..ec8e5b1b643 --- /dev/null +++ b/static/bidder-params/streamkey.json @@ -0,0 +1,26 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Streamkey Adapter Params", + "description": "A schema which validates params accepted by the Streamkey adapter", + + "type": "object", + "properties": { + "placementId": { + "type": "integer", + "description": "An ID which identifies this placement of the impression" + }, + "siteId": { + "type": "integer", + "description": "An ID which identifies the site selling the impression" + }, + "aid": { + "type": "integer", + "description": "An ID which identifies the channel" + }, + "bidFloor": { + "type": "number", + "description": "BidFloor, US Dollars" + } + }, + "required": ["aid"] +} diff --git a/static/bidder-params/videobyte.json b/static/bidder-params/videobyte.json new file mode 100644 index 00000000000..a9388e26684 --- /dev/null +++ b/static/bidder-params/videobyte.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "VideoByte Adapter Params", + "description": "A schema which validates params accepted by the VideoByte adapter", + + "type": "object", + "properties": { + "pubId": { + "type": "string", + "description": "Publisher ID" + }, + "placementId": { + "type": "string", + "description": "Placement ID" + }, + "nid": { + "type": "string", + "description": "Network ID" + } + }, + "required": ["pubId"] +} diff --git a/stored_requests/backends/file_fetcher/fetcher_test.go b/stored_requests/backends/file_fetcher/fetcher_test.go index a145a3b43a2..7d23f942d56 100644 --- a/stored_requests/backends/file_fetcher/fetcher_test.go +++ b/stored_requests/backends/file_fetcher/fetcher_test.go @@ -32,7 +32,7 @@ func TestAccountFetcher(t *testing.T) { assertErrorCount(t, 0, errs) assert.JSONEq(t, `{"disabled":false, "id":"valid"}`, string(account)) - account, errs = fetcher.FetchAccount(context.Background(), "nonexistent") + _, errs = fetcher.FetchAccount(context.Background(), "nonexistent") assertErrorCount(t, 1, errs) assert.Error(t, errs[0]) assert.Equal(t, stored_requests.NotFoundError{"nonexistent", "Account"}, errs[0]) diff --git a/stored_requests/backends/http_fetcher/fetcher.go b/stored_requests/backends/http_fetcher/fetcher.go index bc12caecb98..75a92e3f331 100644 --- a/stored_requests/backends/http_fetcher/fetcher.go +++ b/stored_requests/backends/http_fetcher/fetcher.go @@ -74,7 +74,6 @@ func NewFetcher(client *http.Client, endpoint string) *HttpFetcher { type HttpFetcher struct { client *http.Client Endpoint string - hasQuery bool Categories map[string]map[string]stored_requests.Category } diff --git a/stored_requests/backends/http_fetcher/fetcher_test.go b/stored_requests/backends/http_fetcher/fetcher_test.go index 30933181e1d..10d3984a818 100644 --- a/stored_requests/backends/http_fetcher/fetcher_test.go +++ b/stored_requests/backends/http_fetcher/fetcher_test.go @@ -1,10 +1,8 @@ package http_fetcher import ( - "bytes" "context" "encoding/json" - "io" "net/http" "net/http/httptest" "strings" @@ -168,42 +166,6 @@ func TestErrResponse(t *testing.T) { assert.Len(t, errs, 1) } -func assertSameContents(t *testing.T, expected map[string]json.RawMessage, actual map[string]json.RawMessage) { - if len(expected) != len(actual) { - t.Errorf("Wrong counts. Expected %d, actual %d", len(expected), len(actual)) - return - } - for expectedKey, expectedVal := range expected { - if actualVal, ok := actual[expectedKey]; ok { - if !bytes.Equal(expectedVal, actualVal) { - t.Errorf("actual[%s] value %s does not match expected: %s", expectedKey, string(actualVal), string(actualVal)) - } - } else { - t.Errorf("actual map missing expected key %s", expectedKey) - } - } -} - -func assertSameErrMsgs(t *testing.T, expected []string, actual []error) { - if len(expected) != len(actual) { - t.Errorf("Wrong error counts. Expected %d, actual %d", len(expected), len(actual)) - return - } - for i, expectedErr := range expected { - if actual[i].Error() != expectedErr { - t.Errorf("Wrong error[%d]. Expected %s, got %s", i, expectedErr, actual[i].Error()) - } - } -} - -type closeWrapper struct { - io.Reader -} - -func (w closeWrapper) Close() error { - return nil -} - func newFetcherBrokenBackend() (fetcher *HttpFetcher, closer func()) { handler := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) diff --git a/stored_requests/caches/nil_cache/nil_cache.go b/stored_requests/caches/nil_cache/nil_cache.go index d043ae55c96..88bbd404674 100644 --- a/stored_requests/caches/nil_cache/nil_cache.go +++ b/stored_requests/caches/nil_cache/nil_cache.go @@ -13,9 +13,7 @@ func (c *NilCache) Get(ctx context.Context, ids []string) map[string]json.RawMes } func (c *NilCache) Save(ctx context.Context, data map[string]json.RawMessage) { - return } func (c *NilCache) Invalidate(ctx context.Context, ids []string) { - return } diff --git a/stored_requests/config/config.go b/stored_requests/config/config.go index f682ff932f4..89022582ace 100644 --- a/stored_requests/config/config.go +++ b/stored_requests/config/config.go @@ -93,12 +93,12 @@ func CreateStoredRequests(cfg *config.StoredRequests, metricsEngine metrics.Metr return } -// NewStoredRequests returns five things: +// NewStoredRequests returns: // -// 1. A DB connection, if one was created. This may be nil. -// 2. A function which should be called on shutdown for graceful cleanups. -// 3. A Fetcher which can be used to get Stored Requests for /openrtb2/auction -// 4. A Fetcher which can be used to get Stored Requests for /openrtb2/amp +// 1. A function which should be called on shutdown for graceful cleanups. +// 2. A Fetcher which can be used to get Stored Requests for /openrtb2/auction +// 3. A Fetcher which can be used to get Stored Requests for /openrtb2/amp +// 4. A Fetcher which can be used to get Account data // 5. A Fetcher which can be used to get Category Mapping data // 6. A Fetcher which can be used to get Stored Requests for /openrtb2/video // @@ -107,7 +107,7 @@ func CreateStoredRequests(cfg *config.StoredRequests, metricsEngine metrics.Metr // // As a side-effect, it will add some endpoints to the router if the config calls for it. // In the future we should look for ways to simplify this so that it's not doing two things. -func NewStoredRequests(cfg *config.Configuration, metricsEngine metrics.MetricsEngine, client *http.Client, router *httprouter.Router) (db *sql.DB, shutdown func(), fetcher stored_requests.Fetcher, ampFetcher stored_requests.Fetcher, accountsFetcher stored_requests.AccountFetcher, categoriesFetcher stored_requests.CategoryFetcher, videoFetcher stored_requests.Fetcher) { +func NewStoredRequests(cfg *config.Configuration, metricsEngine metrics.MetricsEngine, client *http.Client, router *httprouter.Router) (shutdown func(), fetcher stored_requests.Fetcher, ampFetcher stored_requests.Fetcher, accountsFetcher stored_requests.AccountFetcher, categoriesFetcher stored_requests.CategoryFetcher, videoFetcher stored_requests.Fetcher) { // TODO: Switch this to be set in config defaults //if cfg.CategoryMapping.CacheEvents.Enabled && cfg.CategoryMapping.CacheEvents.Endpoint == "" { // cfg.CategoryMapping.CacheEvents.Endpoint = "/storedrequest/categorymapping" @@ -121,8 +121,6 @@ func NewStoredRequests(cfg *config.Configuration, metricsEngine metrics.MetricsE fetcher4, shutdown4 := CreateStoredRequests(&cfg.StoredVideo, metricsEngine, client, router, &dbc) fetcher5, shutdown5 := CreateStoredRequests(&cfg.Accounts, metricsEngine, client, router, &dbc) - db = dbc.db - fetcher = fetcher1.(stored_requests.Fetcher) ampFetcher = fetcher2.(stored_requests.Fetcher) categoriesFetcher = fetcher3.(stored_requests.CategoryFetcher) diff --git a/stored_requests/events/events.go b/stored_requests/events/events.go index 5b89943572f..7cb8f4b9b6d 100644 --- a/stored_requests/events/events.go +++ b/stored_requests/events/events.go @@ -77,7 +77,7 @@ func (e *EventListener) Listen(cache stored_requests.Cache, events EventProducer e.onInvalidate() } case <-e.stop: - break + return } } } diff --git a/usersync/cookie_test.go b/usersync/cookie_test.go index 4792db1969f..4e87db5dd0a 100644 --- a/usersync/cookie_test.go +++ b/usersync/cookie_test.go @@ -33,7 +33,7 @@ func TestEmptyOptOutCookie(t *testing.T) { func TestEmptyCookie(t *testing.T) { cookie := &Cookie{ - uids: make(map[string]uidWithExpiry, 0), + uids: make(map[string]uidWithExpiry), optOut: false, birthday: timestamp(), } diff --git a/usersync/syncer.go b/usersync/syncer.go index 2db220a2340..c944cf0b488 100644 --- a/usersync/syncer.go +++ b/usersync/syncer.go @@ -86,7 +86,7 @@ func NewSyncer(hostConfig config.UserSync, syncerConfig config.Syncer) (Syncer, if syncerConfig.IFrame != nil { var err error - syncer.iframe, err = buildTemplate(syncerConfig.Key, setuidSyncTypeIFrame, hostConfig, *syncerConfig.IFrame) + syncer.iframe, err = buildTemplate(syncerConfig.Key, setuidSyncTypeIFrame, hostConfig, syncerConfig.ExternalURL, *syncerConfig.IFrame) if err != nil { return nil, fmt.Errorf("iframe %v", err) } @@ -97,7 +97,7 @@ func NewSyncer(hostConfig config.UserSync, syncerConfig config.Syncer) (Syncer, if syncerConfig.Redirect != nil { var err error - syncer.redirect, err = buildTemplate(syncerConfig.Key, setuidSyncTypeRedirect, hostConfig, *syncerConfig.Redirect) + syncer.redirect, err = buildTemplate(syncerConfig.Key, setuidSyncTypeRedirect, hostConfig, syncerConfig.ExternalURL, *syncerConfig.Redirect) if err != nil { return nil, fmt.Errorf("redirect %v", err) } @@ -147,16 +147,13 @@ var ( macroRegex = regexp.MustCompile(`{{\s*\..*?\s*}}`) ) -func buildTemplate(key, syncTypeValue string, hostConfig config.UserSync, syncerEndpoint config.SyncerEndpoint) (*template.Template, error) { +func buildTemplate(key, syncTypeValue string, hostConfig config.UserSync, syncerExternalURL string, syncerEndpoint config.SyncerEndpoint) (*template.Template, error) { redirectTemplate := syncerEndpoint.RedirectURL if redirectTemplate == "" { redirectTemplate = hostConfig.RedirectURL } - externalURL := syncerEndpoint.ExternalURL - if externalURL == "" { - externalURL = hostConfig.ExternalURL - } + externalURL := chooseExternalURL(syncerEndpoint.ExternalURL, syncerExternalURL, hostConfig.ExternalURL) redirectURL := macroRegexSyncerKey.ReplaceAllLiteralString(redirectTemplate, key) redirectURL = macroRegexSyncType.ReplaceAllLiteralString(redirectURL, syncTypeValue) @@ -170,6 +167,19 @@ func buildTemplate(key, syncTypeValue string, hostConfig config.UserSync, syncer return template.New(templateName).Parse(url) } +// chooseExternalURL selects the external url to use for the template, where the most specific config wins. +func chooseExternalURL(syncerEndpointURL, syncerURL, hostConfigURL string) string { + if syncerEndpointURL != "" { + return syncerEndpointURL + } + + if syncerURL != "" { + return syncerURL + } + + return hostConfigURL +} + // escapeTemplate url encodes a string template leaving the macro tags unaffected. func escapeTemplate(x string) string { escaped := strings.Builder{} diff --git a/usersync/syncer_test.go b/usersync/syncer_test.go index 2f4e3203905..6ff230094e4 100644 --- a/usersync/syncer_test.go +++ b/usersync/syncer_test.go @@ -29,6 +29,7 @@ func TestNewSyncer(t *testing.T) { givenDefault string givenIFrameConfig *config.SyncerEndpoint givenRedirectConfig *config.SyncerEndpoint + givenExternalURL string expectedError string expectedDefault SyncType expectedIFrame string @@ -100,6 +101,17 @@ func TestNewSyncer(t *testing.T) { givenRedirectConfig: errInvalidConfig, expectedError: "redirect composed url: \"notAURL:http%3A%2F%2Fhost.com%2Fhost\" is invalid", }, + { + description: "Syncer Level External URL", + givenKey: "a", + givenDefault: "iframe", + givenExternalURL: "http://syncer.com", + givenIFrameConfig: iframeConfig, + givenRedirectConfig: redirectConfig, + expectedDefault: SyncTypeIFrame, + expectedIFrame: "https://bidder.com/iframe?redirect=http%3A%2F%2Fsyncer.com%2Fhost", + expectedRedirect: "https://bidder.com/redirect?redirect=http%3A%2F%2Fsyncer.com%2Fhost", + }, } for _, test := range testCases { @@ -109,6 +121,7 @@ func TestNewSyncer(t *testing.T) { Default: test.givenDefault, IFrame: test.givenIFrameConfig, Redirect: test.givenRedirectConfig, + ExternalURL: test.givenExternalURL, } result, err := NewSyncer(hostConfig, syncerConfig) @@ -232,11 +245,12 @@ func TestBuildTemplate(t *testing.T) { ) testCases := []struct { - description string - givenHostConfig config.UserSync - givenSyncerEndpoint config.SyncerEndpoint - expectedError string - expectedRendered string + description string + givenHostConfig config.UserSync + givenSyncerExternalURL string + givenSyncerEndpoint config.SyncerEndpoint + expectedError string + expectedRendered string }{ { description: "No Composed Macros", @@ -278,6 +292,23 @@ func TestBuildTemplate(t *testing.T) { }, expectedRendered: "https://bidder.com/sync?redirect=http%3A%2F%2Fsyncer.com%2Fhost", }, + { + description: "External URL From Syncer Config", + givenSyncerExternalURL: "http://syncershared.com", + givenSyncerEndpoint: config.SyncerEndpoint{ + URL: "https://bidder.com/sync?redirect={{.RedirectURL}}", + }, + expectedRendered: "https://bidder.com/sync?redirect=http%3A%2F%2Fsyncershared.com%2Fhost", + }, + { + description: "External URL From Syncer Config (Most Specific Wins)", + givenSyncerExternalURL: "http://syncershared.com", + givenSyncerEndpoint: config.SyncerEndpoint{ + URL: "https://bidder.com/sync?redirect={{.RedirectURL}}", + ExternalURL: "http://syncer.com", + }, + expectedRendered: "https://bidder.com/sync?redirect=http%3A%2F%2Fsyncer.com%2Fhost", + }, { description: "Template Parse Error", givenSyncerEndpoint: config.SyncerEndpoint{ @@ -339,7 +370,7 @@ func TestBuildTemplate(t *testing.T) { } for _, test := range testCases { - result, err := buildTemplate(key, syncTypeValue, hostConfig, test.givenSyncerEndpoint) + result, err := buildTemplate(key, syncTypeValue, hostConfig, test.givenSyncerExternalURL, test.givenSyncerEndpoint) if test.expectedError == "" { assert.NoError(t, err, test.description+":err") @@ -353,6 +384,50 @@ func TestBuildTemplate(t *testing.T) { } } +func TestChooseExternalURL(t *testing.T) { + testCases := []struct { + description string + givenSyncerEndpointURL string + givenSyncerURL string + givenHostConfigURL string + expected string + }{ + { + description: "Syncer Endpoint Chosen", + givenSyncerEndpointURL: "a", + givenSyncerURL: "b", + givenHostConfigURL: "c", + expected: "a", + }, + { + description: "Syncer Chosen", + givenSyncerEndpointURL: "", + givenSyncerURL: "b", + givenHostConfigURL: "c", + expected: "b", + }, + { + description: "Host Config Chosen", + givenSyncerEndpointURL: "", + givenSyncerURL: "", + givenHostConfigURL: "c", + expected: "c", + }, + { + description: "All Empty", + givenSyncerEndpointURL: "", + givenSyncerURL: "", + givenHostConfigURL: "", + expected: "", + }, + } + + for _, test := range testCases { + result := chooseExternalURL(test.givenSyncerEndpointURL, test.givenSyncerURL, test.givenHostConfigURL) + assert.Equal(t, test.expected, result, test.description) + } +} + func TestEscapeTemplate(t *testing.T) { testCases := []struct { description string diff --git a/util/jsonutil/jsonutil.go b/util/jsonutil/jsonutil.go index 4fd56745301..d8716297faf 100644 --- a/util/jsonutil/jsonutil.go +++ b/util/jsonutil/jsonutil.go @@ -8,12 +8,18 @@ import ( var comma = []byte(",")[0] var colon = []byte(":")[0] +var sqBracket = []byte("]")[0] +var openCurlyBracket = []byte("{")[0] +var closingCurlyBracket = []byte("}")[0] +var quote = []byte(`"`)[0] -func findElementIndexes(extension []byte, elementName string) (bool, int64, int64, error) { - found := false +//Finds element in json byte array with any level of nesting +func FindElement(extension []byte, elementNames ...string) (bool, int64, int64, error) { + elementName := elementNames[0] buf := bytes.NewBuffer(extension) dec := json.NewDecoder(buf) - var startIndex int64 + found := false + var startIndex, endIndex int64 var i interface{} for { token, err := dec.Token() @@ -24,21 +30,18 @@ func findElementIndexes(extension []byte, elementName string) (bool, int64, int6 if err != nil { return false, -1, -1, err } - if token == elementName { err := dec.Decode(&i) if err != nil { return false, -1, -1, err } - found = true - endIndex := dec.InputOffset() + endIndex = dec.InputOffset() if dec.More() { //if there were other elements before if extension[startIndex] == comma { startIndex++ } - for { //structure has more elements, need to find index of comma if extension[endIndex] == comma { @@ -48,43 +51,62 @@ func findElementIndexes(extension []byte, elementName string) (bool, int64, int6 endIndex++ } } - return found, startIndex, endIndex, nil + found = true + break } else { startIndex = dec.InputOffset() } - } - - return false, -1, -1, nil -} - -func DropElement(extension []byte, elementName string) ([]byte, error) { - found, startIndex, endIndex, err := findElementIndexes(extension, elementName) if found { - extension = append(extension[:startIndex], extension[endIndex:]...) - } - return extension, err -} - -func FindElement(extension []byte, elementName string) (bool, []byte, error) { + if len(elementNames) == 1 { + return found, startIndex, endIndex, nil + } else if len(elementNames) > 1 { + for { + //find the beginning of nested element + if extension[startIndex] == colon { + startIndex++ + break + } + startIndex++ + } + for { + if endIndex == int64(len(extension)) { + endIndex-- + } - found, startIndex, endIndex, err := findElementIndexes(extension, elementName) + //if structure had more elements, need to find index of comma at the end + if extension[endIndex] == sqBracket || extension[endIndex] == closingCurlyBracket { + break + } - if found && err == nil { - element := extension[startIndex:endIndex] - index := 0 - for { - if index < len(element) && element[index] != colon { - index++ - } else { - index++ - break + if extension[endIndex] == comma { + endIndex-- + break + } else { + endIndex-- + } } + if found { + found, startInd, endInd, err := FindElement(extension[startIndex:endIndex], elementNames[1:]...) + return found, startIndex + startInd, startIndex + endInd, err + } + return found, startIndex, startIndex, nil } - element = element[index:] - return found, element, err } + return found, startIndex, endIndex, nil +} - return found, nil, err - +//Drops element from json byte array +// - Doesn't support drop element from json list +// - Keys in the path can skip levels +// - First found element will be removed +func DropElement(extension []byte, elementNames ...string) ([]byte, error) { + found, startIndex, endIndex, err := FindElement(extension, elementNames...) + if err != nil { + return nil, err + } + if found { + extension = append(extension[:startIndex], extension[endIndex:]...) + } + return extension, nil } diff --git a/util/jsonutil/jsonutil_test.go b/util/jsonutil/jsonutil_test.go index d8738e171c7..12b1fd5e803 100644 --- a/util/jsonutil/jsonutil_test.go +++ b/util/jsonutil/jsonutil_test.go @@ -7,300 +7,179 @@ import ( ) func TestDropElement(t *testing.T) { + tests := []struct { description string input []byte - elementToRemove string + elementToRemove []string output []byte errorExpected bool errorContains string }{ { - description: "Drop single element after another element", + description: "Drop Single Element After Another Element", input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"test": 1,"consented_providers": [1608,765,492]}}`), - elementToRemove: "consented_providers", + elementToRemove: []string{"consented_providers"}, output: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"test": 1}}`), errorExpected: false, errorContains: "", }, { - description: "Drop single element before another element", + description: "Drop Single Element Before Another Element", input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1608,765,492],"test": 1}}`), - elementToRemove: "consented_providers", + elementToRemove: []string{"consented_providers"}, output: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"test": 1}}`), errorExpected: false, errorContains: "", }, { - description: "Drop single element", + description: "Drop Single Element", input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1545,2563,1411]}}`), - elementToRemove: "consented_providers", + elementToRemove: []string{"consented_providers"}, output: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {}}`), errorExpected: false, errorContains: "", }, { - description: "Drop single element string", + description: "Drop Single Element string", input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": "test"}}`), - elementToRemove: "consented_providers", + elementToRemove: []string{"consented_providers"}, output: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {}}`), errorExpected: false, errorContains: "", }, { - description: "Drop parent element between two elements", + description: "Drop Parent Element Between Two Elements", input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1},"test": 123}`), - elementToRemove: "consented_providers_settings", + elementToRemove: []string{"consented_providers_settings"}, output: []byte(`{"consent": "TESTCONSENT","test": 123}`), errorExpected: false, errorContains: "", }, { - description: "Drop parent element before element", + description: "Drop Parent Element Before Element", input: []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1},"test": 123}`), - elementToRemove: "consented_providers_settings", + elementToRemove: []string{"consented_providers_settings"}, output: []byte(`{"test": 123}`), errorExpected: false, errorContains: "", }, { - description: "Drop parent element after element", + description: "Drop Parent Element After Element", input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`), - elementToRemove: "consented_providers_settings", + elementToRemove: []string{"consented_providers_settings"}, output: []byte(`{"consent": "TESTCONSENT"}`), errorExpected: false, errorContains: "", }, { - description: "Drop parent element only", + description: "Drop Parent Element Only", input: []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`), - elementToRemove: "consented_providers_settings", + elementToRemove: []string{"consented_providers_settings"}, output: []byte(`{}`), errorExpected: false, errorContains: "", }, { - description: "Drop element that doesn't exist", - input: []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`), - elementToRemove: "test2", - output: []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`), + description: "Drop Parent Element List", + input: []byte(`{"consented_providers_settings":{"consented_providers":[1608,765,492],"test":1},"data": [{"test1":5},{"test2": [1,2,3]}]}`), + elementToRemove: []string{"data"}, + output: []byte(`{"consented_providers_settings":{"consented_providers":[1608,765,492],"test":1}}`), errorExpected: false, errorContains: "", }, - //Errors { - description: "Error decode", - input: []byte(`{"consented_providers_settings": {"consented_providers": ["123",1,,1365,5678,1545,2563,1411], "test": 1}}`), - elementToRemove: "consented_providers", - output: []byte(``), - errorExpected: true, - errorContains: "looking for beginning of value", - }, - { - description: "Error malformed", - input: []byte(`{consented_providers_settings: {"consented_providers": [1365,5678,1545,2563,1411], "test": 1}}`), - elementToRemove: "consented_providers", - output: []byte(``), - errorExpected: true, - errorContains: "invalid character", - }, - } - - for _, tt := range tests { - res, err := DropElement(tt.input, tt.elementToRemove) - - if tt.errorExpected { - assert.Error(t, err, "Error should not be nil") - assert.True(t, strings.Contains(err.Error(), tt.errorContains)) - } else { - assert.NoError(t, err, "Error should be nil") - assert.Equal(t, tt.output, res, "Result is incorrect") - } - - } -} - -func TestFindElement(t *testing.T) { - tests := []struct { - description string - input []byte - elementToFind string - output []byte - elementFound bool - }{ - { - description: "Find array element", - input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"test": 1,"consented_providers":[1608,765,492]}}`), - elementToFind: "consented_providers", - output: []byte(`[1608,765,492]`), - elementFound: true, - }, - { - description: "Find object element", - input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings":{"test": 1,"consented_providers": [1608,765,492]}}`), - elementToFind: "consented_providers_settings", - output: []byte(`{"test": 1,"consented_providers": [1608,765,492]}`), - elementFound: true, - }, - { - description: "Find element that doesn't exist", - input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings":{"test": 1,"consented_providers": [1608,765,492]}}`), - elementToFind: "test_element", - output: []byte(nil), - elementFound: false, - }, - } - - for _, tt := range tests { - exists, res, err := FindElement(tt.input, tt.elementToFind) - assert.NoError(t, err, "Error should be nil") - assert.Equal(t, tt.output, res, "Result is incorrect") - - if tt.elementFound { - assert.True(t, exists, "Element must be found") - } else { - assert.False(t, exists, "Element must not be found") - } - } -} - -func TestFindElementIndexes(t *testing.T) { - tests := []struct { - description string - input []byte - elementToFind string - startIndex int64 - endIndex int64 - found bool - errorExpected bool - errorContains string - }{ - { - description: "Find single element after another element", - input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"test": 1,"consented_providers": [1608,765,492]}}`), - elementToFind: "consented_providers", - startIndex: 68, - endIndex: 106, - found: true, - errorExpected: false, - errorContains: "", - }, - { - description: "Find single element before another element", - input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1608,765,492],"test": 1}}`), - elementToFind: "consented_providers", - startIndex: 59, - endIndex: 97, - found: true, - errorExpected: false, - errorContains: "", + description: "Drop Element That Doesn't Exist", + input: []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`), + elementToRemove: []string{"test2"}, + output: []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`), + errorExpected: false, + errorContains: "", }, { - description: "Find single element", - input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1545,2563,1411]}}`), - elementToFind: "consented_providers", - startIndex: 59, - endIndex: 98, - found: true, - errorExpected: false, - errorContains: "", + description: "Drop Nested Element Single Occurrence", + input: []byte(`{"consented_providers_settings":{"consented_providers":[1608,765,492],"test":1},"data": [{"test1":5},{"test2": [1,2,3]}]}`), + elementToRemove: []string{"consented_providers_settings", "test"}, + output: []byte(`{"consented_providers_settings":{"consented_providers":[1608,765,492]},"data": [{"test1":5},{"test2": [1,2,3]}]}`), + errorExpected: false, + errorContains: "", }, { - description: "Find single element string", - input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": "test"}}`), - elementToFind: "consented_providers", - startIndex: 59, - endIndex: 88, - found: true, - errorExpected: false, - errorContains: "", + description: "Drop Nested Element Multiple Occurrence", + input: []byte(`{"consented_providers_settings":{"consented_providers":[1608,765,492],"test":1},"data": [{"test":5},{"test": [1,2,3]}]}`), + elementToRemove: []string{"consented_providers_settings", "test"}, + output: []byte(`{"consented_providers_settings":{"consented_providers":[1608,765,492]},"data": [{"test":5},{"test": [1,2,3]}]}`), + errorExpected: false, + errorContains: "", }, { - description: "Find parent element between two elements", - input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1},"test": 123}`), - elementToFind: "consented_providers_settings", - startIndex: 26, - endIndex: 109, - found: true, - errorExpected: false, - errorContains: "", + description: "Drop Nested Element Multiple Occurrence Skip Path", + input: []byte(`{"consented_providers_settings":{"consented_providers":[1608,765,492],"data": {"amp":1, "test": 25}}}`), + elementToRemove: []string{"consented_providers_settings", "test"}, + output: []byte(`{"consented_providers_settings":{"consented_providers":[1608,765,492],"data": {"amp":1}}}`), + errorExpected: false, + errorContains: "", }, { - description: "Find parent element before element", - input: []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1},"test": 123}`), - elementToFind: "consented_providers_settings", - startIndex: 1, - endIndex: 84, - found: true, - errorExpected: false, - errorContains: "", + description: "Drop Nested Structure Single Occurrence", + input: []byte(`{"consented_providers":{"providers":[1608,765,492],"test":{"nested":true}},"data": [{"test":5},{"test": [1,2,3]}]}`), + elementToRemove: []string{"consented_providers", "test"}, + output: []byte(`{"consented_providers":{"providers":[1608,765,492]},"data": [{"test":5},{"test": [1,2,3]}]}`), + errorExpected: false, + errorContains: "", }, { - description: "Find parent element after element", - input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`), - elementToFind: "consented_providers_settings", - startIndex: 25, - endIndex: 108, - found: true, - errorExpected: false, - errorContains: "", + description: "Drop Nested Structure Single Occurrence Deep Nested", + input: []byte(`{"consented_providers":{"providers":[1608,765,492],"test":{"nested":true, "nested2": {"test6": 123}}},"data": [{"test":5},{"test": [1,2,3]}]}`), + elementToRemove: []string{"consented_providers", "test6"}, + output: []byte(`{"consented_providers":{"providers":[1608,765,492],"test":{"nested":true, "nested2": {}}},"data": [{"test":5},{"test": [1,2,3]}]}`), + errorExpected: false, + errorContains: "", }, { - description: "Find parent element only", - input: []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`), - elementToFind: "consented_providers_settings", - startIndex: 1, - endIndex: 83, - found: true, - errorExpected: false, - errorContains: "", + description: "Drop Nested Structure Single Occurrence Deep Nested Full Path", + input: []byte(`{"consented_providers":{"providers":[1608,765,492],"test":{"nested":true,"nested2": {"test6": 123}}},"data": [{"test":5},{"test": [1,2,3]}]}`), + elementToRemove: []string{"consented_providers", "test", "nested"}, + output: []byte(`{"consented_providers":{"providers":[1608,765,492],"test":{"nested2": {"test6": 123}}},"data": [{"test":5},{"test": [1,2,3]}]}`), + errorExpected: false, + errorContains: "", }, { - description: "Find element that doesn't exist", - input: []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`), - elementToFind: "test2", - startIndex: -1, - endIndex: -1, - found: false, - errorExpected: false, - errorContains: "", + description: "Drop Nested Structure Doesn't Exist", + input: []byte(`{"consented_providers":{"providers":[1608,765,492]},"test":{"nested":true}},"data": [{"test":5},{"test": [1,2,3]}]}`), + elementToRemove: []string{"consented_providers", "test2"}, + output: []byte(`{"consented_providers":{"providers":[1608,765,492]},"test":{"nested":true}},"data": [{"test":5},{"test": [1,2,3]}]}`), + errorExpected: false, + errorContains: "", }, //Errors { - description: "Error decode", - input: []byte(`{"consented_providers_settings": {"consented_providers": ["123",1,,1365,5678,1545,2563,1411], "test": 1}}`), - elementToFind: "consented_providers", - startIndex: -1, - endIndex: -1, - found: false, - errorExpected: true, - errorContains: "looking for beginning of value", + description: "Error Decode", + input: []byte(`{"consented_providers_settings": {"consented_providers": ["123",1,,1365,5678,1545,2563,1411], "test": 1}}`), + elementToRemove: []string{"consented_providers"}, + output: []byte(``), + errorExpected: true, + errorContains: "looking for beginning of value", }, { - description: "Error malformed", - input: []byte(`{consented_providers_settings: {"consented_providers": [1365,5678,1545,2563,1411], "test": 1}}`), - elementToFind: "consented_providers", - startIndex: -1, - endIndex: -1, - found: false, - errorExpected: true, - errorContains: "invalid character", + description: "Error Malformed", + input: []byte(`{consented_providers_settings: {"consented_providers": [1365,5678,1545,2563,1411], "test": 1}}`), + elementToRemove: []string{"consented_providers"}, + output: []byte(``), + errorExpected: true, + errorContains: "invalid character", }, } for _, tt := range tests { - found, start, end, err := findElementIndexes(tt.input, tt.elementToFind) - - assert.Equal(t, tt.found, found, "Incorrect value of element existence") + res, err := DropElement(tt.input, tt.elementToRemove...) if tt.errorExpected { assert.Error(t, err, "Error should not be nil") assert.True(t, strings.Contains(err.Error(), tt.errorContains)) } else { assert.NoError(t, err, "Error should be nil") - assert.Equal(t, tt.startIndex, start, "Result is incorrect") - assert.Equal(t, tt.endIndex, end, "Result is incorrect") + assert.Equal(t, tt.output, res, "Result is incorrect") } - } }