diff --git a/adapters/displayio/displayio.go b/adapters/displayio/displayio.go new file mode 100644 index 00000000000..b54998553b3 --- /dev/null +++ b/adapters/displayio/displayio.go @@ -0,0 +1,188 @@ +package displayio + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "text/template" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +type adapter struct { + endpoint *template.Template +} + +type reqDioExt struct { + UserSession string `json:"userSession,omitempty"` + PlacementId string `json:"placementId"` + InventoryId string `json:"inventoryId"` +} + +func (adapter *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + headers.Add("x-openrtb-version", "2.5") + + result := make([]*adapters.RequestData, 0, len(request.Imp)) + errs := make([]error, 0, len(request.Imp)) + + for _, impression := range request.Imp { + var requestExt map[string]interface{} + + if impression.BidFloorCur == "" || impression.BidFloor == 0 { + impression.BidFloorCur = "USD" + } else if impression.BidFloorCur != "USD" { + convertedValue, err := requestInfo.ConvertCurrency(impression.BidFloor, impression.BidFloorCur, "USD") + + if err != nil { + errs = append(errs, err) + continue + } + + impression.BidFloor = convertedValue + impression.BidFloorCur = "USD" + } + + if len(impression.Ext) == 0 { + errs = append(errs, errors.New("impression extensions required")) + continue + } + + var bidderExt adapters.ExtImpBidder + err := json.Unmarshal(impression.Ext, &bidderExt) + + if err != nil { + errs = append(errs, err) + continue + } + + var impressionExt openrtb_ext.ExtImpDisplayio + err = json.Unmarshal(bidderExt.Bidder, &impressionExt) + if err != nil { + errs = append(errs, err) + continue + } + + dioExt := reqDioExt{PlacementId: impressionExt.PlacementId, InventoryId: impressionExt.InventoryId} + + requestCopy := *request + + err = json.Unmarshal(requestCopy.Ext, &requestExt) + if err != nil { + requestExt = make(map[string]interface{}) + } + + requestExt["displayio"] = dioExt + + requestCopy.Ext, err = json.Marshal(requestExt) + if err != nil { + errs = append(errs, err) + continue + } + + requestCopy.Imp = []openrtb2.Imp{impression} + body, err := json.Marshal(requestCopy) + if err != nil { + errs = append(errs, err) + continue + } + + url, err := adapter.buildEndpointURL(&impressionExt) + if err != nil { + return nil, []error{err} + } + + result = append(result, &adapters.RequestData{ + Method: "POST", + Uri: url, + Body: body, + Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(requestCopy.Imp), + }) + } + + if len(result) == 0 { + return nil, errs + } + return result, errs +} + +// MakeBids translates Displayio bid response to prebid-server specific format +func (adapter *adapter) MakeBids(internalRequest *openrtb2.BidRequest, _ *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + + if adapters.IsResponseStatusCodeNoContent(responseData) { + return nil, nil + } + + if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil { + return nil, []error{err} + } + + var bidResp openrtb2.BidResponse + + if err := json.Unmarshal(responseData.Body, &bidResp); err != nil { + msg := fmt.Sprintf("Bad server response: %d", err) + return nil, []error{&errortypes.BadServerResponse{Message: msg}} + } + + if len(bidResp.SeatBid) != 1 { + msg := fmt.Sprintf("Invalid SeatBids count: %d", len(bidResp.SeatBid)) + return nil, []error{&errortypes.BadServerResponse{Message: msg}} + } + + var errs []error + bidResponse := adapters.NewBidderResponse() + + for _, sb := range bidResp.SeatBid { + for i := range sb.Bid { + bidType, err := getBidMediaTypeFromMtype(&sb.Bid[i]) + if err != nil { + errs = append(errs, err) + } else { + b := &adapters.TypedBid{ + Bid: &sb.Bid[i], + BidType: bidType, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + } + + return bidResponse, errs +} + +func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) { + endpoint, err := template.New("endpointTemplate").Parse(config.Endpoint) + if err != nil { + return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) + } + + bidder := &adapter{ + endpoint: endpoint, + } + return bidder, nil +} + +func getBidMediaTypeFromMtype(bid *openrtb2.Bid) (openrtb_ext.BidType, error) { + switch bid.MType { + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner, nil + case openrtb2.MarkupVideo: + return openrtb_ext.BidTypeVideo, nil + default: + return "", fmt.Errorf("unexpected media type for bid: %s", bid.ImpID) + } +} + +func (adapter *adapter) buildEndpointURL(params *openrtb_ext.ExtImpDisplayio) (string, error) { + endpointParams := macros.EndpointTemplateParams{PublisherID: params.PublisherId} + return macros.ResolveMacros(adapter.endpoint, endpointParams) +} diff --git a/adapters/displayio/displayio_test.go b/adapters/displayio/displayio_test.go new file mode 100644 index 00000000000..9f41a59e2a0 --- /dev/null +++ b/adapters/displayio/displayio_test.go @@ -0,0 +1,22 @@ +package displayio + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderDisplayio, + config.Adapter{Endpoint: "https://adapter.endpoint/?macro={{.PublisherID}}"}, + config.Server{ExternalUrl: "https://server.endpoint/"}, + ) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "displayiotest", bidder) +} diff --git a/adapters/displayio/displayiotest/exemplary/multi-format.json b/adapters/displayio/displayiotest/exemplary/multi-format.json new file mode 100644 index 00000000000..c0e149a8c46 --- /dev/null +++ b/adapters/displayio/displayiotest/exemplary/multi-format.json @@ -0,0 +1,147 @@ +{ + "mockBidRequest": { + "id": "requestId10111011101110111011", + "app": { + "id": "1011" + }, + "imp": [ + { + "id": "impId10111011101110111011", + "tagid": "1011", + "ext": { + "bidder": { + "placementId": "1011", + "publisherId": "101", + "inventoryId": "1011" + } + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 640, + "h": 480 + }, + "bidfloor": 0.5, + "bidfloorcur": "USD" + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://adapter.endpoint/?macro=101", + "body": { + "id": "requestId10111011101110111011", + "app": { + "id": "1011" + }, + "imp": [ + { + "id": "impId10111011101110111011", + "tagid": "1011", + "ext": { + "bidder": { + "placementId": "1011", + "publisherId": "101", + "inventoryId": "1011" + } + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 640, + "h": 480 + }, + "bidfloor": 0.5, + "bidfloorcur": "USD" + } + ], + "ext": { + "displayio": { + "placementId": "1011", + "inventoryId": "1011" + } + } + }, + "impIDs": [ + "impId10111011101110111011" + ] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "bidid": "5778926625248726496", + "seatbid": [ + { + "seat": "seat1", + "bid": [ + { + "id": "12345", + "impid": "impId10111011101110111011", + "price": 0.01, + "adm": "", + "adomain": [ + "domain.test" + ], + "w": 300, + "h": 250, + "mtype": 1 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "12345", + "impid": "impId10111011101110111011", + "price": 0.01, + "adm": "", + "adomain": [ + "domain.test" + ], + "w": 300, + "h": 250, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/displayio/displayiotest/exemplary/multi-imp.json b/adapters/displayio/displayiotest/exemplary/multi-imp.json new file mode 100644 index 00000000000..8588ea5bf01 --- /dev/null +++ b/adapters/displayio/displayiotest/exemplary/multi-imp.json @@ -0,0 +1,211 @@ +{ + "mockBidRequest": { + "id": "test-request-multi-id", + "imp": [ + { + "id": "test-imp-id-1", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "placementId": "1010", + "publisherId": "2020", + "inventoryId": "3030" + } + }, + "bidfloor": 0.5 + }, + { + "id": "test-imp-id-2", + "banner": { + "format": [ + { + "w": 300, + "h": 150 + } + ] + }, + "ext": { + "bidder": { + "placementId": "4040", + "publisherId": "5050", + "inventoryId": "6060" + } + }, + "bidfloor": 0.5 + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://adapter.endpoint/?macro=2020", + "body": { + "id": "test-request-multi-id", + "imp": [ + { + "id": "test-imp-id-1", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "placementId": "1010", + "publisherId": "2020", + "inventoryId": "3030" + } + }, + "bidfloor": 0.5, + "bidfloorcur": "USD" + } + ], + "ext": { + "displayio": { + "placementId": "1010", + "inventoryId": "3030" + } + } + }, + "impIDs": [ + "test-imp-id-1" + ] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-multi-id", + "seatbid": [ + { + "seat": "seat1", + "bid": [ + { + "id": "testid1", + "impid": "test-imp-id-1", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "mtype": 1, + "h": 90, + "w": 728 + } + ] + } + ], + "cur": "USD" + } + } + }, + { + "expectedRequest": { + "uri": "https://adapter.endpoint/?macro=5050", + "body": { + "id": "test-request-multi-id", + "imp": [ + { + "id": "test-imp-id-2", + "banner": { + "format": [ + { + "w": 300, + "h": 150 + } + ] + }, + "ext": { + "bidder": { + "placementId": "4040", + "publisherId": "5050", + "inventoryId": "6060" + } + }, + "bidfloor": 0.5, + "bidfloorcur": "USD" + } + ], + "ext": { + "displayio": { + "placementId": "4040", + "inventoryId": "6060" + } + } + }, + "impIDs": [ + "test-imp-id-2" + ] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-multi-id", + "seatbid": [ + { + "seat": "seat2", + "bid": [ + { + "id": "testid2", + "impid": "test-imp-id-2", + "price": 0.800000, + "adm": "some-test-ad", + "crid": "crid_11", + "mtype": 1, + "h": 150, + "w": 300 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "testid1", + "impid": "test-imp-id-1", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "mtype": 1, + "w": 728, + "h": 90 + }, + "type": "banner" + } + ] + }, + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "testid2", + "impid": "test-imp-id-2", + "price": 0.8, + "adm": "some-test-ad", + "crid": "crid_11", + "mtype": 1, + "w": 300, + "h": 150 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/displayio/displayiotest/exemplary/simple-banner.json b/adapters/displayio/displayiotest/exemplary/simple-banner.json new file mode 100644 index 00000000000..15a5f913210 --- /dev/null +++ b/adapters/displayio/displayiotest/exemplary/simple-banner.json @@ -0,0 +1,117 @@ +{ + "mockBidRequest": { + "app": { + "id": "1011" + }, + "id": "requestId10111011101110111011", + "imp": [ + { + "banner": { + "h": 250, + "w": 300 + }, + "bidfloor": 0.225, + "bidfloorcur": "USD", + "id": "impId10111011101110111011", + "ext": { + "bidder": { + "placementId": "1011", + "publisherId": "101", + "inventoryId": "1011" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://adapter.endpoint/?macro=101", + "body": { + "id": "requestId10111011101110111011", + "app": { + "id": "1011" + }, + "imp": [ + { + "banner": { + "h": 250, + "w": 300 + }, + "bidfloor": 0.225, + "bidfloorcur": "USD", + "id": "impId10111011101110111011", + "ext": { + "bidder": { + "placementId": "1011", + "publisherId": "101", + "inventoryId": "1011" + } + } + } + ], + "ext": { + "displayio": { + "placementId": "1011", + "inventoryId": "1011" + } + } + }, + "impIDs": [ + "impId10111011101110111011" + ] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "12345", + "impid": "impId10111011101110111011", + "price": 0.01, + "adm": "", + "adid": "12235", + "adomain": [ + "domain.test" + ], + "w": 300, + "h": 250, + "mtype": 1 + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "12345", + "impid": "impId10111011101110111011", + "price": 0.01, + "adm": "", + "adid": "12235", + "adomain": [ + "domain.test" + ], + "w": 300, + "h": 250, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/displayio/displayiotest/exemplary/simple-video.json b/adapters/displayio/displayiotest/exemplary/simple-video.json new file mode 100644 index 00000000000..b5d027f33ba --- /dev/null +++ b/adapters/displayio/displayiotest/exemplary/simple-video.json @@ -0,0 +1,124 @@ +{ + "mockBidRequest": { + "app": { + "id": "1011" + }, + "id": "requestId10111011101110111011", + "imp": [ + { + "video": { + "mimes": [ + "video/mp4" + ], + "w": 300, + "h": 250 + }, + "bidfloor": 0.5, + "bidfloorcur": "USD", + "id": "impId10111011101110111011", + "ext": { + "bidder": { + "placementId": "1011", + "publisherId": "101", + "inventoryId": "1011" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://adapter.endpoint/?macro=101", + "body": { + "app": { + "id": "1011" + }, + "id": "requestId10111011101110111011", + "imp": [ + { + "video": { + "mimes": [ + "video/mp4" + ], + "w": 300, + "h": 250 + }, + "bidfloor": 0.5, + "bidfloorcur": "USD", + "id": "impId10111011101110111011", + "ext": { + "bidder": { + "placementId": "1011", + "publisherId": "101", + "inventoryId": "1011" + } + } + } + ], + "ext": { + "displayio": { + "placementId": "1011", + "inventoryId": "1011" + } + } + }, + "impIDs": [ + "impId10111011101110111011" + ] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "requestId10111011101110111011", + "seatbid": [ + { + "bid": [ + { + "id": "12345", + "impid": "impId10111011101110111011", + "price": 2, + "adm": "", + "adid": "12235", + "adomain": [ + "domain.test" + ], + "w": 300, + "h": 250, + "mtype": 2 + } + ], + "seat": "displayio123", + "group": 1 + } + ], + "bidid": "test123", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "12345", + "impid": "impId10111011101110111011", + "price": 2, + "adm": "", + "adid": "12235", + "adomain": [ + "domain.test" + ], + "w": 300, + "h": 250, + "mtype": 2 + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/displayio/displayiotest/supplemental/bad-response.json b/adapters/displayio/displayiotest/supplemental/bad-response.json new file mode 100644 index 00000000000..98cc44e8626 --- /dev/null +++ b/adapters/displayio/displayiotest/supplemental/bad-response.json @@ -0,0 +1,77 @@ +{ + "mockBidRequest": { + "id": "testid", + "imp": [ + { + "id": "testimpid", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "bidfloor": 0.01, + "ext": { + "bidder": { + "placementId": "1101", + "inventoryId": "1101", + "publisherId": "101" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://adapter.endpoint/?macro=101", + "body": { + "id": "testid", + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "id": "testimpid", + "bidfloor": 0.01, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "placementId": "1101", + "inventoryId": "1101", + "publisherId": "101" + } + } + } + ], + "ext": { + "displayio": { + "placementId": "1101", + "inventoryId": "1101" + } + } + }, + "impIDs": [ + "testimpid" + ] + }, + "mockResponse": { + "status": 200, + "body": "" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Bad server response: .*", + "comparison": "regex" + } + ] +} \ No newline at end of file diff --git a/adapters/displayio/displayiotest/supplemental/currency-conversion.json b/adapters/displayio/displayiotest/supplemental/currency-conversion.json new file mode 100644 index 00000000000..32b6b2a16b4 --- /dev/null +++ b/adapters/displayio/displayiotest/supplemental/currency-conversion.json @@ -0,0 +1,95 @@ +{ + "mockBidRequest": { + "id": "testid", + "imp": [ + { + "id": "testimpid", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": "1101", + "inventoryId": "1101", + "publisherId": "101" + } + }, + "bidfloor": 100, + "bidfloorcur": "RUB" + } + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "RUB": { + "USD": 0.01 + } + }, + "usepbsrates": false + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://adapter.endpoint/?macro=101", + "body": { + "id": "testid", + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "id": "testimpid", + "bidfloorcur": "USD", + "bidfloor": 1, + "ext": { + "bidder": { + "placementId": "1101", + "inventoryId": "1101", + "publisherId": "101" + } + } + } + ], + "ext": { + "displayio": { + "placementId": "1101", + "inventoryId": "1101" + }, + "prebid": { + "currency": { + "rates": { + "RUB": { + "USD": 0.01 + } + }, + "usepbsrates": false + } + } + } + }, + "impIDs": [ + "testimpid" + ] + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedMakeRequestsErrors": [], + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/displayio/displayiotest/supplemental/ext.json b/adapters/displayio/displayiotest/supplemental/ext.json new file mode 100644 index 00000000000..ad0835d1c61 --- /dev/null +++ b/adapters/displayio/displayiotest/supplemental/ext.json @@ -0,0 +1,30 @@ +{ + "mockBidRequest": { + "id": "testid", + "imp": [ + { + "id": "testimpid", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 320, + "h": 50 + } + ] + }, + "bidfloor": 0.5, + "bidfloorcur": "USD" + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "impression extensions required", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/displayio/displayiotest/supplemental/nobid-response.json b/adapters/displayio/displayiotest/supplemental/nobid-response.json new file mode 100644 index 00000000000..ccfb2c12ca8 --- /dev/null +++ b/adapters/displayio/displayiotest/supplemental/nobid-response.json @@ -0,0 +1,72 @@ +{ + "mockBidRequest": { + "id": "testid", + "imp": [ + { + "id": "testimpid", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": "1101", + "inventoryId": "1101", + "publisherId": "101" + } + }, + "bidfloor": 0.5, + "bidfloorcur": "USD" + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://adapter.endpoint/?macro=101", + "body": { + "id": "testid", + "imp": [ + { + "id": "testimpid", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": "1101", + "inventoryId": "1101", + "publisherId": "101" + } + }, + "bidfloor": 0.5, + "bidfloorcur": "USD" + } + ], + "ext": { + "displayio": { + "placementId": "1101", + "inventoryId": "1101" + } + } + }, + "impIDs": [ + "testimpid" + ] + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/displayio/displayiotest/supplemental/response-code-invalid.json b/adapters/displayio/displayiotest/supplemental/response-code-invalid.json new file mode 100644 index 00000000000..4bfa579c672 --- /dev/null +++ b/adapters/displayio/displayiotest/supplemental/response-code-invalid.json @@ -0,0 +1,74 @@ +{ + "mockBidRequest": { + "id": "testid", + "imp": [ + { + "id": "testimpid", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "bidfloor": 0.01, + "ext": { + "bidder": { + "placementId": "1101", + "inventoryId": "1101", + "publisherId": "101" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://adapter.endpoint/?macro=101", + "body": { + "id": "testid", + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "id": "testimpid", + "ext": { + "bidder": { + "placementId": "1101", + "inventoryId": "1101", + "publisherId": "101" + } + } + } + ], + "ext": { + "displayio": { + "placementId": "1101", + "inventoryId": "1101" + } + } + }, + "impIDs":["testimpid"] + }, + "mockResponse": { + "status": 400 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: [0-9]{3,3}. Run with request.debug = 1 for more info", + "comparison": "regex" + } + ] +} \ No newline at end of file diff --git a/adapters/displayio/displayiotest/supplemental/seatbid-response.json b/adapters/displayio/displayiotest/supplemental/seatbid-response.json new file mode 100644 index 00000000000..f0467d1f0bb --- /dev/null +++ b/adapters/displayio/displayiotest/supplemental/seatbid-response.json @@ -0,0 +1,77 @@ +{ + "mockBidRequest": { + "id": "testid", + "imp": [ + { + "id": "testimpid", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "bidfloor": 0.01, + "ext": { + "bidder": { + "placementId": "1101", + "inventoryId": "1101", + "publisherId": "101" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://adapter.endpoint/?macro=101", + "body": { + "id": "testid", + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "id": "testimpid", + "bidfloor": 0.01, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "placementId": "1101", + "inventoryId": "1101", + "publisherId": "101" + } + } + } + ], + "ext": { + "displayio": { + "placementId": "1101", + "inventoryId": "1101" + } + } + }, + "impIDs":["testimpid"] + }, + "mockResponse": { + "status": 200, + "body": { + "seatbid": [] + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Invalid SeatBids count: 0", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/displayio/displayiotest/supplemental/unexpected-media-type.json b/adapters/displayio/displayiotest/supplemental/unexpected-media-type.json new file mode 100644 index 00000000000..be66747a4c4 --- /dev/null +++ b/adapters/displayio/displayiotest/supplemental/unexpected-media-type.json @@ -0,0 +1,105 @@ +{ + "mockBidRequest": { + "app": { + "id": "1011" + }, + "id": "requestId10111011101110111011", + "imp": [ + { + "banner": { + "h": 250, + "w": 300 + }, + "bidfloor": 0.225, + "bidfloorcur": "USD", + "id": "impId10111011101110111011", + "ext": { + "bidder": { + "placementId": "1011", + "publisherId": "101", + "inventoryId": "1011" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://adapter.endpoint/?macro=101", + "body": { + "id": "requestId10111011101110111011", + "app": { + "id": "1011" + }, + "imp": [ + { + "banner": { + "h": 250, + "w": 300 + }, + "bidfloor": 0.225, + "bidfloorcur": "USD", + "id": "impId10111011101110111011", + "ext": { + "bidder": { + "placementId": "1011", + "publisherId": "101", + "inventoryId": "1011" + } + } + } + ], + "ext": { + "displayio": { + "placementId": "1011", + "inventoryId": "1011" + } + } + }, + "impIDs": [ + "impId10111011101110111011" + ] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "12345", + "impid": "impId10111011101110111011", + "price": 0.01, + "adm": "", + "adomain": [ + "domain.test" + ], + "w": 300, + "h": 250, + "mtype": 5 + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [] + } + ], + "expectedMakeBidsErrors": [ + { + "value": "unexpected media type for bid: .*", + "comparison": "regex" + } + ] +} \ No newline at end of file diff --git a/adapters/displayio/params_test.go b/adapters/displayio/params_test.go new file mode 100644 index 00000000000..f3e1de922a2 --- /dev/null +++ b/adapters/displayio/params_test.go @@ -0,0 +1,48 @@ +package displayio + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderDisplayio, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected dmx params: %s", validParam) + } + } + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderDisplayio, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema was not supposed to be valid: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"placementId": "anyPlacementId", "publisherId":"anyPublisherId", "inventoryId":"anyInventoryId"}`, +} + +var invalidParams = []string{ + `null`, + `nil`, + ``, + `[]`, + `true`, + `{}`, + `{"placementId": 1, "publisherId":"anyPublisherId", "inventoryId":"anyInventoryId"}`, + `{"placementId": "anyPlacementId", "publisherId":1, "inventoryId":"anyInventoryId"}`, + `{"placementId": "anyPlacementId", "publisherId":"anyPublisherId", "inventoryId":1}`, + `{"publisherId":"anyPublisherId", "inventoryId":"anyInventoryId"}`, + `{"placementId": "anyPlacementId", "inventoryId":"anyInventoryId"}`, + `{"placementId": "anyPlacementId", "publisherId":"anyPublisherId"}`, + `{"placementId": "anyPlacementId"}`, + `{"inventoryId":"anyInventoryId"}`, + `{"publisherId":"anyPublisherId"}`, +} diff --git a/adapters/inmobi/inmobi.go b/adapters/inmobi/inmobi.go index 9ceac3ec01a..eb69bdf3ec2 100644 --- a/adapters/inmobi/inmobi.go +++ b/adapters/inmobi/inmobi.go @@ -77,7 +77,10 @@ func (a *InMobiAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalR for _, sb := range serverBidResponse.SeatBid { for i := range sb.Bid { - mediaType := getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp) + mediaType, err := getMediaTypeForImp(sb.Bid[i]) + if err != nil { + return nil, []error{err} + } bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ Bid: &sb.Bid[i], BidType: mediaType, @@ -118,18 +121,17 @@ func preprocess(imp *openrtb2.Imp) error { return nil } -func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { - mediaType := openrtb_ext.BidTypeBanner - for _, imp := range imps { - if imp.ID == impId { - if imp.Video != nil { - mediaType = openrtb_ext.BidTypeVideo - } - if imp.Native != nil { - mediaType = openrtb_ext.BidTypeNative - } - break +func getMediaTypeForImp(bid openrtb2.Bid) (openrtb_ext.BidType, error) { + switch bid.MType { + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner, nil + case openrtb2.MarkupVideo: + return openrtb_ext.BidTypeVideo, nil + case openrtb2.MarkupNative: + return openrtb_ext.BidTypeNative, nil + default: + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unsupported mtype %d for bid %s", bid.MType, bid.ID), } } - return mediaType } diff --git a/adapters/inmobi/inmobitest/exemplary/simple-app-banner.json b/adapters/inmobi/inmobitest/exemplary/simple-app-banner.json index 563c8b9103e..01ccd98596f 100644 --- a/adapters/inmobi/inmobitest/exemplary/simple-app-banner.json +++ b/adapters/inmobi/inmobitest/exemplary/simple-app-banner.json @@ -75,7 +75,8 @@ "price": 2.0, "id": "1234", "adm": "bannerhtml", - "impid": "imp-id" + "impid": "imp-id", + "mtype": 1 } ] } @@ -94,6 +95,7 @@ "adm": "bannerhtml", "crid": "123456789", "nurl": "https://some.event.url/params", + "mtype": 1, "ext": { "prebid": { "meta": { diff --git a/adapters/inmobi/inmobitest/exemplary/simple-app-native.json b/adapters/inmobi/inmobitest/exemplary/simple-app-native.json index 7b823c13e11..0e956b6e586 100644 --- a/adapters/inmobi/inmobitest/exemplary/simple-app-native.json +++ b/adapters/inmobi/inmobitest/exemplary/simple-app-native.json @@ -73,7 +73,8 @@ "price": 2.0, "id": "1234", "adm": "native-json", - "impid": "imp-id" + "impid": "imp-id", + "mtype": 4 } ] } @@ -92,6 +93,7 @@ "adm": "native-json", "crid": "123456789", "nurl": "https://some.event.url/params", + "mtype": 4, "ext": { "prebid": { "meta": { diff --git a/adapters/inmobi/inmobitest/exemplary/simple-app-video.json b/adapters/inmobi/inmobitest/exemplary/simple-app-video.json index 69356fd4de7..644b42d573a 100644 --- a/adapters/inmobi/inmobitest/exemplary/simple-app-video.json +++ b/adapters/inmobi/inmobitest/exemplary/simple-app-video.json @@ -77,7 +77,8 @@ "price": 2.0, "id": "1234", "adm": " ", - "impid": "imp-id" + "impid": "imp-id", + "mtype": 2 } ] } @@ -96,6 +97,7 @@ "adm": " ", "crid": "123456789", "nurl": "https://some.event.url/params", + "mtype": 2, "ext": { "prebid": { "meta": { diff --git a/adapters/inmobi/inmobitest/exemplary/simple-web-banner.json b/adapters/inmobi/inmobitest/exemplary/simple-web-banner.json index 0aac1b1571d..3359906e436 100644 --- a/adapters/inmobi/inmobitest/exemplary/simple-web-banner.json +++ b/adapters/inmobi/inmobitest/exemplary/simple-web-banner.json @@ -73,7 +73,8 @@ "price": 2.0, "id": "1234", "adm": "bannerhtml", - "impid": "imp-id" + "impid": "imp-id", + "mtype": 1 } ] } @@ -92,6 +93,7 @@ "adm": "bannerhtml", "crid": "123456789", "nurl": "https://some.event.url/params", + "mtype": 1, "ext": { "prebid": { "meta": { diff --git a/adapters/inmobi/inmobitest/exemplary/simple-web-video.json b/adapters/inmobi/inmobitest/exemplary/simple-web-video.json index 7ea5dd268ef..582f9044fc9 100644 --- a/adapters/inmobi/inmobitest/exemplary/simple-web-video.json +++ b/adapters/inmobi/inmobitest/exemplary/simple-web-video.json @@ -75,7 +75,8 @@ "price": 2.0, "id": "1234", "adm": " ", - "impid": "imp-id" + "impid": "imp-id", + "mtype": 2 } ] } @@ -94,6 +95,7 @@ "adm": " ", "crid": "123456789", "nurl": "https://some.event.url/params", + "mtype": 2, "ext": { "prebid": { "meta": { diff --git a/adapters/inmobi/inmobitest/supplemental/banner-format-coersion.json b/adapters/inmobi/inmobitest/supplemental/banner-format-coersion.json index 211348de3f3..ee6aae529ee 100644 --- a/adapters/inmobi/inmobitest/supplemental/banner-format-coersion.json +++ b/adapters/inmobi/inmobitest/supplemental/banner-format-coersion.json @@ -81,7 +81,8 @@ "price": 2.0, "id": "1234", "adm": "bannerhtml", - "impid": "imp-id" + "impid": "imp-id", + "mtype": 1 } ] } @@ -100,6 +101,7 @@ "adm": "bannerhtml", "crid": "123456789", "nurl": "https://some.event.url/params", + "mtype": 1, "ext": { "prebid": { "meta": { diff --git a/adapters/inmobi/inmobitest/supplemental/invalid-mtype.json b/adapters/inmobi/inmobitest/supplemental/invalid-mtype.json new file mode 100644 index 00000000000..af2192836b0 --- /dev/null +++ b/adapters/inmobi/inmobitest/supplemental/invalid-mtype.json @@ -0,0 +1,97 @@ +{ + "mockBidRequest": { + "site": { + "page": "https://www.inmobi.com" + }, + "id": "req-id", + "device": { + "ip": "1.1.1.1", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + }, + "imp": [ + { + "ext": { + "bidder": { + "plc": "1621323101291" + } + }, + "video": { + "w": 640, + "h": 360, + "mimes": ["video/mp4"] + }, + "id": "imp-id" + } + ] + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://api.w.inmobi.com/showad/openrtb/bidder/prebid", + "body": { + "site": { + "page": "https://www.inmobi.com" + }, + "id": "req-id", + "device": { + "ip": "1.1.1.1", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + }, + "imp": [ + { + "ext": { + "bidder": { + "plc": "1621323101291" + } + }, + "video": { + "w": 640, + "h": 360, + "mimes": ["video/mp4"] + }, + "id": "imp-id" + } + ] + }, + "impIDs":["imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "req-id", + "seatbid": [ + { + "bid": [ + { + "ext": { + "prebid": { + "meta": { + "networkName": "inmobi" + } + } + }, + "nurl": "https://some.event.url/params", + "crid": "123456789", + "adomain": [], + "price": 2.0, + "id": "1234", + "adm": " ", + "impid": "imp-id", + "mtype": 0 + } + ] + } + ] + } + } + }], + + "expectedBidResponses":[], + "expectedMakeBidsErrors":[ + { + "value":"Unsupported mtype 0 for bid 1234", + "comparison":"literal" + } + ] +} + + diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go index eea6babf34e..5e533080f43 100644 --- a/adapters/pubmatic/pubmatic.go +++ b/adapters/pubmatic/pubmatic.go @@ -7,7 +7,6 @@ import ( "math" "net/http" "net/url" - "regexp" "strconv" "strings" @@ -24,22 +23,18 @@ import ( const MAX_IMPRESSIONS_PUBMATIC = 30 const MAX_MULTIFLOORS_PUBMATIC = 3 -var re = regexp.MustCompile(appLovinMaxImpressionPattern) - const ( - ae = "ae" - PUBMATIC = "[PUBMATIC]" - buyId = "buyid" - buyIdTargetingKey = "hb_buyid_" - skAdnetworkKey = "skadn" - rewardKey = "reward" - dctrKeywordName = "dctr" - urlEncodedEqualChar = "%3D" - AdServerKey = "adserver" - PBAdslotKey = "pbadslot" - bidViewability = "bidViewability" - multiFloors = "_mf" - appLovinMaxImpressionPattern = "_mf.*" + ae = "ae" + PUBMATIC = "[PUBMATIC]" + buyId = "buyid" + buyIdTargetingKey = "hb_buyid_" + skAdnetworkKey = "skadn" + rewardKey = "reward" + dctrKeywordName = "dctr" + urlEncodedEqualChar = "%3D" + AdServerKey = "adserver" + PBAdslotKey = "pbadslot" + bidViewability = "bidViewability" ) type PubmaticAdapter struct { @@ -88,6 +83,7 @@ type extRequestAdServer struct { Wrapper *pubmaticWrapperExt `json:"wrapper,omitempty"` Acat []string `json:"acat,omitempty"` Marketplace *marketplaceReqExt `json:"marketplace,omitempty"` + SendBurl bool `json:"sendburl,omitempty"` openrtb_ext.ExtRequest } @@ -113,6 +109,11 @@ func (a *PubmaticAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ad extractWrapperExtFromImp := true extractPubIDFromImp := true + displayManager, displayManagerVer := "", "" + if request.App != nil && request.App.Ext != nil { + displayManager, displayManagerVer = getDisplayManagerAndVer(request.App) + } + newReqExt, cookies, err := extractPubmaticExtFromRequest(request) if err != nil { return nil, []error{err} @@ -125,7 +126,7 @@ func (a *PubmaticAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ad impFloorsMap := map[string][]float64{} for i := 0; i < len(request.Imp); i++ { - wrapperExtFromImp, pubIDFromImp, floors, err := parseImpressionObject(&request.Imp[i], extractWrapperExtFromImp, extractPubIDFromImp) + wrapperExtFromImp, pubIDFromImp, floors, err := parseImpressionObject(&request.Imp[i], extractWrapperExtFromImp, extractPubIDFromImp, displayManager, displayManagerVer) // If the parsing is failed, remove imp and add the error. if err != nil { errs = append(errs, err) @@ -258,44 +259,6 @@ func (a *PubmaticAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ad return requestData, errs } -// buildMultiFloorRequests builds multiple requests for each floor value -func (a *PubmaticAdapter) buildMultiFloorRequests(request *openrtb2.BidRequest, impFloorsMap map[string][]float64, cookies []string) ([]*adapters.RequestData, []error) { - requestData := []*adapters.RequestData{} - errs := make([]error, 0, MAX_MULTIFLOORS_PUBMATIC*len(request.Imp)) - - for i := 0; i < MAX_MULTIFLOORS_PUBMATIC; i++ { - isFloorsUpdated := false - newImps := make([]openrtb2.Imp, len(request.Imp)) - copy(newImps, request.Imp) - //TODO-AK: Remove the imp from the request if the floor is not present except for the first floor - for j := range newImps { - floors, ok := impFloorsMap[request.Imp[j].ID] - if !ok || len(floors) <= i { - continue - } - isFloorsUpdated = true - newImps[j].BidFloor = floors[i] - newImps[j].ID = fmt.Sprintf("%s"+multiFloors+"%d", newImps[j].ID, i+1) - } - - if !isFloorsUpdated { - continue - } - - newRequest := *request - newRequest.Imp = newImps - - newRequestData, errData := a.buildAdapterRequest(&newRequest, cookies) - if errData != nil { - errs = append(errs, errData) - } - if len(newRequestData) > 0 { - requestData = append(requestData, newRequestData...) - } - } - return requestData, errs -} - // buildAdapterRequest builds the request for Pubmatic func (a *PubmaticAdapter) buildAdapterRequest(request *openrtb2.BidRequest, cookies []string) ([]*adapters.RequestData, error) { reqJSON, err := json.Marshal(request) @@ -383,7 +346,7 @@ 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, extractWrapperExtFromImp, extractPubIDFromImp bool) (*pubmaticWrapperExt, string, []float64, error) { +func parseImpressionObject(imp *openrtb2.Imp, extractWrapperExtFromImp, extractPubIDFromImp bool, displayManager, displayManagerVer string) (*pubmaticWrapperExt, string, []float64, error) { var wrapExt *pubmaticWrapperExt var pubID string var floors []float64 @@ -397,6 +360,12 @@ func parseImpressionObject(imp *openrtb2.Imp, extractWrapperExtFromImp, extractP imp.Audio = nil } + // Populate imp.displaymanager and imp.displaymanagerver if the SDK failed to do it. + if imp.DisplayManager == "" && imp.DisplayManagerVer == "" && displayManager != "" && displayManagerVer != "" { + imp.DisplayManager = displayManager + imp.DisplayManagerVer = displayManagerVer + } + var bidderExt ExtImpBidderPubmatic if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return wrapExt, pubID, floors, err @@ -451,10 +420,6 @@ func parseImpressionObject(imp *openrtb2.Imp, extractWrapperExtFromImp, extractP extMap[pmZoneIDKeyName] = pubmaticExt.PmZoneID } - if pubmaticExt.SendBurl { - extMap[sendBurlKey] = pubmaticExt.SendBurl - } - if bidderExt.SKAdnetwork != nil { extMap[skAdnetworkKey] = bidderExt.SKAdnetwork } @@ -560,6 +525,9 @@ func extractPubmaticExtFromRequest(request *openrtb2.BidRequest) (extRequestAdSe if wrapperObj, present := reqExtBidderParams["Cookie"]; present && len(wrapperObj) != 0 { err = json.Unmarshal(wrapperObj, &cookies) } + if sendBurl, ok := reqExtBidderParams[sendBurlKey]; ok { + pmReqExt.SendBurl, _ = strconv.ParseBool(string(sendBurl)) + } // OW patch -end- return pmReqExt, cookies, nil @@ -634,7 +602,12 @@ func (a *PubmaticAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa targets := getTargetingKeys(sb.Ext, string(externalRequest.BidderName)) for i := 0; i < len(sb.Bid); i++ { bid := sb.Bid[i] - bid.ImpID = trimSuffixWithPattern(bid.ImpID) + + //If imp is multi-floored then update the bid.ext.mbmfv with the floor value + if appLovinMaxImpressionRegex.MatchString(bid.ImpID) { + bid.Ext = updateBidExtWithMultiFloor(bid.ImpID, bid.Ext, externalRequest.Body) + bid.ImpID = trimSuffixWithPattern(bid.ImpID) + } bid.Ext = renameTransparencyParamsKey(bid.Ext) // Copy SeatBid Ext to Bid.Ext @@ -698,10 +671,6 @@ func (a *PubmaticAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa return bidResponse, errs } -func trimSuffixWithPattern(input string) string { - return re.ReplaceAllString(input, "") -} - func getNativeAdm(adm string) (string, error) { var err error nativeAdm := make(map[string]interface{}) @@ -854,3 +823,19 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server co } return bidder, nil } + +// getDisplayManagerAndVer returns the display manager and version from the request.app.ext or request.app.prebid.ext source and version +func getDisplayManagerAndVer(app *openrtb2.App) (string, string) { + if source, err := jsonparser.GetString(app.Ext, openrtb_ext.PrebidExtKey, "source"); err == nil && source != "" { + if version, err := jsonparser.GetString(app.Ext, openrtb_ext.PrebidExtKey, "version"); err == nil && version != "" { + return source, version + } + } + + if source, err := jsonparser.GetString(app.Ext, "source"); err == nil && source != "" { + if version, err := jsonparser.GetString(app.Ext, "version"); err == nil && version != "" { + return source, version + } + } + return "", "" +} diff --git a/adapters/pubmatic/pubmatic_ow.go b/adapters/pubmatic/pubmatic_ow.go index cc52e1d182e..7a3e99a19ba 100644 --- a/adapters/pubmatic/pubmatic_ow.go +++ b/adapters/pubmatic/pubmatic_ow.go @@ -4,16 +4,21 @@ import ( "bytes" "encoding/json" "fmt" + "regexp" "strconv" "github.com/buger/jsonparser" "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" "github.com/prebid/prebid-server/v2/openrtb_ext" ) const ( - dsaKey = "dsa" - transparencyKey = "transparency" + dsaKey = "dsa" + transparencyKey = "transparency" + multiFloors = "_mf" + appLovinMaxImpressionPattern = `_mf[0-9]+$` + multiBidMultiFloorValueKey = "mbmfv" ) var ( @@ -21,6 +26,8 @@ var ( dsaParamKey = []byte(`"dsaparams"`) ) +var appLovinMaxImpressionRegex = regexp.MustCompile(appLovinMaxImpressionPattern) + func getTargetingKeys(bidExt json.RawMessage, bidderName string) map[string]string { targets := map[string]string{} if bidExt != nil { @@ -102,3 +109,77 @@ func renameTransparencyParamsKey(bidExt []byte) []byte { return bidExt } + +// buildMultiFloorRequests builds multiple requests for each floor value +func (a *PubmaticAdapter) buildMultiFloorRequests(request *openrtb2.BidRequest, impFloorsMap map[string][]float64, cookies []string) ([]*adapters.RequestData, []error) { + requestData := make([]*adapters.RequestData, 0, MAX_MULTIFLOORS_PUBMATIC*len(request.Imp)) + errs := make([]error, 0, MAX_MULTIFLOORS_PUBMATIC*len(request.Imp)) + + for i := 0; i < MAX_MULTIFLOORS_PUBMATIC; i++ { + isFloorsUpdated := false + newImps := make([]openrtb2.Imp, len(request.Imp)) + copy(newImps, request.Imp) + //TODO-AK: Remove the imp from the request if the floor is not present except for the first floor + for j := range newImps { + floors, ok := impFloorsMap[request.Imp[j].ID] + if !ok || len(floors) <= i { + continue + } + isFloorsUpdated = true + newImps[j].BidFloor = floors[i] + newImps[j].ID = fmt.Sprintf("%s"+multiFloors+"%d", newImps[j].ID, i+1) + } + + if !isFloorsUpdated { + continue + } + + newRequest := *request + newRequest.Imp = newImps + + newRequestData, errData := a.buildAdapterRequest(&newRequest, cookies) + if errData != nil { + errs = append(errs, errData) + } + if len(newRequestData) > 0 { + requestData = append(requestData, newRequestData...) + } + } + return requestData, errs +} + +func trimSuffixWithPattern(input string) string { + return appLovinMaxImpressionRegex.ReplaceAllString(input, "") +} + +func updateBidExtWithMultiFloor(bidImpID string, bidExt, reqBody []byte) []byte { + var externalRequest openrtb2.BidRequest + + if err := json.Unmarshal(reqBody, &externalRequest); err != nil { + return bidExt + } + + updatedBidExt := bidExt + if bidExt == nil { + updatedBidExt = json.RawMessage(`{}`) + } + + for _, imp := range externalRequest.Imp { + if imp.ID != bidImpID { + continue + } + if imp.BidFloor <= 0 { + continue + } + var err error + updatedBidExt, err = jsonparser.Set(updatedBidExt, []byte(fmt.Sprintf("%f", imp.BidFloor)), multiBidMultiFloorValueKey) + if err != nil { + return bidExt + } + } + + if string(updatedBidExt) != "{}" { + return updatedBidExt + } + return bidExt +} diff --git a/adapters/pubmatic/pubmatic_ow_test.go b/adapters/pubmatic/pubmatic_ow_test.go index e46ccc70a5a..f62b92d782a 100644 --- a/adapters/pubmatic/pubmatic_ow_test.go +++ b/adapters/pubmatic/pubmatic_ow_test.go @@ -9,6 +9,7 @@ import ( "github.com/prebid/openrtb/v20/openrtb2" "github.com/prebid/prebid-server/v2/adapters" "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/stretchr/testify/assert" ) @@ -474,6 +475,48 @@ func TestPubmaticMakeBids(t *testing.T) { Currency: "USD", }, }, + { + name: "MultiBid MultiFloor request", + args: args{ + response: &adapters.ResponseData{ + StatusCode: http.StatusOK, + Body: []byte(`{"id": "test-request-id", "seatbid":[{"seat": "958", "bid":[{"id": "7706636740145184841", "impid": "test-imp-id_mf1", "price": 0.500000, "adid": "29681110", "adm": "some-test-ad", "adomain":["pubmatic.com"], "crid": "29681110", "h": 250, "w": 300, "dealid": "testdeal", "ext":{}}]}], "bidid": "5778926625248726496", "cur": "USD"}`), + }, + externalRequest: &adapters.RequestData{ + BidderName: openrtb_ext.BidderPubmatic, + Body: []byte(`{"id":"test-request-id","imp":[{"id":"test-imp-id_mf1","banner":{"w":300,"h":250},"bidfloor":0.12,"ext":{}}],"app":{"name":"AutoScout24","bundle":"com.autoscout24","storeurl":"https://play.google.com/store/apps/details?id=com.autoscout24\u0026hl=fr"}}`), + }, + }, + wantErr: nil, + wantResp: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "7706636740145184841", + ImpID: "test-imp-id", + Price: 0.500000, + AdID: "29681110", + AdM: "some-test-ad", + ADomain: []string{"pubmatic.com"}, + CrID: "29681110", + H: 250, + W: 300, + DealID: "testdeal", + Ext: json.RawMessage(`{"mbmfv":0.120000}`), + }, + BidType: openrtb_ext.BidTypeBanner, + BidVideo: &openrtb_ext.ExtBidPrebidVideo{}, + BidTargets: map[string]string{}, + BidMeta: &openrtb_ext.ExtBidPrebidMeta{ + AdvertiserID: 958, + AgencyID: 958, + MediaType: "banner", + }, + }, + }, + Currency: "USD", + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -486,3 +529,545 @@ func TestPubmaticMakeBids(t *testing.T) { }) } } + +func TestPubmaticAdapter_buildMultiFloorRequests(t *testing.T) { + type fields struct { + URI string + bidderName string + } + type args struct { + request *openrtb2.BidRequest + impFloorsMap map[string][]float64 + cookies []string + } + tests := []struct { + name string + fields fields + args args + wantRequestData []*adapters.RequestData + wantError []error + }{ + { + name: "request with single imp and single floor", + fields: fields{ + URI: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", + bidderName: "pubmatic", + }, + args: args{ + request: &openrtb2.BidRequest{ + ID: "test-request-id", + App: &openrtb2.App{ + Name: "AutoScout24", + Bundle: "com.autoscout24", + StoreURL: "https://play.google.com/store/apps/details?id=com.autoscout24&hl=fr", + }, + Imp: []openrtb2.Imp{ + { + ID: "test-imp-id", + BidFloor: 0.12, + Banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](300), + H: ptrutil.ToPtr[int64](250), + }, + Ext: json.RawMessage(`{}`), + }, + }, + }, + impFloorsMap: map[string][]float64{ + "test-imp-id": {1.2}, + }, + cookies: []string{"test-cookie"}, + }, + wantRequestData: []*adapters.RequestData{ + { + Method: "POST", + Uri: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", + Body: []byte(`{"id":"test-request-id","imp":[{"id":"test-imp-id_mf1","banner":{"w":300,"h":250},"bidfloor":1.2,"ext":{}}],"app":{"name":"AutoScout24","bundle":"com.autoscout24","storeurl":"https://play.google.com/store/apps/details?id=com.autoscout24\u0026hl=fr"}}`), + Headers: http.Header{ + "Content-Type": []string{"application/json;charset=utf-8"}, + "Accept": []string{"application/json"}, + "Cookie": []string{"test-cookie"}, + }, + ImpIDs: []string{"test-imp-id_mf1"}, + }, + }, + wantError: []error{}, + }, + { + name: "request with single imp and two floors", + fields: fields{ + URI: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", + bidderName: "pubmatic", + }, + args: args{ + request: &openrtb2.BidRequest{ + ID: "test-request-id", + App: &openrtb2.App{ + Name: "AutoScout24", + Bundle: "com.autoscout24", + StoreURL: "https://play.google.com/store/apps/details?id=com.autoscout24&hl=fr", + }, + Imp: []openrtb2.Imp{ + { + ID: "test-imp-id", + BidFloor: 0.12, + Banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](300), + H: ptrutil.ToPtr[int64](250), + }, + Ext: json.RawMessage(`{}`), + }, + }, + }, + impFloorsMap: map[string][]float64{ + "test-imp-id": {1.2, 1.3}, + }, + cookies: []string{"test-cookie"}, + }, + wantRequestData: []*adapters.RequestData{ + { + Method: "POST", + Uri: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", + Body: []byte(`{"id":"test-request-id","imp":[{"id":"test-imp-id_mf1","banner":{"w":300,"h":250},"bidfloor":1.2,"ext":{}}],"app":{"name":"AutoScout24","bundle":"com.autoscout24","storeurl":"https://play.google.com/store/apps/details?id=com.autoscout24\u0026hl=fr"}}`), + Headers: http.Header{ + "Content-Type": []string{"application/json;charset=utf-8"}, + "Accept": []string{"application/json"}, + "Cookie": []string{"test-cookie"}, + }, + ImpIDs: []string{"test-imp-id_mf1"}, + }, + { + Method: "POST", + Uri: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", + Body: []byte(`{"id":"test-request-id","imp":[{"id":"test-imp-id_mf2","banner":{"w":300,"h":250},"bidfloor":1.3,"ext":{}}],"app":{"name":"AutoScout24","bundle":"com.autoscout24","storeurl":"https://play.google.com/store/apps/details?id=com.autoscout24\u0026hl=fr"}}`), + Headers: http.Header{ + "Content-Type": []string{"application/json;charset=utf-8"}, + "Accept": []string{"application/json"}, + "Cookie": []string{"test-cookie"}, + }, + ImpIDs: []string{"test-imp-id_mf2"}, + }, + }, + wantError: []error{}, + }, + { + name: "request with single imp and max multi floors(3)", + fields: fields{ + URI: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", + bidderName: "pubmatic", + }, + args: args{ + request: &openrtb2.BidRequest{ + ID: "test-request-id", + App: &openrtb2.App{ + Name: "AutoScout24", + Bundle: "com.autoscout24", + StoreURL: "https://play.google.com/store/apps/details?id=com.autoscout24&hl=fr", + }, + Imp: []openrtb2.Imp{ + { + ID: "test-imp-id", + BidFloor: 0.12, + Banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](300), + H: ptrutil.ToPtr[int64](250), + }, + Ext: json.RawMessage(`{}`), + }, + }, + }, + impFloorsMap: map[string][]float64{ + "test-imp-id": {1.2, 1.3, 1.4}, + }, + cookies: []string{"test-cookie"}, + }, + wantRequestData: []*adapters.RequestData{ + { + Method: "POST", + Uri: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", + Body: []byte(`{"id":"test-request-id","imp":[{"id":"test-imp-id_mf1","banner":{"w":300,"h":250},"bidfloor":1.2,"ext":{}}],"app":{"name":"AutoScout24","bundle":"com.autoscout24","storeurl":"https://play.google.com/store/apps/details?id=com.autoscout24\u0026hl=fr"}}`), + Headers: http.Header{ + "Content-Type": []string{"application/json;charset=utf-8"}, + "Accept": []string{"application/json"}, + "Cookie": []string{"test-cookie"}, + }, + ImpIDs: []string{"test-imp-id_mf1"}, + }, + { + Method: "POST", + Uri: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", + Body: []byte(`{"id":"test-request-id","imp":[{"id":"test-imp-id_mf2","banner":{"w":300,"h":250},"bidfloor":1.3,"ext":{}}],"app":{"name":"AutoScout24","bundle":"com.autoscout24","storeurl":"https://play.google.com/store/apps/details?id=com.autoscout24\u0026hl=fr"}}`), + Headers: http.Header{ + "Content-Type": []string{"application/json;charset=utf-8"}, + "Accept": []string{"application/json"}, + "Cookie": []string{"test-cookie"}, + }, + ImpIDs: []string{"test-imp-id_mf2"}, + }, + { + Method: "POST", + Uri: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", + Body: []byte(`{"id":"test-request-id","imp":[{"id":"test-imp-id_mf3","banner":{"w":300,"h":250},"bidfloor":1.4,"ext":{}}],"app":{"name":"AutoScout24","bundle":"com.autoscout24","storeurl":"https://play.google.com/store/apps/details?id=com.autoscout24\u0026hl=fr"}}`), + Headers: http.Header{ + "Content-Type": []string{"application/json;charset=utf-8"}, + "Accept": []string{"application/json"}, + "Cookie": []string{"test-cookie"}, + }, + ImpIDs: []string{"test-imp-id_mf3"}, + }, + }, + wantError: []error{}, + }, + { + name: "request with multiple imp and single floor", + fields: fields{ + URI: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", + bidderName: "pubmatic", + }, + args: args{ + request: &openrtb2.BidRequest{ + ID: "test-request-id", + App: &openrtb2.App{ + Name: "AutoScout24", + Bundle: "com.autoscout24", + StoreURL: "https://play.google.com/store/apps/details?id=com.autoscout24&hl=fr", + }, + Imp: []openrtb2.Imp{ + { + ID: "test-imp-id", + BidFloor: 0.12, + Banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](300), + H: ptrutil.ToPtr[int64](250), + }, + Ext: json.RawMessage(`{}`), + }, + { + ID: "test-imp-id2", + BidFloor: 0.13, + Banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](300), + H: ptrutil.ToPtr[int64](250), + }, + Ext: json.RawMessage(`{}`), + }, + }, + }, + impFloorsMap: map[string][]float64{ + "test-imp-id": {1.2}, + "test-imp-id2": {1.3}, + }, + cookies: []string{"test-cookie"}, + }, + wantRequestData: []*adapters.RequestData{ + { + Method: "POST", + Uri: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", + Body: []byte(`{"id":"test-request-id","imp":[{"id":"test-imp-id_mf1","banner":{"w":300,"h":250},"bidfloor":1.2,"ext":{}},{"id":"test-imp-id2_mf1","banner":{"w":300,"h":250},"bidfloor":1.3,"ext":{}}],"app":{"name":"AutoScout24","bundle":"com.autoscout24","storeurl":"https://play.google.com/store/apps/details?id=com.autoscout24\u0026hl=fr"}}`), + Headers: http.Header{ + "Content-Type": []string{"application/json;charset=utf-8"}, + "Accept": []string{"application/json"}, + "Cookie": []string{"test-cookie"}, + }, + ImpIDs: []string{"test-imp-id_mf1", "test-imp-id2_mf1"}, + }, + }, + wantError: []error{}, + }, + { + name: "request with multiple imp and 3 floors (max) for only one imp", + fields: fields{ + URI: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", + bidderName: "pubmatic", + }, + args: args{ + request: &openrtb2.BidRequest{ + ID: "test-request-id", + App: &openrtb2.App{ + Name: "AutoScout24", + Bundle: "com.autoscout24", + StoreURL: "https://play.google.com/store/apps/details?id=com.autoscout24&hl=fr", + }, + Imp: []openrtb2.Imp{ + { + ID: "test-imp-id", + BidFloor: 0.12, + Banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](300), + H: ptrutil.ToPtr[int64](250), + }, + Ext: json.RawMessage(`{}`), + }, + { + ID: "test-imp-id2", + BidFloor: 0.34, + Banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](300), + H: ptrutil.ToPtr[int64](250), + }, + Ext: json.RawMessage(`{}`), + }, + }, + }, + impFloorsMap: map[string][]float64{ + "test-imp-id": {1.2, 1.3, 1.4}, + }, + cookies: []string{"test-cookie"}, + }, + wantRequestData: []*adapters.RequestData{ + { + Method: "POST", + Uri: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", + Body: []byte(`{"id":"test-request-id","imp":[{"id":"test-imp-id_mf1","banner":{"w":300,"h":250},"bidfloor":1.2,"ext":{}},{"id":"test-imp-id2","banner":{"w":300,"h":250},"bidfloor":0.34,"ext":{}}],"app":{"name":"AutoScout24","bundle":"com.autoscout24","storeurl":"https://play.google.com/store/apps/details?id=com.autoscout24\u0026hl=fr"}}`), + Headers: http.Header{ + "Content-Type": []string{"application/json;charset=utf-8"}, + "Accept": []string{"application/json"}, + "Cookie": []string{"test-cookie"}, + }, + ImpIDs: []string{"test-imp-id_mf1", "test-imp-id2"}, + }, + { + Method: "POST", + Uri: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", + Body: []byte(`{"id":"test-request-id","imp":[{"id":"test-imp-id_mf2","banner":{"w":300,"h":250},"bidfloor":1.3,"ext":{}},{"id":"test-imp-id2","banner":{"w":300,"h":250},"bidfloor":0.34,"ext":{}}],"app":{"name":"AutoScout24","bundle":"com.autoscout24","storeurl":"https://play.google.com/store/apps/details?id=com.autoscout24\u0026hl=fr"}}`), + Headers: http.Header{ + "Content-Type": []string{"application/json;charset=utf-8"}, + "Accept": []string{"application/json"}, + "Cookie": []string{"test-cookie"}, + }, + ImpIDs: []string{"test-imp-id_mf2", "test-imp-id2"}, + }, + { + Method: "POST", + Uri: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", + Body: []byte(`{"id":"test-request-id","imp":[{"id":"test-imp-id_mf3","banner":{"w":300,"h":250},"bidfloor":1.4,"ext":{}},{"id":"test-imp-id2","banner":{"w":300,"h":250},"bidfloor":0.34,"ext":{}}],"app":{"name":"AutoScout24","bundle":"com.autoscout24","storeurl":"https://play.google.com/store/apps/details?id=com.autoscout24\u0026hl=fr"}}`), + Headers: http.Header{ + "Content-Type": []string{"application/json;charset=utf-8"}, + "Accept": []string{"application/json"}, + "Cookie": []string{"test-cookie"}, + }, + ImpIDs: []string{"test-imp-id_mf3", "test-imp-id2"}, + }, + }, + wantError: []error{}, + }, + { + name: "request with multiple imp with 3 floors for one imp and 2 floors for another imp", + fields: fields{ + URI: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", + bidderName: "pubmatic", + }, + args: args{ + request: &openrtb2.BidRequest{ + ID: "test-request-id", + App: &openrtb2.App{ + Name: "AutoScout24", + Bundle: "com.autoscout24", + StoreURL: "https://play.google.com/store/apps/details?id=com.autoscout24&hl=fr", + }, + Imp: []openrtb2.Imp{ + { + ID: "test-imp-id", + BidFloor: 0.12, + Banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](300), + H: ptrutil.ToPtr[int64](250), + }, + Ext: json.RawMessage(`{}`), + }, + { + ID: "test-imp-id2", + BidFloor: 0.34, + Banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](300), + H: ptrutil.ToPtr[int64](250), + }, + Ext: json.RawMessage(`{}`), + }, + }, + }, + impFloorsMap: map[string][]float64{ + "test-imp-id": {1.2, 1.3, 1.4}, + "test-imp-id2": {1.2, 1.3}, + }, + cookies: []string{"test-cookie"}, + }, + wantRequestData: []*adapters.RequestData{ + { + Method: "POST", + Uri: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", + Body: []byte(`{"id":"test-request-id","imp":[{"id":"test-imp-id_mf1","banner":{"w":300,"h":250},"bidfloor":1.2,"ext":{}},{"id":"test-imp-id2_mf1","banner":{"w":300,"h":250},"bidfloor":1.2,"ext":{}}],"app":{"name":"AutoScout24","bundle":"com.autoscout24","storeurl":"https://play.google.com/store/apps/details?id=com.autoscout24\u0026hl=fr"}}`), + Headers: http.Header{ + "Content-Type": []string{"application/json;charset=utf-8"}, + "Accept": []string{"application/json"}, + "Cookie": []string{"test-cookie"}, + }, + ImpIDs: []string{"test-imp-id_mf1", "test-imp-id2_mf1"}, + }, + { + Method: "POST", + Uri: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", + Body: []byte(`{"id":"test-request-id","imp":[{"id":"test-imp-id_mf2","banner":{"w":300,"h":250},"bidfloor":1.3,"ext":{}},{"id":"test-imp-id2_mf2","banner":{"w":300,"h":250},"bidfloor":1.3,"ext":{}}],"app":{"name":"AutoScout24","bundle":"com.autoscout24","storeurl":"https://play.google.com/store/apps/details?id=com.autoscout24\u0026hl=fr"}}`), + Headers: http.Header{ + "Content-Type": []string{"application/json;charset=utf-8"}, + "Accept": []string{"application/json"}, + "Cookie": []string{"test-cookie"}, + }, + ImpIDs: []string{"test-imp-id_mf2", "test-imp-id2_mf2"}, + }, + { + Method: "POST", + Uri: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", + Body: []byte(`{"id":"test-request-id","imp":[{"id":"test-imp-id_mf3","banner":{"w":300,"h":250},"bidfloor":1.4,"ext":{}},{"id":"test-imp-id2","banner":{"w":300,"h":250},"bidfloor":0.34,"ext":{}}],"app":{"name":"AutoScout24","bundle":"com.autoscout24","storeurl":"https://play.google.com/store/apps/details?id=com.autoscout24\u0026hl=fr"}}`), + Headers: http.Header{ + "Content-Type": []string{"application/json;charset=utf-8"}, + "Accept": []string{"application/json"}, + "Cookie": []string{"test-cookie"}, + }, + ImpIDs: []string{"test-imp-id_mf3", "test-imp-id2"}, + }, + }, + wantError: []error{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &PubmaticAdapter{ + URI: tt.fields.URI, + bidderName: tt.fields.bidderName, + } + gotRequestData, gotError := a.buildMultiFloorRequests(tt.args.request, tt.args.impFloorsMap, tt.args.cookies) + assert.Equal(t, tt.wantRequestData, gotRequestData) + assert.Equal(t, tt.wantError, gotError) + }) + } +} + +func TestTrimSuffixWithPattern(t *testing.T) { + type args struct { + input string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "input string is empty", + args: args{ + input: "", + }, + want: "", + }, + { + name: "input string does not contain pattern", + args: args{ + input: "div123456789", + }, + want: "div123456789", + }, + { + name: "input string contains pattern", + args: args{ + input: "div123456789_mf1", + }, + want: "div123456789", + }, + { + name: "input string contains pattern at the end", + args: args{ + input: "div123456789_mf1_mf2", + }, + want: "div123456789_mf1", + }, + { + name: "input string contains pattern at the start", + args: args{ + input: "mf1_mf2_mf123456789", + }, + want: "mf1_mf2", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := trimSuffixWithPattern(tt.args.input) + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_updateBidExtWithMultiFloor(t *testing.T) { + type args struct { + bidImpID string + bidExt []byte + reqBody []byte + } + tests := []struct { + name string + args args + want []byte + }{ + { + name: "empty request body", + args: args{ + bidImpID: "test-imp-id", + reqBody: []byte(``), + bidExt: []byte(`{"buyid":"testBuyId","deal_channel":1,"dsa":{"transparency":[{"dsaparams":[1,2]}]},"dspid":6,"prebiddealpriority":1}`), + }, + want: []byte(`{"buyid":"testBuyId","deal_channel":1,"dsa":{"transparency":[{"dsaparams":[1,2]}]},"dspid":6,"prebiddealpriority":1}`), + }, + { + name: "request body with no imp", + args: args{ + bidImpID: "test-imp-id", + reqBody: []byte(`{"id":"test-request-id"}`), + bidExt: []byte(`{"buyid":"testBuyId","deal_channel":1,"dsa":{"transparency":[{"dsaparams":[1,2]}]},"dspid":6,"prebiddealpriority":1}`), + }, + want: []byte(`{"buyid":"testBuyId","deal_channel":1,"dsa":{"transparency":[{"dsaparams":[1,2]}]},"dspid":6,"prebiddealpriority":1}`), + }, + { + name: "request body with imp but no matching imp with bidImpID", + args: args{ + bidImpID: "test-imp-id", + reqBody: []byte(`{"id":"test-request-id","imp":[{"id":"test-imp-id2","banner":{"w":300,"h":250},"bidfloor":0.12,"ext":{}}]}`), + bidExt: []byte(`{"buyid":"testBuyId","deal_channel":1,"dsa":{"transparency":[{"dsaparams":[1,2]}]},"dspid":6,"prebiddealpriority":1}`), + }, + want: []byte(`{"buyid":"testBuyId","deal_channel":1,"dsa":{"transparency":[{"dsaparams":[1,2]}]},"dspid":6,"prebiddealpriority":1}`), + }, + { + name: "request body with imp and matching imp with bidImpID", + args: args{ + bidImpID: "test-imp-id", + reqBody: []byte(`{"id":"test-request-id","imp":[{"id":"test-imp-id","banner":{"w":300,"h":250},"bidfloor":0.12,"ext":{}}]}`), + bidExt: []byte(`{"buyid":"testBuyId","deal_channel":1,"dsa":{"transparency":[{"dsaparams":[1,2]}]},"dspid":6,"prebiddealpriority":1}`), + }, + want: []byte(`{"buyid":"testBuyId","deal_channel":1,"dsa":{"transparency":[{"dsaparams":[1,2]}]},"dspid":6,"prebiddealpriority":1,"mbmfv":0.120000}`), + }, + { + name: "request body with multiple imp and matching imp with bidImpID", + args: args{ + bidImpID: "test-imp-id", + reqBody: []byte(`{"id":"test-request-id","imp":[{"id":"test-imp-id","banner":{"w":300,"h":250},"bidfloor":0.12,"ext":{}},{"id":"test-imp-id2","banner":{"w":300,"h":250},"bidfloor":0.13,"ext":{}}]}`), + bidExt: []byte(`{"buyid":"testBuyId","deal_channel":1,"dsa":{"transparency":[{"dsaparams":[1,2]}]},"dspid":6,"prebiddealpriority":1}`), + }, + want: []byte(`{"buyid":"testBuyId","deal_channel":1,"dsa":{"transparency":[{"dsaparams":[1,2]}]},"dspid":6,"prebiddealpriority":1,"mbmfv":0.120000}`), + }, + { + name: "request body with imp and matching imp with bidImpID and no bidExt", + args: args{ + bidImpID: "test-imp-id", + reqBody: []byte(`{"id":"test-request-id","imp":[{"id":"test-imp-id","banner":{"w":300,"h":250},"bidfloor":0.12,"ext":{}}]}`), + bidExt: nil, + }, + want: []byte(`{"mbmfv":0.120000}`), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := updateBidExtWithMultiFloor(tt.args.bidImpID, tt.args.bidExt, tt.args.reqBody) + assert.Equal(t, tt.want, got) + }) + } +} + +//Need to write happy path test cases with nil bidExt diff --git a/adapters/pubmatic/pubmatic_test.go b/adapters/pubmatic/pubmatic_test.go index 78d495ba983..6bf8a8bf06d 100644 --- a/adapters/pubmatic/pubmatic_test.go +++ b/adapters/pubmatic/pubmatic_test.go @@ -81,15 +81,22 @@ func TestParseImpressionObject(t *testing.T) { imp *openrtb2.Imp extractWrapperExtFromImp bool extractPubIDFromImp bool + displayManager string + displayManagerVer string + } + type want struct { + bidfloor float64 + impExt json.RawMessage + displayManager string + displayManagerVer string } tests := []struct { name string args args expectedWrapperExt *pubmaticWrapperExt expectedPublisherId string + want want wantErr bool - expectedBidfloor float64 - expectedImpExt json.RawMessage }{ { name: "imp.bidfloor empty and kadfloor set", @@ -99,8 +106,10 @@ func TestParseImpressionObject(t *testing.T) { Ext: json.RawMessage(`{"bidder":{"kadfloor":"0.12"}}`), }, }, - expectedBidfloor: 0.12, - expectedImpExt: json.RawMessage(nil), + want: want{ + bidfloor: 0.12, + impExt: json.RawMessage(nil), + }, }, { name: "imp.bidfloor set and kadfloor empty", @@ -111,8 +120,10 @@ func TestParseImpressionObject(t *testing.T) { Ext: json.RawMessage(`{"bidder":{}}`), }, }, - expectedBidfloor: 0.12, - expectedImpExt: json.RawMessage(nil), + want: want{ + bidfloor: 0.12, + impExt: json.RawMessage(nil), + }, }, { name: "imp.bidfloor set and kadfloor invalid", @@ -123,8 +134,10 @@ func TestParseImpressionObject(t *testing.T) { Ext: json.RawMessage(`{"bidder":{"kadfloor":"aaa"}}`), }, }, - expectedBidfloor: 0.12, - expectedImpExt: json.RawMessage(nil), + want: want{ + bidfloor: 0.12, + impExt: json.RawMessage(nil), + }, }, { name: "imp.bidfloor set and kadfloor set, higher imp.bidfloor", @@ -135,7 +148,10 @@ func TestParseImpressionObject(t *testing.T) { Ext: json.RawMessage(`{"bidder":{"kadfloor":"0.11"}}`), }, }, - expectedBidfloor: 0.12, + want: want{ + bidfloor: 0.12, + impExt: json.RawMessage(nil), + }, }, { name: "imp.bidfloor set and kadfloor set, higher kadfloor", @@ -146,8 +162,10 @@ func TestParseImpressionObject(t *testing.T) { Ext: json.RawMessage(`{"bidder":{"kadfloor":"0.13"}}`), }, }, - expectedBidfloor: 0.13, - expectedImpExt: json.RawMessage(nil), + want: want{ + bidfloor: 0.13, + impExt: json.RawMessage(nil), + }, }, { name: "kadfloor string set with whitespace", @@ -158,8 +176,10 @@ func TestParseImpressionObject(t *testing.T) { Ext: json.RawMessage(`{"bidder":{"kadfloor":" \t 0.13 "}}`), }, }, - expectedBidfloor: 0.13, - expectedImpExt: json.RawMessage(nil), + want: want{ + bidfloor: 0.13, + impExt: json.RawMessage(nil), + }, }, { name: "bidViewability Object is set in imp.ext.prebid.pubmatic, pass to imp.ext", @@ -169,27 +189,74 @@ func TestParseImpressionObject(t *testing.T) { Ext: json.RawMessage(`{"bidder":{"bidViewability":{"adSizes":{"728x90":{"createdAt":1679993940011,"rendered":20,"totalViewTime":424413,"viewed":17}},"adUnit":{"createdAt":1679993940011,"rendered":25,"totalViewTime":424413,"viewed":17}}}}`), }, }, - expectedImpExt: json.RawMessage(`{"bidViewability":{"adSizes":{"728x90":{"createdAt":1679993940011,"rendered":20,"totalViewTime":424413,"viewed":17}},"adUnit":{"createdAt":1679993940011,"rendered":25,"totalViewTime":424413,"viewed":17}}}`), + want: want{ + impExt: json.RawMessage(`{"bidViewability":{"adSizes":{"728x90":{"createdAt":1679993940011,"rendered":20,"totalViewTime":424413,"viewed":17}},"adUnit":{"createdAt":1679993940011,"rendered":25,"totalViewTime":424413,"viewed":17}}}`), + }, }, { - name: "sendburl set in imp.ext.prebid.pubmatic, pass to imp.ext", + name: "Populate imp.displaymanager and imp.displaymanagerver if both are empty in imp", args: args{ imp: &openrtb2.Imp{ Video: &openrtb2.Video{}, - Ext: json.RawMessage(`{"bidder":{"sendburl":true}}`), + Ext: json.RawMessage(`{"bidder":{"kadfloor":"0.12"}}`), + }, + displayManager: "prebid-mobile", + displayManagerVer: "1.0.0", + }, + want: want{ + bidfloor: 0.12, + impExt: json.RawMessage(nil), + displayManager: "prebid-mobile", + displayManagerVer: "1.0.0", + }, + }, + { + name: "do not populate imp.displaymanager and imp.displaymanagerver in imp if only displaymanager or displaymanagerver is present in args", + args: args{ + imp: &openrtb2.Imp{ + Video: &openrtb2.Video{}, + Ext: json.RawMessage(`{"bidder":{"kadfloor":"0.12"}}`), + DisplayManagerVer: "1.0.0", }, + displayManager: "prebid-mobile", + displayManagerVer: "1.0.0", + }, + want: want{ + bidfloor: 0.12, + impExt: json.RawMessage(nil), + displayManagerVer: "1.0.0", + }, + }, + { + name: "do not populate imp.displaymanager and imp.displaymanagerver if already present in imp", + args: args{ + imp: &openrtb2.Imp{ + Video: &openrtb2.Video{}, + Ext: json.RawMessage(`{"bidder":{"kadfloor":"0.12"}}`), + DisplayManager: "prebid-mobile", + DisplayManagerVer: "1.0.0", + }, + displayManager: "prebid-android", + displayManagerVer: "2.0.0", + }, + want: want{ + bidfloor: 0.12, + impExt: json.RawMessage(nil), + displayManager: "prebid-mobile", + displayManagerVer: "1.0.0", }, - expectedImpExt: json.RawMessage(`{"sendburl":true}`), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - receivedWrapperExt, receivedPublisherId, _, err := parseImpressionObject(tt.args.imp, tt.args.extractWrapperExtFromImp, tt.args.extractPubIDFromImp) + receivedWrapperExt, receivedPublisherId, _, err := parseImpressionObject(tt.args.imp, tt.args.extractWrapperExtFromImp, tt.args.extractPubIDFromImp, tt.args.displayManager, tt.args.displayManagerVer) assert.Equal(t, tt.wantErr, err != nil) assert.Equal(t, tt.expectedWrapperExt, receivedWrapperExt) assert.Equal(t, tt.expectedPublisherId, receivedPublisherId) - assert.Equal(t, tt.expectedBidfloor, tt.args.imp.BidFloor) - assert.Equal(t, tt.expectedImpExt, tt.args.imp.Ext) + assert.Equal(t, tt.want.bidfloor, tt.args.imp.BidFloor) + assert.Equal(t, tt.want.impExt, tt.args.imp.Ext) + assert.Equal(t, tt.want.displayManager, tt.args.imp.DisplayManager) + assert.Equal(t, tt.want.displayManagerVer, tt.args.imp.DisplayManagerVer) }) } } @@ -315,6 +382,42 @@ func TestExtractPubmaticExtFromRequest(t *testing.T) { }, wantErr: false, }, + { + name: "valid wrapper object and senburl true in bidderparams", + args: args{ + request: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"bidderparams":{"wrapper":{"profile":123,"version":456},"sendburl":true}}}`), + }, + }, + expectedReqExt: extRequestAdServer{ + Wrapper: &pubmaticWrapperExt{ProfileID: 123, VersionID: 456}, + ExtRequest: openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + BidderParams: json.RawMessage(`{"wrapper":{"profile":123,"version":456},"sendburl":true}`), + }, + }, + SendBurl: true, + }, + wantErr: false, + }, + { + name: "valid wrapper object and invalid senburl true in bidderparams", + args: args{ + request: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"bidderparams":{"wrapper":{"profile":123,"version":456},"sendburl":{}}}}`), + }, + }, + expectedReqExt: extRequestAdServer{ + Wrapper: &pubmaticWrapperExt{ProfileID: 123, VersionID: 456}, + ExtRequest: openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + BidderParams: json.RawMessage(`{"wrapper":{"profile":123,"version":456},"sendburl":{}}`), + }, + }, + SendBurl: false, + }, + wantErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -556,40 +659,6 @@ func TestPubmaticAdapter_MakeBids(t *testing.T) { Currency: "USD", }, }, - { - name: "response impid has suffix(mf1)", - args: args{ - response: &adapters.ResponseData{ - StatusCode: http.StatusOK, - Body: []byte(`{"id": "test-request-id", "seatbid":[{"seat": "958", "bid":[{"id": "7706636740145184841", "impid": "test-imp-id_mf1", "price": 0.500000, "adid": "29681110", "adm": "some-test-ad", "adomain":["pubmatic.com"], "crid": "29681110", "h": 250, "w": 300, "dealid": "testdeal", "ext":null}]}], "bidid": "5778926625248726496", "cur": "USD"}`), - }, - externalRequest: &adapters.RequestData{BidderName: openrtb_ext.BidderPubmatic}, - }, - wantErr: nil, - wantResp: &adapters.BidderResponse{ - Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ - ID: "7706636740145184841", - ImpID: "test-imp-id", - Price: 0.500000, - AdID: "29681110", - AdM: "some-test-ad", - ADomain: []string{"pubmatic.com"}, - CrID: "29681110", - H: 250, - W: 300, - DealID: "testdeal", - Ext: json.RawMessage(`null`), - }, - BidType: openrtb_ext.BidTypeBanner, - BidVideo: &openrtb_ext.ExtBidPrebidVideo{}, - BidTargets: map[string]string{}, - }, - }, - Currency: "USD", - }, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -893,419 +962,6 @@ func TestGetMapFromJSON(t *testing.T) { } } -func TestPubmaticAdapter_buildMultiFloorRequests(t *testing.T) { - type fields struct { - URI string - bidderName string - } - type args struct { - request *openrtb2.BidRequest - impFloorsMap map[string][]float64 - cookies []string - } - tests := []struct { - name string - fields fields - args args - wantRequestData []*adapters.RequestData - wantError []error - }{ - { - name: "request with single imp and single floor", - fields: fields{ - URI: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", - bidderName: "pubmatic", - }, - args: args{ - request: &openrtb2.BidRequest{ - ID: "test-request-id", - App: &openrtb2.App{ - Name: "AutoScout24", - Bundle: "com.autoscout24", - StoreURL: "https://play.google.com/store/apps/details?id=com.autoscout24&hl=fr", - }, - Imp: []openrtb2.Imp{ - { - ID: "test-imp-id", - BidFloor: 0.12, - Banner: &openrtb2.Banner{ - W: ptrutil.ToPtr[int64](300), - H: ptrutil.ToPtr[int64](250), - }, - Ext: json.RawMessage(`{}`), - }, - }, - }, - impFloorsMap: map[string][]float64{ - "test-imp-id": {1.2}, - }, - cookies: []string{"test-cookie"}, - }, - wantRequestData: []*adapters.RequestData{ - { - Method: "POST", - Uri: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", - Body: []byte(`{"id":"test-request-id","imp":[{"id":"test-imp-id_mf1","banner":{"w":300,"h":250},"bidfloor":1.2,"ext":{}}],"app":{"name":"AutoScout24","bundle":"com.autoscout24","storeurl":"https://play.google.com/store/apps/details?id=com.autoscout24\u0026hl=fr"}}`), - Headers: http.Header{ - "Content-Type": []string{"application/json;charset=utf-8"}, - "Accept": []string{"application/json"}, - "Cookie": []string{"test-cookie"}, - }, - ImpIDs: []string{"test-imp-id_mf1"}, - }, - }, - wantError: []error{}, - }, - { - name: "request with single imp and two floors", - fields: fields{ - URI: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", - bidderName: "pubmatic", - }, - args: args{ - request: &openrtb2.BidRequest{ - ID: "test-request-id", - App: &openrtb2.App{ - Name: "AutoScout24", - Bundle: "com.autoscout24", - StoreURL: "https://play.google.com/store/apps/details?id=com.autoscout24&hl=fr", - }, - Imp: []openrtb2.Imp{ - { - ID: "test-imp-id", - BidFloor: 0.12, - Banner: &openrtb2.Banner{ - W: ptrutil.ToPtr[int64](300), - H: ptrutil.ToPtr[int64](250), - }, - Ext: json.RawMessage(`{}`), - }, - }, - }, - impFloorsMap: map[string][]float64{ - "test-imp-id": {1.2, 1.3}, - }, - cookies: []string{"test-cookie"}, - }, - wantRequestData: []*adapters.RequestData{ - { - Method: "POST", - Uri: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", - Body: []byte(`{"id":"test-request-id","imp":[{"id":"test-imp-id_mf1","banner":{"w":300,"h":250},"bidfloor":1.2,"ext":{}}],"app":{"name":"AutoScout24","bundle":"com.autoscout24","storeurl":"https://play.google.com/store/apps/details?id=com.autoscout24\u0026hl=fr"}}`), - Headers: http.Header{ - "Content-Type": []string{"application/json;charset=utf-8"}, - "Accept": []string{"application/json"}, - "Cookie": []string{"test-cookie"}, - }, - ImpIDs: []string{"test-imp-id_mf1"}, - }, - { - Method: "POST", - Uri: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", - Body: []byte(`{"id":"test-request-id","imp":[{"id":"test-imp-id_mf2","banner":{"w":300,"h":250},"bidfloor":1.3,"ext":{}}],"app":{"name":"AutoScout24","bundle":"com.autoscout24","storeurl":"https://play.google.com/store/apps/details?id=com.autoscout24\u0026hl=fr"}}`), - Headers: http.Header{ - "Content-Type": []string{"application/json;charset=utf-8"}, - "Accept": []string{"application/json"}, - "Cookie": []string{"test-cookie"}, - }, - ImpIDs: []string{"test-imp-id_mf2"}, - }, - }, - wantError: []error{}, - }, - { - name: "request with single imp and max multi floors(3)", - fields: fields{ - URI: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", - bidderName: "pubmatic", - }, - args: args{ - request: &openrtb2.BidRequest{ - ID: "test-request-id", - App: &openrtb2.App{ - Name: "AutoScout24", - Bundle: "com.autoscout24", - StoreURL: "https://play.google.com/store/apps/details?id=com.autoscout24&hl=fr", - }, - Imp: []openrtb2.Imp{ - { - ID: "test-imp-id", - BidFloor: 0.12, - Banner: &openrtb2.Banner{ - W: ptrutil.ToPtr[int64](300), - H: ptrutil.ToPtr[int64](250), - }, - Ext: json.RawMessage(`{}`), - }, - }, - }, - impFloorsMap: map[string][]float64{ - "test-imp-id": {1.2, 1.3, 1.4}, - }, - cookies: []string{"test-cookie"}, - }, - wantRequestData: []*adapters.RequestData{ - { - Method: "POST", - Uri: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", - Body: []byte(`{"id":"test-request-id","imp":[{"id":"test-imp-id_mf1","banner":{"w":300,"h":250},"bidfloor":1.2,"ext":{}}],"app":{"name":"AutoScout24","bundle":"com.autoscout24","storeurl":"https://play.google.com/store/apps/details?id=com.autoscout24\u0026hl=fr"}}`), - Headers: http.Header{ - "Content-Type": []string{"application/json;charset=utf-8"}, - "Accept": []string{"application/json"}, - "Cookie": []string{"test-cookie"}, - }, - ImpIDs: []string{"test-imp-id_mf1"}, - }, - { - Method: "POST", - Uri: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", - Body: []byte(`{"id":"test-request-id","imp":[{"id":"test-imp-id_mf2","banner":{"w":300,"h":250},"bidfloor":1.3,"ext":{}}],"app":{"name":"AutoScout24","bundle":"com.autoscout24","storeurl":"https://play.google.com/store/apps/details?id=com.autoscout24\u0026hl=fr"}}`), - Headers: http.Header{ - "Content-Type": []string{"application/json;charset=utf-8"}, - "Accept": []string{"application/json"}, - "Cookie": []string{"test-cookie"}, - }, - ImpIDs: []string{"test-imp-id_mf2"}, - }, - { - Method: "POST", - Uri: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", - Body: []byte(`{"id":"test-request-id","imp":[{"id":"test-imp-id_mf3","banner":{"w":300,"h":250},"bidfloor":1.4,"ext":{}}],"app":{"name":"AutoScout24","bundle":"com.autoscout24","storeurl":"https://play.google.com/store/apps/details?id=com.autoscout24\u0026hl=fr"}}`), - Headers: http.Header{ - "Content-Type": []string{"application/json;charset=utf-8"}, - "Accept": []string{"application/json"}, - "Cookie": []string{"test-cookie"}, - }, - ImpIDs: []string{"test-imp-id_mf3"}, - }, - }, - wantError: []error{}, - }, - { - name: "request with multiple imp and single floor", - fields: fields{ - URI: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", - bidderName: "pubmatic", - }, - args: args{ - request: &openrtb2.BidRequest{ - ID: "test-request-id", - App: &openrtb2.App{ - Name: "AutoScout24", - Bundle: "com.autoscout24", - StoreURL: "https://play.google.com/store/apps/details?id=com.autoscout24&hl=fr", - }, - Imp: []openrtb2.Imp{ - { - ID: "test-imp-id", - BidFloor: 0.12, - Banner: &openrtb2.Banner{ - W: ptrutil.ToPtr[int64](300), - H: ptrutil.ToPtr[int64](250), - }, - Ext: json.RawMessage(`{}`), - }, - { - ID: "test-imp-id2", - BidFloor: 0.13, - Banner: &openrtb2.Banner{ - W: ptrutil.ToPtr[int64](300), - H: ptrutil.ToPtr[int64](250), - }, - Ext: json.RawMessage(`{}`), - }, - }, - }, - impFloorsMap: map[string][]float64{ - "test-imp-id": {1.2}, - "test-imp-id2": {1.3}, - }, - cookies: []string{"test-cookie"}, - }, - wantRequestData: []*adapters.RequestData{ - { - Method: "POST", - Uri: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", - Body: []byte(`{"id":"test-request-id","imp":[{"id":"test-imp-id_mf1","banner":{"w":300,"h":250},"bidfloor":1.2,"ext":{}},{"id":"test-imp-id2_mf1","banner":{"w":300,"h":250},"bidfloor":1.3,"ext":{}}],"app":{"name":"AutoScout24","bundle":"com.autoscout24","storeurl":"https://play.google.com/store/apps/details?id=com.autoscout24\u0026hl=fr"}}`), - Headers: http.Header{ - "Content-Type": []string{"application/json;charset=utf-8"}, - "Accept": []string{"application/json"}, - "Cookie": []string{"test-cookie"}, - }, - ImpIDs: []string{"test-imp-id_mf1", "test-imp-id2_mf1"}, - }, - }, - wantError: []error{}, - }, - { - name: "request with multiple imp and 3 floors (max) for only one imp", - fields: fields{ - URI: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", - bidderName: "pubmatic", - }, - args: args{ - request: &openrtb2.BidRequest{ - ID: "test-request-id", - App: &openrtb2.App{ - Name: "AutoScout24", - Bundle: "com.autoscout24", - StoreURL: "https://play.google.com/store/apps/details?id=com.autoscout24&hl=fr", - }, - Imp: []openrtb2.Imp{ - { - ID: "test-imp-id", - BidFloor: 0.12, - Banner: &openrtb2.Banner{ - W: ptrutil.ToPtr[int64](300), - H: ptrutil.ToPtr[int64](250), - }, - Ext: json.RawMessage(`{}`), - }, - { - ID: "test-imp-id2", - BidFloor: 0.34, - Banner: &openrtb2.Banner{ - W: ptrutil.ToPtr[int64](300), - H: ptrutil.ToPtr[int64](250), - }, - Ext: json.RawMessage(`{}`), - }, - }, - }, - impFloorsMap: map[string][]float64{ - "test-imp-id": {1.2, 1.3, 1.4}, - }, - cookies: []string{"test-cookie"}, - }, - wantRequestData: []*adapters.RequestData{ - { - Method: "POST", - Uri: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", - Body: []byte(`{"id":"test-request-id","imp":[{"id":"test-imp-id_mf1","banner":{"w":300,"h":250},"bidfloor":1.2,"ext":{}},{"id":"test-imp-id2","banner":{"w":300,"h":250},"bidfloor":0.34,"ext":{}}],"app":{"name":"AutoScout24","bundle":"com.autoscout24","storeurl":"https://play.google.com/store/apps/details?id=com.autoscout24\u0026hl=fr"}}`), - Headers: http.Header{ - "Content-Type": []string{"application/json;charset=utf-8"}, - "Accept": []string{"application/json"}, - "Cookie": []string{"test-cookie"}, - }, - ImpIDs: []string{"test-imp-id_mf1", "test-imp-id2"}, - }, - { - Method: "POST", - Uri: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", - Body: []byte(`{"id":"test-request-id","imp":[{"id":"test-imp-id_mf2","banner":{"w":300,"h":250},"bidfloor":1.3,"ext":{}},{"id":"test-imp-id2","banner":{"w":300,"h":250},"bidfloor":0.34,"ext":{}}],"app":{"name":"AutoScout24","bundle":"com.autoscout24","storeurl":"https://play.google.com/store/apps/details?id=com.autoscout24\u0026hl=fr"}}`), - Headers: http.Header{ - "Content-Type": []string{"application/json;charset=utf-8"}, - "Accept": []string{"application/json"}, - "Cookie": []string{"test-cookie"}, - }, - ImpIDs: []string{"test-imp-id_mf2", "test-imp-id2"}, - }, - { - Method: "POST", - Uri: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", - Body: []byte(`{"id":"test-request-id","imp":[{"id":"test-imp-id_mf3","banner":{"w":300,"h":250},"bidfloor":1.4,"ext":{}},{"id":"test-imp-id2","banner":{"w":300,"h":250},"bidfloor":0.34,"ext":{}}],"app":{"name":"AutoScout24","bundle":"com.autoscout24","storeurl":"https://play.google.com/store/apps/details?id=com.autoscout24\u0026hl=fr"}}`), - Headers: http.Header{ - "Content-Type": []string{"application/json;charset=utf-8"}, - "Accept": []string{"application/json"}, - "Cookie": []string{"test-cookie"}, - }, - ImpIDs: []string{"test-imp-id_mf3", "test-imp-id2"}, - }, - }, - wantError: []error{}, - }, - { - name: "request with multiple imp with 3 floors for one imp and 2 floors for another imp", - fields: fields{ - URI: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", - bidderName: "pubmatic", - }, - args: args{ - request: &openrtb2.BidRequest{ - ID: "test-request-id", - App: &openrtb2.App{ - Name: "AutoScout24", - Bundle: "com.autoscout24", - StoreURL: "https://play.google.com/store/apps/details?id=com.autoscout24&hl=fr", - }, - Imp: []openrtb2.Imp{ - { - ID: "test-imp-id", - BidFloor: 0.12, - Banner: &openrtb2.Banner{ - W: ptrutil.ToPtr[int64](300), - H: ptrutil.ToPtr[int64](250), - }, - Ext: json.RawMessage(`{}`), - }, - { - ID: "test-imp-id2", - BidFloor: 0.34, - Banner: &openrtb2.Banner{ - W: ptrutil.ToPtr[int64](300), - H: ptrutil.ToPtr[int64](250), - }, - Ext: json.RawMessage(`{}`), - }, - }, - }, - impFloorsMap: map[string][]float64{ - "test-imp-id": {1.2, 1.3, 1.4}, - "test-imp-id2": {1.2, 1.3}, - }, - cookies: []string{"test-cookie"}, - }, - wantRequestData: []*adapters.RequestData{ - { - Method: "POST", - Uri: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", - Body: []byte(`{"id":"test-request-id","imp":[{"id":"test-imp-id_mf1","banner":{"w":300,"h":250},"bidfloor":1.2,"ext":{}},{"id":"test-imp-id2_mf1","banner":{"w":300,"h":250},"bidfloor":1.2,"ext":{}}],"app":{"name":"AutoScout24","bundle":"com.autoscout24","storeurl":"https://play.google.com/store/apps/details?id=com.autoscout24\u0026hl=fr"}}`), - Headers: http.Header{ - "Content-Type": []string{"application/json;charset=utf-8"}, - "Accept": []string{"application/json"}, - "Cookie": []string{"test-cookie"}, - }, - ImpIDs: []string{"test-imp-id_mf1", "test-imp-id2_mf1"}, - }, - { - Method: "POST", - Uri: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", - Body: []byte(`{"id":"test-request-id","imp":[{"id":"test-imp-id_mf2","banner":{"w":300,"h":250},"bidfloor":1.3,"ext":{}},{"id":"test-imp-id2_mf2","banner":{"w":300,"h":250},"bidfloor":1.3,"ext":{}}],"app":{"name":"AutoScout24","bundle":"com.autoscout24","storeurl":"https://play.google.com/store/apps/details?id=com.autoscout24\u0026hl=fr"}}`), - Headers: http.Header{ - "Content-Type": []string{"application/json;charset=utf-8"}, - "Accept": []string{"application/json"}, - "Cookie": []string{"test-cookie"}, - }, - ImpIDs: []string{"test-imp-id_mf2", "test-imp-id2_mf2"}, - }, - { - Method: "POST", - Uri: "https://hbopenbid.pubmatic.com/translator?source=prebid-server", - Body: []byte(`{"id":"test-request-id","imp":[{"id":"test-imp-id_mf3","banner":{"w":300,"h":250},"bidfloor":1.4,"ext":{}},{"id":"test-imp-id2","banner":{"w":300,"h":250},"bidfloor":0.34,"ext":{}}],"app":{"name":"AutoScout24","bundle":"com.autoscout24","storeurl":"https://play.google.com/store/apps/details?id=com.autoscout24\u0026hl=fr"}}`), - Headers: http.Header{ - "Content-Type": []string{"application/json;charset=utf-8"}, - "Accept": []string{"application/json"}, - "Cookie": []string{"test-cookie"}, - }, - ImpIDs: []string{"test-imp-id_mf3", "test-imp-id2"}, - }, - }, - wantError: []error{}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - a := &PubmaticAdapter{ - URI: tt.fields.URI, - bidderName: tt.fields.bidderName, - } - gotRequestData, gotError := a.buildMultiFloorRequests(tt.args.request, tt.args.impFloorsMap, tt.args.cookies) - assert.Equal(t, tt.wantRequestData, gotRequestData) - assert.Equal(t, tt.wantError, gotError) - }) - } -} - func TestPubmaticAdapter_buildAdapterRequest(t *testing.T) { type fields struct { URI string @@ -1447,55 +1103,131 @@ func TestPubmaticAdapter_buildAdapterRequest(t *testing.T) { } } -func TestTrimSuffixWithPattern(t *testing.T) { +func TestGetDisplayManagerAndVer(t *testing.T) { type args struct { - input string + app *openrtb2.App + } + type want struct { + displayManager string + displayManagerVer string } tests := []struct { name string args args - want string + want want }{ { - name: "input string is empty", + name: "request app object is not nil but app.ext has no source and version", + args: args{ + + app: &openrtb2.App{ + Name: "AutoScout24", + Ext: json.RawMessage(`{}`), + }, + }, + want: want{ + displayManager: "", + displayManagerVer: "", + }, + }, + { + name: "request app object is not nil and app.ext has source and version", args: args{ - input: "", + + app: &openrtb2.App{ + Name: "AutoScout24", + Ext: json.RawMessage(`{"source":"prebid-mobile","version":"1.0.0"}`), + }, + }, + want: want{ + displayManager: "prebid-mobile", + displayManagerVer: "1.0.0", }, - want: "", }, { - name: "input string does not contain pattern", + name: "request app object is not nil and app.ext.prebid has source and version", args: args{ - input: "div123456789", + app: &openrtb2.App{ + Name: "AutoScout24", + Ext: json.RawMessage(`{"prebid":{"source":"prebid-mobile","version":"1.0.0"}}`), + }, + }, + want: want{ + displayManager: "prebid-mobile", + displayManagerVer: "1.0.0", }, - want: "div123456789", }, { - name: "input string contains pattern", + name: "request app object is not nil and app.ext has only version", args: args{ - input: "div123456789_mf1", + app: &openrtb2.App{ + Name: "AutoScout24", + Ext: json.RawMessage(`{"version":"1.0.0"}`), + }, + }, + want: want{ + displayManager: "", + displayManagerVer: "", }, - want: "div123456789", }, { - name: "input string contains pattern at the end", + name: "request app object is not nil and app.ext has only source", args: args{ - input: "div123456789_mf1_mf2", + app: &openrtb2.App{ + Name: "AutoScout24", + Ext: json.RawMessage(`{"source":"prebid-mobile"}`), + }, + }, + want: want{ + displayManager: "", + displayManagerVer: "", }, - want: "div123456789", }, { - name: "input string contains pattern at the start", + name: "request app object is not nil and app.ext have empty source but version is present", args: args{ - input: "mf1_mf2_div123456789", + app: &openrtb2.App{ + Name: "AutoScout24", + Ext: json.RawMessage(`{"source":"", "version":"1.0.0"}`), + }, + }, + want: want{ + displayManager: "", + displayManagerVer: "", + }, + }, + { + name: "request app object is not nil and app.ext have empty version but source is present", + args: args{ + app: &openrtb2.App{ + Name: "AutoScout24", + Ext: json.RawMessage(`{"source":"prebid-mobile", "version":""}`), + }, + }, + want: want{ + displayManager: "", + displayManagerVer: "", + }, + }, + { + name: "request app object is not nil and both app.ext and app.ext.prebid have source and version", + args: args{ + app: &openrtb2.App{ + Name: "AutoScout24", + Ext: json.RawMessage(`{"source":"prebid-mobile-android","version":"2.0.0","prebid":{"source":"prebid-mobile","version":"1.0.0"}}`), + }, + }, + want: want{ + displayManager: "prebid-mobile", + displayManagerVer: "1.0.0", }, - want: "mf1", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := trimSuffixWithPattern(tt.args.input) - assert.Equal(t, tt.want, got) + displayManager, displayManagerVer := getDisplayManagerAndVer(tt.args.app) + assert.Equal(t, tt.want.displayManager, displayManager) + assert.Equal(t, tt.want.displayManagerVer, displayManagerVer) }) } } diff --git a/adapters/vastbidder/etree_parser_test.go b/adapters/vastbidder/etree_parser_test.go index 43b79a46562..25afb18af31 100644 --- a/adapters/vastbidder/etree_parser_test.go +++ b/adapters/vastbidder/etree_parser_test.go @@ -330,6 +330,12 @@ func getPricingDetailsTestCases() []struct { wantPrice: 12.05, wantCurrency: "USD", }, + { + name: "vast_with_whitespace_cdata_pricing", + vastXML: ` `, + wantPrice: 12.05, + wantCurrency: "USD", + }, { name: "vast_gt_2.x_pricing", vastXML: `12.05`, diff --git a/analytics/pubmatic/helper.go b/analytics/pubmatic/helper.go index a0bc446b1c2..1776b29d062 100644 --- a/analytics/pubmatic/helper.go +++ b/analytics/pubmatic/helper.go @@ -51,7 +51,7 @@ func getGdprEnabledFlag(partnerConfigMap map[int]map[string]string) int { } // send function will send the owlogger to analytics endpoint -func send(rCtx *models.RequestCtx, url string, headers http.Header, mhc mhttp.MultiHttpContextInterface) { +var send = func(rCtx *models.RequestCtx, url string, headers http.Header, mhc mhttp.MultiHttpContextInterface) { startTime := time.Now() hc, _ := mhttp.NewHttpCall(url, "") @@ -72,7 +72,7 @@ func send(rCtx *models.RequestCtx, url string, headers http.Header, mhc mhttp.Mu rCtx.PubID, rCtx.ProfileID, rCtx.VersionID) // we will not record at version level in prometheus metric - rCtx.MetricsEngine.RecordPublisherWrapperLoggerFailure(rCtx.PubIDStr, rCtx.ProfileIDStr, "") + rCtx.MetricsEngine.RecordPublisherWrapperLoggerFailure(rCtx.PubIDStr) return } rCtx.MetricsEngine.RecordSendLoggerDataTime(time.Since(startTime)) diff --git a/analytics/pubmatic/helper_test.go b/analytics/pubmatic/helper_test.go index 0eb48d8a1ff..18d3dd1fa15 100644 --- a/analytics/pubmatic/helper_test.go +++ b/analytics/pubmatic/helper_test.go @@ -209,7 +209,7 @@ func TestSendMethod(t *testing.T) { }, getMetricsEngine: func() *mock_metrics.MockMetricsEngine { mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) - mockEngine.EXPECT().RecordPublisherWrapperLoggerFailure("5890", "1", "") + mockEngine.EXPECT().RecordPublisherWrapperLoggerFailure("5890") return mockEngine }, getMockMultiHttpContext: func() *mock_mhttp.MockMultiHttpContextInterface { diff --git a/analytics/pubmatic/logger.go b/analytics/pubmatic/logger.go index 597c4320801..569740069c0 100644 --- a/analytics/pubmatic/logger.go +++ b/analytics/pubmatic/logger.go @@ -332,7 +332,7 @@ func getPartnerRecordsByImp(ao analytics.AuctionObject, rCtx *models.RequestCtx) // owlogger would not contain non-mapped bidders, do we need this if _, ok := impCtx.NonMapped[seat]; ok { - break + continue } var kgp, kgpv, kgpsv, adFormat string @@ -383,6 +383,9 @@ func getPartnerRecordsByImp(ao analytics.AuctionObject, rCtx *models.RequestCtx) } price := bid.Price + // If bids are rejected before setting bidExt.OriginalBidCPM, calculate the price and ocpm values based on the currency and revshare. + price = computeBidPriceForBidsRejectedBeforeSettingOCPM(rCtx, &bidExt, price, revShare, ao) + bid.Price = price if ao.Response.Cur != models.USD { if bidCtx.EN != 0 { // valid-bids + dropped-bids+ default-bids price = bidCtx.EN @@ -564,3 +567,32 @@ func getAdPodSlot(adPodConfig *models.AdPod) *AdPodSlot { return &adPodSlot } + +func GetBidPriceAfterCurrencyConversion(price float64, requestCurrencies []string, responseCurrency string, + currencyConverter func(fromCurrency string, toCurrency string, value float64) (float64, error)) float64 { + if len(requestCurrencies) == 0 { + requestCurrencies = []string{models.USD} + } + for _, requestCurrency := range requestCurrencies { + if value, err := currencyConverter(responseCurrency, requestCurrency, price); err == nil { + return value + } + } + return 0 // in case of error, send 0 value to make it consistent with prebid +} + +func computeBidPriceForBidsRejectedBeforeSettingOCPM(rCtx *models.RequestCtx, bidExt *models.BidExt, + price, revshare float64, ao analytics.AuctionObject) float64 { + if price != 0 && bidExt.OriginalBidCPM == 0 { + if len(bidExt.OriginalBidCur) == 0 { + bidExt.OriginalBidCur = models.USD + } + bidExt.OriginalBidCPM = price + price = price * models.GetBidAdjustmentValue(revshare) + if cpmUSD, err := rCtx.CurrencyConversion(bidExt.OriginalBidCur, models.USD, price); err == nil { + bidExt.OriginalBidCPMUSD = cpmUSD + } + price = GetBidPriceAfterCurrencyConversion(price, ao.RequestWrapper.Cur, bidExt.OriginalBidCur, rCtx.CurrencyConversion) + } + return price +} diff --git a/analytics/pubmatic/logger_test.go b/analytics/pubmatic/logger_test.go index b01f1f7b501..65d2e6d048e 100644 --- a/analytics/pubmatic/logger_test.go +++ b/analytics/pubmatic/logger_test.go @@ -2,6 +2,7 @@ package pubmatic import ( "encoding/json" + "fmt" "net/http" "net/url" "testing" @@ -294,7 +295,8 @@ func TestGetPartnerRecordsByImp(t *testing.T) { BidCtx: map[string]models.BidCtx{ "bid-id-1": { BidExt: models.BidExt{ - ExtBid: openrtb_ext.ExtBid{}, + ExtBid: openrtb_ext.ExtBid{}, + OriginalBidCPM: 10, }, }, }, @@ -347,7 +349,8 @@ func TestGetPartnerRecordsByImp(t *testing.T) { BidCtx: map[string]models.BidCtx{ "bid-id-1": { BidExt: models.BidExt{ - ExtBid: openrtb_ext.ExtBid{}, + ExtBid: openrtb_ext.ExtBid{}, + OriginalBidCPM: 10, }, }, }, @@ -403,7 +406,8 @@ func TestGetPartnerRecordsByImp(t *testing.T) { BidCtx: map[string]models.BidCtx{ "bid-id-1": { BidExt: models.BidExt{ - ExtBid: openrtb_ext.ExtBid{}, + ExtBid: openrtb_ext.ExtBid{}, + OriginalBidCPM: 10, }, }, }, @@ -459,7 +463,8 @@ func TestGetPartnerRecordsByImp(t *testing.T) { BidCtx: map[string]models.BidCtx{ "bid-id-1": { BidExt: models.BidExt{ - ExtBid: openrtb_ext.ExtBid{}, + ExtBid: openrtb_ext.ExtBid{}, + OriginalBidCPM: 10, }, }, }, @@ -512,7 +517,8 @@ func TestGetPartnerRecordsByImp(t *testing.T) { BidCtx: map[string]models.BidCtx{ "bid-id-1": { BidExt: models.BidExt{ - ExtBid: openrtb_ext.ExtBid{}, + ExtBid: openrtb_ext.ExtBid{}, + OriginalBidCPM: 10, }, }, }, @@ -564,7 +570,8 @@ func TestGetPartnerRecordsByImp(t *testing.T) { BidCtx: map[string]models.BidCtx{ "bid-id-1": { BidExt: models.BidExt{ - ExtBid: openrtb_ext.ExtBid{}, + ExtBid: openrtb_ext.ExtBid{}, + OriginalBidCPM: 10, }, }, }, @@ -709,7 +716,8 @@ func TestGetPartnerRecordsByImpForTracker(t *testing.T) { BidCtx: map[string]models.BidCtx{ "bid-id-1": { BidExt: models.BidExt{ - ExtBid: openrtb_ext.ExtBid{}, + OriginalBidCPM: 10, + ExtBid: openrtb_ext.ExtBid{}, }, }, }, @@ -771,6 +779,7 @@ func TestGetPartnerRecordsByImpForTracker(t *testing.T) { BidCtx: map[string]models.BidCtx{ "bid-id-1": { BidExt: models.BidExt{ + OriginalBidCPM: 12, ExtBid: openrtb_ext.ExtBid{ Prebid: &openrtb_ext.ExtBidPrebid{ Floors: &openrtb_ext.ExtBidPrebidFloors{ @@ -1015,7 +1024,8 @@ func TestGetPartnerRecordsByImpForDefaultBids(t *testing.T) { BidCtx: map[string]models.BidCtx{ "bid-id-1": { BidExt: models.BidExt{ - ExtBid: openrtb_ext.ExtBid{}, + ExtBid: openrtb_ext.ExtBid{}, + OriginalBidCPM: 10, }, }, }, @@ -1232,6 +1242,210 @@ func TestGetPartnerRecordsByImpForDefaultBids(t *testing.T) { }, partners: map[string][]PartnerRecord{}, }, + { + name: "Multi_impression_request_slot_not_mapped_for_imp1_for_appnexus", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 10, + }, + }, + Seat: "pubmatic", + }, + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp2", + Price: 10, + }, + }, + Seat: "pubmatic", + }, + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-2", + ImpID: "imp2", + Price: 20, + }, + }, + Seat: "appnexus", + }, + }, + }, + SeatNonBid: []openrtb_ext.SeatNonBid{}, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidFloorCur: "USD", + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + OriginalBidCPM: 10, + OriginalBidCur: "USD", + }, + }, + "bid-id-2": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + Nbr: exchange.ErrorGeneral.Ptr(), + }, + }, + }, + NonMapped: map[string]struct{}{ + "appnexus": {}, + }, + }, + "imp2": { + BidFloorCur: "USD", + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + OriginalBidCPM: 10, + OriginalBidCur: "USD", + }, + }, + "bid-id-2": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + OriginalBidCPM: 20, + OriginalBidCur: "USD", + }, + }, + }, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + NetECPM: 10, + GrossECPM: 10, + OriginalCPM: 10, + OriginalCur: "USD", + PartnerID: "pubmatic", + BidderCode: "pubmatic", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + }, + }, + "imp2": { + { + NetECPM: 10, + GrossECPM: 10, + OriginalCPM: 10, + OriginalCur: "USD", + PartnerID: "pubmatic", + BidderCode: "pubmatic", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + }, + { + NetECPM: 20, + GrossECPM: 20, + OriginalCPM: 20, + OriginalCur: "USD", + PartnerID: "appnexus", + BidderCode: "appnexus", + PartnerSize: "0x0", + BidID: "bid-id-2", + OrigBidID: "bid-id-2", + DealID: "-1", + ServerSide: 1, + }, + }, + }, + }, + { + name: "slot_not_mapped_for_pubmatic", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 0, + }, + }, + Seat: "pubmatic", + }, + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-2", + ImpID: "imp1", + Price: 10, + }, + }, + Seat: "appnexus", + }, + }, + }, + SeatNonBid: []openrtb_ext.SeatNonBid{}, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidFloorCur: "USD", + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + }, + }, + "bid-id-2": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + OriginalBidCPM: 10, + OriginalBidCur: "USD", + }, + }, + }, + NonMapped: map[string]struct{}{ + "pubmatic": {}, + }, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + NetECPM: 10, + GrossECPM: 10, + OriginalCPM: 10, + OriginalCur: "USD", + PartnerID: "appnexus", + BidderCode: "appnexus", + PartnerSize: "0x0", + BidID: "bid-id-2", + OrigBidID: "bid-id-2", + DealID: "-1", + ServerSide: 1, + }, + }, + }, + }, { name: "partner throttled, default bid present is seat-bid but absent in seat-non-bid", args: args{ @@ -1287,6 +1501,7 @@ func TestGetPartnerRecordsByImpForDefaultBids(t *testing.T) { } } func TestGetPartnerRecordsByImpForSeatNonBid(t *testing.T) { + pg, _ := openrtb_ext.NewPriceGranularityFromLegacyID("med") type args struct { ao analytics.AuctionObject rCtx *models.RequestCtx @@ -1331,17 +1546,411 @@ func TestGetPartnerRecordsByImpForSeatNonBid(t *testing.T) { }, }, }, - rCtx: &models.RequestCtx{ - ImpBidCtx: make(map[string]models.ImpCtx), + rCtx: &models.RequestCtx{ + ImpBidCtx: make(map[string]models.ImpCtx), + }, + }, + partners: map[string][]PartnerRecord{}, + }, + { + name: "log rejected non-bid", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{}, + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "appnexus", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: int(exchange.ResponseRejectedBelowFloor), + Ext: openrtb_ext.ExtNonBid{ + Prebid: openrtb_ext.ExtNonBidPrebid{ + Bid: openrtb_ext.ExtNonBidPrebidBid{ + Price: 10, + ID: "bid-id-1", + W: 10, + H: 50, + OriginalBidCPM: 10, + }, + }, + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + Bidders: map[string]models.PartnerData{ + "appnexus": { + PartnerID: 1, + PrebidBidderCode: "appnexus", + KGP: "kgp", + KGPV: "kgpv", + }, + }, + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + Nbr: exchange.ResponseRejectedBelowFloor.Ptr(), + }, + }, + }, + BidFloor: 10.5, + BidFloorCur: "USD", + }, + }, + PartnerConfigMap: map[int]map[string]string{ + 1: { + "rev_share": "0", + }, + }, + WinningBids: make(models.WinningBids), + Platform: models.PLATFORM_APP, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "appnexus", + BidderCode: "appnexus", + KGPV: "kgpv", + KGPSV: "kgpv", + PartnerSize: "10x50", + GrossECPM: 10, + NetECPM: 10, + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCPM: 0, + OriginalCur: models.USD, + FloorValue: 10.5, + FloorRuleValue: 10.5, + Nbr: exchange.ResponseRejectedBelowFloor.Ptr(), + }, + }, + }, + }, + { + name: "log rejected non-bid having bidder_response_currency EUR and request_currency USD", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Cur: []string{models.USD}, + }, + }, + Response: &openrtb2.BidResponse{Cur: models.USD}, + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "appnexus", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: int(nbr.LossBidLostInVastUnwrap), + Ext: openrtb_ext.ExtNonBid{ + Prebid: openrtb_ext.ExtNonBidPrebid{ + Bid: openrtb_ext.ExtNonBidPrebidBid{ + Price: 10, + ID: "bid-id-1", + W: 10, + H: 50, + OriginalBidCur: "EUR", + }, + }, + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + PriceGranularity: &pg, + CurrencyConversion: func(from, to string, value float64) (float64, error) { + if from == "USD" && to == "EUR" { + return value * 1.2, nil + } + if from == "EUR" && to == "USD" { + return value * 0.8, nil + } + return 0, nil + }, + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + Bidders: map[string]models.PartnerData{ + "appnexus": { + PartnerID: 1, + PrebidBidderCode: "appnexus", + KGP: "kgp", + KGPV: "kgpv", + }, + }, + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + Nbr: ptrutil.ToPtr(nbr.LossBidLostInVastUnwrap), + }, + }, + }, + BidFloor: 10.5, + BidFloorCur: "USD", + }, + }, + PartnerConfigMap: map[int]map[string]string{ + 1: { + "rev_share": "0", + }, + }, + WinningBids: make(models.WinningBids), + Platform: models.PLATFORM_APP, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "appnexus", + BidderCode: "appnexus", + KGPV: "kgpv", + KGPSV: "kgpv", + PartnerSize: "10x50", + GrossECPM: 8, + NetECPM: 8, + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCPM: 10, + OriginalCur: "EUR", + FloorValue: 10.5, + FloorRuleValue: 10.5, + PriceBucket: "8.00", + Nbr: ptrutil.ToPtr(nbr.LossBidLostInVastUnwrap), + }, + }, + }, + }, + { + name: "log rejected non-bid having bidder_response_currency EUR and request_currency USD and having 50% revshare", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Cur: []string{models.USD}, + }, + }, + Response: &openrtb2.BidResponse{Cur: models.USD}, + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "appnexus", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: int(nbr.LossBidLostInVastUnwrap), + Ext: openrtb_ext.ExtNonBid{ + Prebid: openrtb_ext.ExtNonBidPrebid{ + Bid: openrtb_ext.ExtNonBidPrebidBid{ + Price: 10, + ID: "bid-id-1", + W: 10, + H: 50, + OriginalBidCur: "EUR", + }, + }, + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + PriceGranularity: &pg, + CurrencyConversion: func(from, to string, value float64) (float64, error) { + if from == "USD" && to == "EUR" { + return value * 1.2, nil + } + if from == "EUR" && to == "USD" { + return value * 0.8, nil + } + return 0, nil + }, + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + Bidders: map[string]models.PartnerData{ + "appnexus": { + PartnerID: 1, + PrebidBidderCode: "appnexus", + KGP: "kgp", + KGPV: "kgpv", + }, + }, + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + Nbr: ptrutil.ToPtr(nbr.LossBidLostInVastUnwrap), + }, + }, + }, + BidFloor: 10.5, + BidFloorCur: "USD", + }, + }, + PartnerConfigMap: map[int]map[string]string{ + 1: { + "rev_share": "50", + }, + }, + WinningBids: make(models.WinningBids), + Platform: models.PLATFORM_APP, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "appnexus", + BidderCode: "appnexus", + KGPV: "kgpv", + KGPSV: "kgpv", + PartnerSize: "10x50", + GrossECPM: 8, + NetECPM: 4, + PriceBucket: "4.00", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCPM: 10, + OriginalCur: "EUR", + FloorValue: 10.5, + FloorRuleValue: 10.5, + Nbr: ptrutil.ToPtr(nbr.LossBidLostInVastUnwrap), + }, + }, + }, + }, + { + name: "log rejected non-bid having response_currency USD and request_currency EUR and having 50% revshare", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Cur: []string{"EUR"}, + }, + }, + Response: &openrtb2.BidResponse{Cur: "EUR"}, + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "appnexus", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: int(nbr.LossBidLostInVastUnwrap), + Ext: openrtb_ext.ExtNonBid{ + Prebid: openrtb_ext.ExtNonBidPrebid{ + Bid: openrtb_ext.ExtNonBidPrebidBid{ + Price: 10, + ID: "bid-id-1", + W: 10, + H: 50, + OriginalBidCur: models.USD, + }, + }, + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + CurrencyConversion: func(from, to string, value float64) (float64, error) { + if from == "USD" && to == "EUR" { + return value * 1.2, nil + } + if from == "EUR" && to == "USD" { + return value * 0.8, nil + } + return value, nil + }, + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + Bidders: map[string]models.PartnerData{ + "appnexus": { + PartnerID: 1, + PrebidBidderCode: "appnexus", + KGP: "kgp", + KGPV: "kgpv", + }, + }, + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + Nbr: ptrutil.ToPtr(nbr.LossBidLostInVastUnwrap), + }, + }, + }, + BidFloor: 10.5, + BidFloorCur: "USD", + }, + }, + PartnerConfigMap: map[int]map[string]string{ + 1: { + "rev_share": "50", + }, + }, + WinningBids: make(models.WinningBids), + Platform: models.PLATFORM_APP, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "appnexus", + BidderCode: "appnexus", + KGPV: "kgpv", + KGPSV: "kgpv", + PartnerSize: "10x50", + GrossECPM: 10, + NetECPM: 5, + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCPM: 10, + OriginalCur: "USD", + FloorValue: 10.5, + FloorRuleValue: 10.5, + Nbr: ptrutil.ToPtr(nbr.LossBidLostInVastUnwrap), + }, }, }, - partners: map[string][]PartnerRecord{}, }, { - name: "log rejected non-bid", + name: "log from seat-non-bid and seat-bid for Endpoint webs2s: here default/proxy bids are present in seat non-bid", args: args{ ao: analytics.AuctionObject{ - Response: &openrtb2.BidResponse{}, + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Seat: "pubmatic", + Bid: []openrtb2.Bid{ + { + ID: "bid-id-2", + Price: 30, + W: 10, + H: 50, + ImpID: "imp1", + Ext: json.RawMessage(`{"origbidcpm":30}`), + }, + }, + }, + }, + }, SeatNonBid: []openrtb_ext.SeatNonBid{ { Seat: "appnexus", @@ -1366,6 +1975,16 @@ func TestGetPartnerRecordsByImpForSeatNonBid(t *testing.T) { }, }, rCtx: &models.RequestCtx{ + PriceGranularity: &pg, + CurrencyConversion: func(from, to string, value float64) (float64, error) { + if from == "USD" && to == "EUR" { + return value * 1.2, nil + } + if from == "EUR" && to == "USD" { + return value * 0.8, nil + } + return 0, nil + }, ImpBidCtx: map[string]models.ImpCtx{ "imp1": { Bidders: map[string]models.PartnerData{ @@ -1375,6 +1994,12 @@ func TestGetPartnerRecordsByImpForSeatNonBid(t *testing.T) { KGP: "kgp", KGPV: "kgpv", }, + "pubmatic": { + PartnerID: 2, + PrebidBidderCode: "pubmatic", + KGP: "kgp", + KGPV: "kgpv", + }, }, BidCtx: map[string]models.BidCtx{ "bid-id-1": { @@ -1415,8 +2040,27 @@ func TestGetPartnerRecordsByImpForSeatNonBid(t *testing.T) { OriginalCur: models.USD, FloorValue: 10.5, FloorRuleValue: 10.5, + PriceBucket: "10.00", Nbr: exchange.ResponseRejectedBelowFloor.Ptr(), }, + { + PartnerID: "pubmatic", + BidderCode: "pubmatic", + KGPV: "kgpv", + KGPSV: "kgpv", + PartnerSize: "10x50", + GrossECPM: 30, + NetECPM: 30, + BidID: "bid-id-2", + OrigBidID: "bid-id-2", + DealID: "-1", + ServerSide: 1, + OriginalCPM: 0, + OriginalCur: models.USD, + FloorValue: 10.5, + FloorRuleValue: 10.5, + PriceBucket: "20.00", + }, }, }, }, @@ -1461,6 +2105,7 @@ func TestGetPartnerRecordsByImpForSeatNonBidForFloors(t *testing.T) { FloorValue: 1, FloorCurrency: models.USD, }, + OriginalBidCPM: 10, }, }, }, @@ -1527,6 +2172,7 @@ func TestGetPartnerRecordsByImpForSeatNonBidForFloors(t *testing.T) { FloorValue: 0, FloorCurrency: models.USD, }, + OriginalBidCPM: 10, }, }, }, @@ -1593,6 +2239,7 @@ func TestGetPartnerRecordsByImpForSeatNonBidForFloors(t *testing.T) { FloorValue: 10, FloorCurrency: models.USD, }, + OriginalBidCPM: 10, }, }, }, @@ -1651,8 +2298,9 @@ func TestGetPartnerRecordsByImpForSeatNonBidForFloors(t *testing.T) { Ext: openrtb_ext.ExtNonBid{ Prebid: openrtb_ext.ExtNonBidPrebid{ Bid: openrtb_ext.ExtNonBidPrebidBid{ - Price: 10, - ID: "bid-id-1", + Price: 10, + ID: "bid-id-1", + OriginalBidCPM: 10, }, }, }, @@ -1711,8 +2359,9 @@ func TestGetPartnerRecordsByImpForSeatNonBidForFloors(t *testing.T) { Ext: openrtb_ext.ExtNonBid{ Prebid: openrtb_ext.ExtNonBidPrebid{ Bid: openrtb_ext.ExtNonBidPrebidBid{ - Price: 10, - ID: "bid-id-1", + Price: 10, + ID: "bid-id-1", + OriginalBidCPM: 10, }, }, }, @@ -1782,6 +2431,7 @@ func TestGetPartnerRecordsByImpForSeatNonBidForFloors(t *testing.T) { FloorValue: 1, FloorCurrency: "JPY", }, + OriginalBidCPM: 10, }, }, }, @@ -1988,6 +2638,7 @@ func TestGetPartnerRecordsByImpForBidIDCollisions(t *testing.T) { BidId: "uuid", }, }, + OriginalBidCPM: 10, }, }, }, @@ -2017,6 +2668,11 @@ func TestGetPartnerRecordsByImpForBidIDCollisions(t *testing.T) { name: "valid bid, but json unmarshal fails", args: args{ ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Cur: []string{}, + }, + }, Response: &openrtb2.BidResponse{ SeatBid: []openrtb2.SeatBid{ { @@ -2046,11 +2702,13 @@ func TestGetPartnerRecordsByImpForBidIDCollisions(t *testing.T) { BidId: "uuid", }, }, + OriginalBidCPM: 10, }, }, }, }, }, + CurrencyConversion: func(from, to string, value float64) (float64, error) { return 10, nil }, }, }, partners: map[string][]PartnerRecord{ @@ -2066,6 +2724,7 @@ func TestGetPartnerRecordsByImpForBidIDCollisions(t *testing.T) { OriginalCur: models.USD, NetECPM: 10, GrossECPM: 10, + OriginalCPM: 10, DealPriority: 0, }, }, @@ -2103,7 +2762,8 @@ func TestGetPartnerRecordsByImpForBidIDCollisions(t *testing.T) { DealTierSatisfied: true, }, }, - Nbr: nbr.LossBidLostToHigherBid.Ptr(), + OriginalBidCPM: 10, + Nbr: nbr.LossBidLostToHigherBid.Ptr(), }, }, }, @@ -2195,9 +2855,10 @@ func TestGetPartnerRecordsByImpForBidIDCollisions(t *testing.T) { Ext: openrtb_ext.ExtNonBid{ Prebid: openrtb_ext.ExtNonBidPrebid{ Bid: openrtb_ext.ExtNonBidPrebidBid{ - Price: 10, - ID: "bid-id-1", - BidId: "uuid", + Price: 10, + ID: "bid-id-1", + BidId: "uuid", + OriginalBidCPM: 10, }, }, }, @@ -2263,6 +2924,7 @@ func TestGetPartnerRecordsByImpForBidIDCollisions(t *testing.T) { BidId: "uuid", }, }, + OriginalBidCPM: 10, }, }, }, @@ -2318,6 +2980,9 @@ func TestGetPartnerRecordsByImpForBidExtFailure(t *testing.T) { name: "valid bid, but bid.ext is empty", args: args{ ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, Response: &openrtb2.BidResponse{ SeatBid: []openrtb2.SeatBid{ { @@ -2352,6 +3017,7 @@ func TestGetPartnerRecordsByImpForBidExtFailure(t *testing.T) { }, }, }, + CurrencyConversion: func(from, to string, value float64) (float64, error) { return 10, nil }, }, }, partners: map[string][]PartnerRecord{ @@ -2367,6 +3033,7 @@ func TestGetPartnerRecordsByImpForBidExtFailure(t *testing.T) { OriginalCur: models.USD, NetECPM: 10, GrossECPM: 10, + OriginalCPM: 10, DealPriority: 0, }, }, @@ -2376,6 +3043,9 @@ func TestGetPartnerRecordsByImpForBidExtFailure(t *testing.T) { name: "dropped bid, bidExt unmarshal fails", args: args{ ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, Response: &openrtb2.BidResponse{ SeatBid: []openrtb2.SeatBid{ { @@ -2410,6 +3080,7 @@ func TestGetPartnerRecordsByImpForBidExtFailure(t *testing.T) { }, }, }, + CurrencyConversion: func(from, to string, value float64) (float64, error) { return 10, nil }, }, }, partners: map[string][]PartnerRecord{ @@ -2425,6 +3096,7 @@ func TestGetPartnerRecordsByImpForBidExtFailure(t *testing.T) { OriginalCur: models.USD, NetECPM: 10, GrossECPM: 10, + OriginalCPM: 10, DealPriority: 0, Nbr: nil, }, @@ -3246,6 +3918,9 @@ func TestGetPartnerRecordsByImpForMarketPlaceBidders(t *testing.T) { name: "overwrite marketplace bid details", args: args{ ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, Response: &openrtb2.BidResponse{ SeatBid: []openrtb2.SeatBid{ { @@ -3294,6 +3969,7 @@ func TestGetPartnerRecordsByImpForMarketPlaceBidders(t *testing.T) { }, }, }, + CurrencyConversion: func(from, to string, value float64) (float64, error) { return value, nil }, }, }, partners: map[string][]PartnerRecord{ @@ -3309,6 +3985,7 @@ func TestGetPartnerRecordsByImpForMarketPlaceBidders(t *testing.T) { OriginalCur: models.USD, GrossECPM: 1, NetECPM: 1, + OriginalCPM: 1, KGPV: "apnx_kgpv", KGPSV: "apnx_kgpv", }, @@ -3323,6 +4000,7 @@ func TestGetPartnerRecordsByImpForMarketPlaceBidders(t *testing.T) { OriginalCur: models.USD, GrossECPM: 2, NetECPM: 2, + OriginalCPM: 2, KGPV: "pubm_kgpv", KGPSV: "pubm_kgpv", }, @@ -3337,6 +4015,7 @@ func TestGetPartnerRecordsByImpForMarketPlaceBidders(t *testing.T) { OriginalCur: models.USD, GrossECPM: 3, NetECPM: 3, + OriginalCPM: 3, KGPV: "pubm_kgpv", KGPSV: "pubm_kgpv", }, @@ -4600,129 +5279,129 @@ func TestSlotRecordsInGetLogAuctionObjectAsURL(t *testing.T) { args args want want }{ - // { - // name: "req.Imp not mapped in ImpBidCtx", - // args: args{ - // ao: analytics.AuctionObject{ - // RequestWrapper: &openrtb_ext.RequestWrapper{ - // BidRequest: &openrtb2.BidRequest{ - // Imp: []openrtb2.Imp{ - // { - // ID: "imp1", - // TagID: "tagid", - // }, - // }, - // }, - // }, - // Response: &openrtb2.BidResponse{}, - // }, - // rCtx: &models.RequestCtx{ - // Endpoint: models.EndpointV25, - // PubID: 5890, - // }, - // logInfo: false, - // forRespExt: true, - // }, - // want: want{ - // logger: ow.cfg.Endpoint + `?json={"pubid":5890,"pid":"0","pdvid":"0","sl":1,"dvc":{},"ft":0,"it":"sdk"}&pubid=5890`, - // header: http.Header{ - // models.USER_AGENT_HEADER: []string{""}, - // models.IP_HEADER: []string{""}, - // }, - // }, - // }, - // { - // name: "multi imps request", - // args: args{ - // ao: analytics.AuctionObject{ - // RequestWrapper: &openrtb_ext.RequestWrapper{ - // BidRequest: &openrtb2.BidRequest{ - // Imp: []openrtb2.Imp{ - // { - // ID: "imp_1", - // TagID: "tagid_1", - // }, - // { - // ID: "imp_2", - // TagID: "tagid_2", - // }, - // }, - // }, - // }, - // Response: &openrtb2.BidResponse{}, - // }, - // rCtx: &models.RequestCtx{ - // PubID: 5890, - // Endpoint: models.EndpointV25, - // ImpBidCtx: map[string]models.ImpCtx{ - // "imp_1": { - // SlotName: "imp_1_tagid_1", - // AdUnitName: "tagid_1", - // }, - // "imp_2": { - // AdUnitName: "tagid_2", - // SlotName: "imp_2_tagid_2", - // }, - // }, - // }, - // logInfo: false, - // forRespExt: true, - // }, - // want: want{ - // logger: ow.cfg.Endpoint + `?json={"pubid":5890,"pid":"0","pdvid":"0","sl":1,"s":[{"sid":"sid","sn":"imp_1_tagid_1","au":"tagid_1","ps":[]},{"sid":"sid","sn":"imp_2_tagid_2","au":"tagid_2","ps":[]}],"dvc":{},"ft":0,"it":"sdk"}&pubid=5890`, - // header: http.Header{ - // models.USER_AGENT_HEADER: []string{""}, - // models.IP_HEADER: []string{""}, - // }, - // }, - // }, - // { - // name: "multi imps request and one request has incomingslots", - // args: args{ - // ao: analytics.AuctionObject{ - // RequestWrapper: &openrtb_ext.RequestWrapper{ - // BidRequest: &openrtb2.BidRequest{ - // Imp: []openrtb2.Imp{ - // { - // ID: "imp_1", - // TagID: "tagid_1", - // }, - // { - // ID: "imp_2", - // TagID: "tagid_2", - // }, - // }, - // }, - // }, - // Response: &openrtb2.BidResponse{}, - // }, - // rCtx: &models.RequestCtx{ - // PubID: 5890, - // Endpoint: models.EndpointV25, - // ImpBidCtx: map[string]models.ImpCtx{ - // "imp_1": { - // IncomingSlots: []string{"0x0v", "100x200"}, - // IsRewardInventory: ptrutil.ToPtr(int8(1)), - // SlotName: "imp_1_tagid_1", - // AdUnitName: "tagid_1", - // }, - // "imp_2": { - // AdUnitName: "tagid_2", - // SlotName: "imp_2_tagid_2", - // }, - // }, - // }, - // logInfo: false, - // forRespExt: true, - // }, - // want: want{ - // logger: ow.cfg.Endpoint + `?json={"pubid":5890,"pid":"0","pdvid":"0","sl":1,"s":[{"sid":"sid","sn":"imp_1_tagid_1","sz":["0x0v","100x200"],"au":"tagid_1","ps":[],"rwrd":1},{"sid":"sid","sn":"imp_2_tagid_2","au":"tagid_2","ps":[]}],"dvc":{},"ft":0,"it":"sdk"}&pubid=5890`, - // header: http.Header{ - // models.USER_AGENT_HEADER: []string{""}, - // models.IP_HEADER: []string{""}, - // }, - // }, - // }, + { + name: "req.Imp not mapped in ImpBidCtx", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + { + ID: "imp1", + TagID: "tagid", + }, + }, + }, + }, + Response: &openrtb2.BidResponse{}, + }, + rCtx: &models.RequestCtx{ + Endpoint: models.EndpointV25, + PubID: 5890, + }, + logInfo: false, + forRespExt: true, + }, + want: want{ + logger: ow.cfg.Endpoint + `?json={"pubid":5890,"pid":"0","pdvid":"0","sl":1,"dvc":{},"ft":0,"it":"sdk"}&pubid=5890`, + header: http.Header{ + models.USER_AGENT_HEADER: []string{""}, + models.IP_HEADER: []string{""}, + }, + }, + }, + { + name: "multi imps request", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + { + ID: "imp_1", + TagID: "tagid_1", + }, + { + ID: "imp_2", + TagID: "tagid_2", + }, + }, + }, + }, + Response: &openrtb2.BidResponse{}, + }, + rCtx: &models.RequestCtx{ + PubID: 5890, + Endpoint: models.EndpointV25, + ImpBidCtx: map[string]models.ImpCtx{ + "imp_1": { + SlotName: "imp_1_tagid_1", + AdUnitName: "tagid_1", + }, + "imp_2": { + AdUnitName: "tagid_2", + SlotName: "imp_2_tagid_2", + }, + }, + }, + logInfo: false, + forRespExt: true, + }, + want: want{ + logger: ow.cfg.Endpoint + `?json={"pubid":5890,"pid":"0","pdvid":"0","sl":1,"s":[{"sid":"sid","sn":"imp_1_tagid_1","au":"tagid_1","ps":[]},{"sid":"sid","sn":"imp_2_tagid_2","au":"tagid_2","ps":[]}],"dvc":{},"ft":0,"it":"sdk"}&pubid=5890`, + header: http.Header{ + models.USER_AGENT_HEADER: []string{""}, + models.IP_HEADER: []string{""}, + }, + }, + }, + { + name: "multi imps request and one request has incomingslots", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + { + ID: "imp_1", + TagID: "tagid_1", + }, + { + ID: "imp_2", + TagID: "tagid_2", + }, + }, + }, + }, + Response: &openrtb2.BidResponse{}, + }, + rCtx: &models.RequestCtx{ + PubID: 5890, + Endpoint: models.EndpointV25, + ImpBidCtx: map[string]models.ImpCtx{ + "imp_1": { + IncomingSlots: []string{"0x0v", "100x200"}, + IsRewardInventory: ptrutil.ToPtr(int8(1)), + SlotName: "imp_1_tagid_1", + AdUnitName: "tagid_1", + }, + "imp_2": { + AdUnitName: "tagid_2", + SlotName: "imp_2_tagid_2", + }, + }, + }, + logInfo: false, + forRespExt: true, + }, + want: want{ + logger: ow.cfg.Endpoint + `?json={"pubid":5890,"pid":"0","pdvid":"0","sl":1,"s":[{"sid":"sid","sn":"imp_1_tagid_1","sz":["0x0v","100x200"],"au":"tagid_1","ps":[],"rwrd":1},{"sid":"sid","sn":"imp_2_tagid_2","au":"tagid_2","ps":[]}],"dvc":{},"ft":0,"it":"sdk"}&pubid=5890`, + header: http.Header{ + models.USER_AGENT_HEADER: []string{""}, + models.IP_HEADER: []string{""}, + }, + }, + }, { name: "multi imps request and one imp has partner record", args: args{ @@ -4943,3 +5622,97 @@ func Test_getFloorValueFromUpdatedRequest(t *testing.T) { }) } } + +func TestGetBidPriceAfterCurrencyConversion(t *testing.T) { + type args struct { + price float64 + requestCurrencies []string + responseCurrency string + currencyConverter func(fromCurrency string, toCurrency string, value float64) (float64, error) + } + tests := []struct { + name string + args args + want float64 + }{ + { + name: "Single request currency - successful conversion", + args: args{ + price: 100.0, + requestCurrencies: []string{"EUR"}, + responseCurrency: "USD", + currencyConverter: func(fromCurrency string, toCurrency string, value float64) (float64, error) { + if fromCurrency == "USD" && toCurrency == "EUR" { + return 85.0, nil // Assuming conversion rate USD to EUR is 0.85 + } + return 0, fmt.Errorf("unsupported conversion") + }, + }, + want: 85.0, + }, + { + name: "Multiple request currencies - first successful conversion", + args: args{ + price: 100.0, + requestCurrencies: []string{"EUR", "GBP"}, + responseCurrency: "USD", + currencyConverter: func(fromCurrency string, toCurrency string, value float64) (float64, error) { + if fromCurrency == "USD" && toCurrency == "EUR" { + return 85.0, nil // Successful conversion to EUR + } + return 0, fmt.Errorf("unsupported conversion") + }, + }, + want: 85.0, + }, + { + name: "Multiple request currencies - second successful conversion", + args: args{ + price: 100.0, + requestCurrencies: []string{"JPY", "GBP"}, + responseCurrency: "USD", + currencyConverter: func(fromCurrency string, toCurrency string, value float64) (float64, error) { + if fromCurrency == "USD" && toCurrency == "GBP" { + return 75.0, nil // Successful conversion to GBP + } + return 0, fmt.Errorf("unsupported conversion") + }, + }, + want: 75.0, + }, + { + name: "No request currencies provided - default to USD", + args: args{ + price: 100.0, + requestCurrencies: []string{}, + responseCurrency: "USD", + currencyConverter: func(fromCurrency string, toCurrency string, value float64) (float64, error) { + if fromCurrency == "USD" && toCurrency == "USD" { + return 100.0, nil // No conversion needed + } + return 0, fmt.Errorf("unsupported conversion") + }, + }, + want: 100.0, + }, + { + name: "Conversion fails for all currencies", + args: args{ + price: 100.0, + requestCurrencies: []string{"JPY", "CNY"}, + responseCurrency: "USD", + currencyConverter: func(fromCurrency string, toCurrency string, value float64) (float64, error) { + return 0, fmt.Errorf("conversion failed") + }, + }, + want: 0.0, // Default to 0 on failure + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := GetBidPriceAfterCurrencyConversion(tt.args.price, tt.args.requestCurrencies, tt.args.responseCurrency, tt.args.currencyConverter) + assert.Equal(t, tt.want, got, "mismatched price") + }) + } +} diff --git a/analytics/pubmatic/pubmatic.go b/analytics/pubmatic/pubmatic.go index 5e5e8a11a7d..3b6f20ce34d 100644 --- a/analytics/pubmatic/pubmatic.go +++ b/analytics/pubmatic/pubmatic.go @@ -5,6 +5,7 @@ import ( "sync" "github.com/golang/glog" + "github.com/prebid/openrtb/v20/openrtb2" "github.com/prebid/prebid-server/v2/analytics" "github.com/prebid/prebid-server/v2/analytics/pubmatic/mhttp" @@ -59,6 +60,12 @@ func (ow HTTPLogger) LogAuctionObject(ao *analytics.AuctionObject) { return } + var orignalMaxBidResponse *openrtb2.BidResponse + if rCtx.Endpoint == models.EndpointAppLovinMax { + orignalMaxBidResponse = new(openrtb2.BidResponse) + *orignalMaxBidResponse = *ao.Response + } + err := RestoreBidResponse(rCtx, *ao) if err != nil { glog.Error("Failed to restore bid response for pub:[%d], profile:[%d], version:[%d], err:[%s].", rCtx.PubID, rCtx.ProfileID, rCtx.VersionID, err.Error()) @@ -73,6 +80,9 @@ func (ow HTTPLogger) LogAuctionObject(ao *analytics.AuctionObject) { go send(rCtx, loggerURL, headers, mhttp.NewMultiHttpContext()) + if rCtx.Endpoint == models.EndpointAppLovinMax { + ao.Response = orignalMaxBidResponse + } setWakandaObject(rCtx, ao, loggerURL) } diff --git a/analytics/pubmatic/pubmatic_test.go b/analytics/pubmatic/pubmatic_test.go index 186c684e8e6..4ee47bf0615 100644 --- a/analytics/pubmatic/pubmatic_test.go +++ b/analytics/pubmatic/pubmatic_test.go @@ -2,14 +2,18 @@ package pubmatic import ( "encoding/json" + "net/http" "testing" "github.com/prebid/openrtb/v20/openrtb2" "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/analytics/pubmatic/mhttp" "github.com/prebid/prebid-server/v2/config" "github.com/prebid/prebid-server/v2/hooks/hookanalytics" "github.com/prebid/prebid-server/v2/hooks/hookexecution" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/wakanda" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -36,10 +40,16 @@ func TestNewHTTPLogger(t *testing.T) { // TestLogAuctionObject just increases code coverage, it does not validate anything func TestLogAuctionObject(t *testing.T) { + oldSend := send + send = func(rCtx *models.RequestCtx, url string, headers http.Header, mhc mhttp.MultiHttpContextInterface) {} + defer func() { + send = oldSend + }() tests := []struct { name string ao *analytics.AuctionObject RestoredResponse *openrtb2.BidResponse + wantWakanda wakanda.WakandaDebug }{ { name: "rctx is nil", @@ -106,7 +116,6 @@ func TestLogAuctionObject(t *testing.T) { }, }, }, - Response: &openrtb2.BidResponse{ ID: "123", BidID: "bid-id-1", @@ -175,9 +184,201 @@ func TestLogAuctionObject(t *testing.T) { }, }, }, + { + name: "AppLovinMax request, RestoreBidResponse for logger and wakanda enable", + ao: &analytics.AuctionObject{ + HookExecutionOutcome: []hookexecution.StageOutcome{ + { + Groups: []hookexecution.GroupOutcome{ + { + InvocationResults: []hookexecution.HookOutcome{ + { + AnalyticsTags: hookanalytics.Analytics{ + Activities: []hookanalytics.Activity{ + { + Results: []hookanalytics.Result{ + { + Values: map[string]interface{}{ + "request-ctx": &models.RequestCtx{ + Endpoint: models.EndpointAppLovinMax, + Debug: false, + PubID: 5890, + PubIDStr: "5890", + ProfileID: 1234, + ProfileIDStr: "1234", + WakandaDebug: &wakanda.Debug{ + Enabled: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + Response: &openrtb2.BidResponse{ + ID: "123", + BidID: "bid-id-1", + Cur: "USD", + SeatBid: []openrtb2.SeatBid{ + { + Seat: "pubmatic", + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp_1", + Ext: json.RawMessage(`{"signaldata":"{\"id\":\"123\",\"seatbid\":[{\"bid\":[{\"id\":\"bid-id-1\",\"impid\":\"imp_1\",\"price\":0}],\"seat\":\"pubmatic\"}],\"bidid\":\"bid-id-1\",\"cur\":\"USD\",\"ext\":{\"matchedimpression\":{\"appnexus\":50,\"pubmatic\":50}}}\r\n"}`), + }, + }, + }, + }, + }, + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + }, + RestoredResponse: &openrtb2.BidResponse{ + ID: "123", + BidID: "bid-id-1", + Cur: "USD", + SeatBid: []openrtb2.SeatBid{ + { + Seat: "pubmatic", + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp_1", + Ext: json.RawMessage(`{"signaldata":"{\"id\":\"123\",\"seatbid\":[{\"bid\":[{\"id\":\"bid-id-1\",\"impid\":\"imp_1\",\"price\":0}],\"seat\":\"pubmatic\"}],\"bidid\":\"bid-id-1\",\"cur\":\"USD\",\"ext\":{\"matchedimpression\":{\"appnexus\":50,\"pubmatic\":50}}}\r\n"}`), + }, + }, + }, + }, + }, + wantWakanda: &wakanda.Debug{ + Enabled: true, + FolderPaths: nil, + DebugLevel: 0, + DebugData: wakanda.DebugData{ + HTTPRequest: nil, + HTTPRequestBody: nil, + HTTPResponse: nil, + HTTPResponseBody: "{\"id\":\"123\",\"seatbid\":[{\"bid\":[{\"id\":\"bid-id-1\",\"impid\":\"imp_1\",\"price\":0,\"ext\":{\"signaldata\":\"{\\\"id\\\":\\\"123\\\",\\\"seatbid\\\":[{\\\"bid\\\":[{\\\"id\\\":\\\"bid-id-1\\\",\\\"impid\\\":\\\"imp_1\\\",\\\"price\\\":0}],\\\"seat\\\":\\\"pubmatic\\\"}],\\\"bidid\\\":\\\"bid-id-1\\\",\\\"cur\\\":\\\"USD\\\",\\\"ext\\\":{\\\"matchedimpression\\\":{\\\"appnexus\\\":50,\\\"pubmatic\\\":50}}}\\r\\n\"}}],\"seat\":\"pubmatic\"}],\"bidid\":\"bid-id-1\",\"cur\":\"USD\"}", + PrebidHTTPRequest: nil, + PrebidRequestBody: nil, + PrebidHTTPResponse: nil, + OpenRTB: &openrtb2.BidRequest{}, + WinningBid: false, + Logger: json.RawMessage(`{"pubid":5890,"pid":"1234","pdvid":"0","sl":1,"dvc":{},"ft":0,"it":"sdk"}`), + }, + Config: wakanda.Wakanda{ + SFTP: wakanda.SFTP{ + User: "", + Password: "", + ServerIP: "", + Destination: "", + }, + HostName: "", + DCName: "", + PodName: "", + MaxDurationInMin: 0, + CleanupFrequencyInMin: 0, + }, + }, + }, + { + name: "AppLovinMax request, RestoreBidResponse for logger and wakanda disable", + ao: &analytics.AuctionObject{ + HookExecutionOutcome: []hookexecution.StageOutcome{ + { + Groups: []hookexecution.GroupOutcome{ + { + InvocationResults: []hookexecution.HookOutcome{ + { + AnalyticsTags: hookanalytics.Analytics{ + Activities: []hookanalytics.Activity{ + { + Results: []hookanalytics.Result{ + { + Values: map[string]interface{}{ + "request-ctx": &models.RequestCtx{ + Endpoint: models.EndpointAppLovinMax, + Debug: false, + PubID: 5890, + PubIDStr: "5890", + ProfileID: 1234, + ProfileIDStr: "1234", + WakandaDebug: &wakanda.Debug{ + Enabled: false, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + Response: &openrtb2.BidResponse{ + ID: "123", + BidID: "bid-id-1", + Cur: "USD", + SeatBid: []openrtb2.SeatBid{ + { + Seat: "pubmatic", + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp_1", + Ext: json.RawMessage(`{"signaldata":"{\"id\":\"123\",\"seatbid\":[{\"bid\":[{\"id\":\"bid-id-1\",\"impid\":\"imp_1\",\"price\":0}],\"seat\":\"pubmatic\"}],\"bidid\":\"bid-id-1\",\"cur\":\"USD\",\"ext\":{\"matchedimpression\":{\"appnexus\":50,\"pubmatic\":50}}}\r\n"}`), + }, + }, + }, + }, + }, + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + }, + RestoredResponse: &openrtb2.BidResponse{ + ID: "123", + BidID: "bid-id-1", + Cur: "USD", + SeatBid: []openrtb2.SeatBid{ + { + Seat: "pubmatic", + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp_1", + Ext: json.RawMessage(`{"signaldata":"{\"id\":\"123\",\"seatbid\":[{\"bid\":[{\"id\":\"bid-id-1\",\"impid\":\"imp_1\",\"price\":0}],\"seat\":\"pubmatic\"}],\"bidid\":\"bid-id-1\",\"cur\":\"USD\",\"ext\":{\"matchedimpression\":{\"appnexus\":50,\"pubmatic\":50}}}\r\n"}`), + }, + }, + }, + }, + }, + wantWakanda: &wakanda.Debug{}, + }, } for _, tt := range tests { HTTPLogger{}.LogAuctionObject(tt.ao) assert.Equal(t, tt.RestoredResponse, tt.ao.Response, tt.name) + var rctx *models.RequestCtx + if tt.ao != nil && tt.ao.HookExecutionOutcome != nil { + rctx = tt.ao.HookExecutionOutcome[0].Groups[0].InvocationResults[0].AnalyticsTags.Activities[0].Results[0].Values["request-ctx"].(*models.RequestCtx) + } + if rctx != nil { + assert.Equal(t, rctx.WakandaDebug, tt.wantWakanda) + } } } diff --git a/analytics/pubmatic/record.go b/analytics/pubmatic/record.go index bf36382f47d..ebdd1a5f9d3 100644 --- a/analytics/pubmatic/record.go +++ b/analytics/pubmatic/record.go @@ -59,6 +59,8 @@ type Device struct { Platform models.DevicePlatform `json:"plt,omitempty"` IFAType *models.DeviceIFAType `json:"ifty,omitempty"` //OTT-416, adding device.ext.ifa_type ATTS *float64 `json:"atts,omitempty"` //device.ext.atts + ID string `json:"id,omitempty"` + Model string `json:"md,omitempty"` } /* @@ -203,6 +205,8 @@ func (wlog *WloggerRecord) logDeviceObject(dvc *models.DeviceCtx) { wlog.Device.Platform = dvc.Platform wlog.Device.IFAType = dvc.IFATypeID + wlog.Device.ID = dvc.ID + wlog.Device.Model = dvc.Model if dvc.Ext != nil { wlog.record.Device.ATTS, _ = dvc.Ext.GetAtts() } diff --git a/analytics/pubmatic/record_test.go b/analytics/pubmatic/record_test.go index 57f8d20f66c..da123ac9942 100644 --- a/analytics/pubmatic/record_test.go +++ b/analytics/pubmatic/record_test.go @@ -434,6 +434,8 @@ func TestLogDeviceObject(t *testing.T) { dvc: &models.DeviceCtx{ Platform: models.DevicePlatformDesktop, IFATypeID: ptrutil.ToPtr(models.DeviceIFATypeID[models.DeviceIFATypeDPID]), + ID: "test_device_id", + Model: "iphone,19", Ext: func() *models.ExtDevice { extDevice := models.ExtDevice{} extDevice.UnmarshalJSON([]byte(`{"atts":0}`)) @@ -445,6 +447,8 @@ func TestLogDeviceObject(t *testing.T) { Platform: models.DevicePlatformDesktop, IFAType: ptrutil.ToPtr(models.DeviceIFATypeID[models.DeviceIFATypeDPID]), ATTS: ptrutil.ToPtr(float64(openrtb_ext.IOSAppTrackingStatusNotDetermined)), + ID: "test_device_id", + Model: "iphone,19", }, }, } diff --git a/endpoints/events/test/ow_failed.txt b/endpoints/events/test/base64_quoted_vast.txt similarity index 100% rename from endpoints/events/test/ow_failed.txt rename to endpoints/events/test/base64_quoted_vast.txt diff --git a/endpoints/events/test/base64_vast.txt b/endpoints/events/test/base64_vast.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/endpoints/events/test/raw_vast.txt b/endpoints/events/test/raw_vast.txt index 2bd4a7e0d79..f245a2fdd0a 100644 --- a/endpoints/events/test/raw_vast.txt +++ b/endpoints/events/test/raw_vast.txt @@ -98,4 +98,5 @@ creative_url RhythmXchange Adserver ebdr adnxs -adnxs \ No newline at end of file +adnxs +Yahoo Ad Manager Plus [ini:PDC][fmt:Video][crs:3682][csz:15s] VAST 2.0 Linear Ad00:00:15 \ No newline at end of file diff --git a/endpoints/events/vtrack_ow.go b/endpoints/events/vtrack_ow.go index 28b35eb8b0c..abcd86bac4e 100644 --- a/endpoints/events/vtrack_ow.go +++ b/endpoints/events/vtrack_ow.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/base64" "errors" + "regexp" "strings" "time" @@ -16,6 +17,7 @@ import ( var ( errEventURLNotConfigured = errors.New("event urls not configured") + tmpWSRemoverRegex = regexp.MustCompile(`>\s+<`) ) // InjectVideoEventTrackers injects the video tracking events @@ -52,16 +54,18 @@ func InjectVideoEventTrackers( etreeParserTime := time.Since(_startTime) if fastXMLExperiment && err == nil { + etreeXMLResponse := response + _startTime = time.Now() fastXMLResponse, _ := injectVideoEventsFastXML(vastXML, eventURLMap, nurlPresent, imp.Video.Linearity) fastXMLParserTime := time.Since(_startTime) //temporary if fastXMLResponse != vastXML { - fastXMLResponse = tmpFastXMLProcessing(fastXMLResponse) + fastXMLResponse, etreeXMLResponse = tmpFastXMLProcessing(fastXMLResponse, response) } - isResponseMismatch := (response != fastXMLResponse) + isResponseMismatch := (etreeXMLResponse != fastXMLResponse) if isResponseMismatch { openrtb_ext.FastXMLLogf("\n[XML_PARSER_TEST] method:[vcr] creative:[%s]", base64.StdEncoding.EncodeToString([]byte(vastXML))) @@ -77,17 +81,6 @@ func InjectVideoEventTrackers( return response, metrics, err } -func tmpFastXMLProcessing(vast string) string { - //replace only if trackers are injected - vast = strings.ReplaceAll(vast, " >", ">") - // if strings.Contains(vast, "'") { - // if index := strings.Index(vast, "<") //step2: remove inbetween whitespaces + fastXML = strings.ReplaceAll(fastXML, " ><", "><") //step3: remove attribute endtag whitespace (this should be always before step2) + fastXML = strings.ReplaceAll(fastXML, "'", "\"") //step4: convert single quote to double quote + + etreeXML = tmpWSRemoverRegex.ReplaceAllString(etreeXML, "><") //step2: remove inbetween whitespaces + etreeXML = strings.ReplaceAll(etreeXML, " ><", "><") //step3: remove attribute endtag whitespace (this should be always before step2) + etreeXML = strings.ReplaceAll(etreeXML, "'", "\"") + return fastXML, etreeXML +} diff --git a/endpoints/events/vtrack_ow_test.go b/endpoints/events/vtrack_ow_test.go index afa7d95e0ec..d984c568045 100644 --- a/endpoints/events/vtrack_ow_test.go +++ b/endpoints/events/vtrack_ow_test.go @@ -10,12 +10,36 @@ import ( "strings" "testing" + "github.com/beevik/etree" "github.com/prebid/openrtb/v20/adcom1" "github.com/prebid/openrtb/v20/openrtb2" "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) +func search(arr []int, value int) bool { + idx := sort.SearchInts(arr, value) + return idx < len(arr) && arr[idx] == value +} + +func quoteUnescape[T []byte | string](s T) string { + buf := bytes.Buffer{} + for i := 0; i < len(s); i++ { + ch := s[i] + if ch == '\\' { + if i+1 < len(s) { + nextCh := s[i+1] + if nextCh == '\\' || nextCh == '"' || nextCh == '\'' { + i++ + ch = nextCh + } + } + } + buf.WriteByte(ch) + } + return buf.String() +} + func TestInjectVideoEventTrackers(t *testing.T) { type args struct { externalURL string @@ -431,115 +455,119 @@ func TestInjectVideoEventTrackers(t *testing.T) { } } -func quoteUnescape[T []byte | string](s T) string { - buf := bytes.Buffer{} - for i := 0; i < len(s); i++ { - ch := s[i] - if ch == '\\' { - if i+1 < len(s) { - nextCh := s[i+1] - if nextCh == '\\' || nextCh == '"' || nextCh == '\'' { - i++ - ch = nextCh - } - } - } - buf.WriteByte(ch) +func TestETreeBehaviour(t *testing.T) { + // vast1 := `Appreciate00:00:30` + tests := []struct { + name string + in string + out string + }{ + { + name: "test", + in: " [ini:PDC][fmt:Video][crs:3682][csz:15s] ", + out: "", + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + doc := etree.NewDocument() + doc.WriteSettings.CanonicalEndTags = true + + err := doc.ReadFromString(tt.in) + assert.Nil(t, err) + + out, err := doc.WriteToString() + assert.Nil(t, err) + assert.Equal(t, tt.out, out) + }) } - return buf.String() } func TestCompareXMLParsers(t *testing.T) { - fileName := `./test/raw_vast.txt` - //fileName = `../../base64_vast.txt` + //$ cat *-prod.txt | sed -n 's/.*creative:\[\(.*\)\].*/\1/p' > $GOPATH/src/github.com/PubMatic-OpenWrap/prebid-server/endpoints/events/test/base64_vast.txt + type stats struct { + valid []int + generalMismatch []int + singleQuote []int + } - base64Decode := strings.Contains(fileName, "base64") + var ( + //fileName = `./test/base64_vast.txt` + //fileName = `./test/base64_quoted_vast.txt` + fileName = `./test/raw_vast.txt` + quoted = strings.Contains(fileName, "quoted") //xml files retrived from prod vast unwrapper + base64Decode = strings.Contains(fileName, "base64") + debugLines = []int{} + st = stats{} + currentLine, xmlCount = 0, 0 + ) file, err := os.Open(fileName) if !assert.Nil(t, err) { return } - defer file.Close() - var mismatched, debugLines []int - line := 0 + scanner := bufio.NewScanner(file) scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024) - - //debugLines = []int{19, 24, 25, 29, 58, 80, 83, 84, 86, 93, 128, 147, 151, 155, 159, 168, 184, 190, 199, 200, 225, 226, 243, 249, 254, 261, 272, 281, 291, 298, 310, 312, 320, 323, 328, 340, 350, 358, 362, 373, 376, 384} sort.Ints(debugLines) for scanner.Scan() { - line++ + currentLine++ vast := scanner.Text() - if len(debugLines) > 0 && sort.SearchInts(debugLines, line) == len(debugLines) { - continue - } - if base64Decode { - data, err := base64.StdEncoding.DecodeString(vast) - if !assert.Nil(t, err) { - continue + //presetup + { + //debug + if len(debugLines) > 0 { + if found := search(debugLines, currentLine); !found { + continue + } + } + + //base64decode + if base64Decode { + data, err := base64.StdEncoding.DecodeString(vast) + if !assert.Nil(t, err) { + continue + } + vast = string(data) + if quoted { + vast = quoteUnescape(data) + } } - vast = quoteUnescape(data) } - t.Run(fmt.Sprintf("vast_%d", line), func(t *testing.T) { + + t.Run(fmt.Sprintf("vast_%d", currentLine), func(t *testing.T) { + xmlCount++ + etreeXML, _ := injectVideoEventsETree(vast, eventURLMap, false, adcom1.LinearityLinear) fastXML, _ := injectVideoEventsFastXML(vast, eventURLMap, false, adcom1.LinearityLinear) + if vast != fastXML { - fastXML = tmpFastXMLProcessing(fastXML) + fastXML, etreeXML = tmpFastXMLProcessing(fastXML, etreeXML) } - if !assert.Equal(t, etreeXML, fastXML) { - mismatched = append(mismatched, line) + if len(debugLines) > 0 { + assert.Equal(t, etreeXML, fastXML, vast) } - }) - } - t.Logf("\ntotal:[%v] mismatched:[%v] lines:[%v]", line, len(mismatched), mismatched) - assert.Equal(t, 0, len(mismatched)) - assert.Nil(t, scanner.Err()) -} - -func TestBase64(t *testing.T) { - fileName := `./test/ow_failed.txt` - file, err := os.Open(fileName) - if !assert.Nil(t, err) { - return + if etreeXML != fastXML { + if idx := strings.Index(etreeXML, "'"); idx != -1 && + (strings.HasPrefix(fastXML[idx:], "'") || strings.HasPrefix(fastXML[idx:], "\"")) { + st.singleQuote = append(st.singleQuote, currentLine) + } else { + st.generalMismatch = append(st.generalMismatch, currentLine) + } + return + } + st.valid = append(st.valid, currentLine) + }) } - defer file.Close() - var mismatched, errored, debugLines []int - var line, singleQuotePresent, maxLength int - - maxLength = 14884 - scanner := bufio.NewScanner(file) - scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024) - - //debugLines = []int{19, 24, 25, 29, 58, 80, 83, 84, 86, 93, 128, 147, 151, 155, 159, 168, 184, 190, 199, 200, 225, 226, 243, 249, 254, 261, 272, 281, 291, 298, 310, 312, 320, 323, 328, 340, 350, 358, 362, 373, 376, 384} - sort.Ints(debugLines) - - for scanner.Scan() { - line++ - value := scanner.Text() - - if len(debugLines) > 0 && sort.SearchInts(debugLines, line) == len(debugLines) { - continue - } - - vast, err := base64.RawStdEncoding.DecodeString(value[0:maxLength]) - - if err != nil { - errored = append(errored, line) - continue - } - - if bytes.Contains(vast, []byte("'")) { - singleQuotePresent++ - } else { - mismatched = append(mismatched, line) - } - } - assert.Empty(t, mismatched) - assert.Empty(t, errored) + t.Logf("\nTotal:[%v] validCount:[%v] generalMismatch:[%v] singleQuote:[%v]", xmlCount, st.valid, st.generalMismatch, st.singleQuote) + assert.NotZero(t, xmlCount) + assert.Equal(t, xmlCount, len(st.valid), "validXMLCount") + assert.Equal(t, 0, len(st.generalMismatch), "generalMismatch") + assert.Equal(t, 0, len(st.singleQuote), "singleQuote") + assert.Nil(t, scanner.Err()) } diff --git a/endpoints/openrtb2/auction_ow.go b/endpoints/openrtb2/auction_ow.go index 30747eb98a5..69bb24d47e9 100644 --- a/endpoints/openrtb2/auction_ow.go +++ b/endpoints/openrtb2/auction_ow.go @@ -57,7 +57,7 @@ func UpdateResponseExtOW(w http.ResponseWriter, bidResponse *openrtb2.BidRespons } //Send owlogger in response only in case of debug mode - if rCtx.Debug { + if rCtx.Debug && !rCtx.LoggerDisabled { var orignalMaxBidResponse *openrtb2.BidResponse if rCtx.Endpoint == models.EndpointAppLovinMax { orignalMaxBidResponse = new(openrtb2.BidResponse) @@ -65,17 +65,15 @@ func UpdateResponseExtOW(w http.ResponseWriter, bidResponse *openrtb2.BidRespons pubmatic.RestoreBidResponse(rCtx, ao) } - if !rCtx.LoggerDisabled { - owlogger, _ := pubmatic.GetLogAuctionObjectAsURL(ao, rCtx, false, true) - if rCtx.Endpoint == models.EndpointAppLovinMax { - *bidResponse = *orignalMaxBidResponse - } - if len(bidResponse.Ext) == 0 { - bidResponse.Ext = []byte("{}") - } - if updatedExt, err := jsonparser.Set([]byte(bidResponse.Ext), []byte(strconv.Quote(owlogger)), "owlogger"); err == nil { - bidResponse.Ext = updatedExt - } + owlogger, _ := pubmatic.GetLogAuctionObjectAsURL(ao, rCtx, false, true) + if rCtx.Endpoint == models.EndpointAppLovinMax { + *bidResponse = *orignalMaxBidResponse + } + if len(bidResponse.Ext) == 0 { + bidResponse.Ext = []byte("{}") + } + if updatedExt, err := jsonparser.Set([]byte(bidResponse.Ext), []byte(strconv.Quote(owlogger)), "owlogger"); err == nil { + bidResponse.Ext = updatedExt } } else if rCtx.Endpoint == models.EndpointAppLovinMax { bidResponse.Ext = nil diff --git a/errortypes/code.go b/errortypes/code.go index b6f4aa33728..7a4017f36dc 100644 --- a/errortypes/code.go +++ b/errortypes/code.go @@ -41,6 +41,7 @@ const ( SecCookieDeprecationLenWarningCode SecBrowsingTopicsWarningCode AdpodPostFilteringWarningCode + InvalidVastVersionWarningCode ) // Coder provides an error or warning code with severity. diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 46191fc3f09..cc1ece2a43b 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -1,6 +1,7 @@ package exchange import ( + "github.com/PubMatic-OpenWrap/prebid-server/v2/adapters/displayio" "github.com/prebid/prebid-server/v2/adapters" ttx "github.com/prebid/prebid-server/v2/adapters/33across" "github.com/prebid/prebid-server/v2/adapters/aax" @@ -290,6 +291,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderDeepintent: deepintent.Builder, openrtb_ext.BidderDefinemedia: definemedia.Builder, openrtb_ext.BidderDianomi: dianomi.Builder, + openrtb_ext.BidderDisplayio: displayio.Builder, openrtb_ext.BidderEdge226: edge226.Builder, openrtb_ext.BidderDmx: dmx.Builder, openrtb_ext.BidderDXKulture: dxkulture.Builder, diff --git a/exchange/exchange.go b/exchange/exchange.go index 29ed299a87d..3aba94ab8c7 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -14,6 +14,7 @@ import ( "strings" "time" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/nbr" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/utils" "github.com/prebid/prebid-server/v2/privacy" @@ -425,6 +426,11 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog recordBids(ctx, e.me, r.PubID, adapterBids) recordVastVersion(e.me, adapterBids) + if requestExtPrebid.StrictVastMode { + validationErrs := filterBidsByVastVersion(adapterBids, &seatNonBid) + errs = append(errs, validationErrs...) + } + if e.priceFloorEnabled { var rejectedBids []*entities.PbsOrtbSeatBid var enforceErrs []error @@ -1415,7 +1421,7 @@ func (e *exchange) makeBid(bids []*entities.PbsOrtbBid, auc *auction, returnCrea } bidResponseExt.Warnings[adapter] = append(bidResponseExt.Warnings[adapter], dsaMessage) nonBidParams := entities.GetNonBidParamsFromPbsOrtbBid(bid, adapter.String()) - nonBidParams.NonBidReason = int(ResponseRejectedGeneral) + nonBidParams.NonBidReason = int(nbr.ResponseRejectedDSA) seatNonBids.AddBid(openrtb_ext.NewNonBid(nonBidParams), adapter.String()) continue // Don't add bid to result } diff --git a/exchange/exchange_ow.go b/exchange/exchange_ow.go index fc9c52e9d72..f8b1b449983 100644 --- a/exchange/exchange_ow.go +++ b/exchange/exchange_ow.go @@ -7,6 +7,7 @@ import ( "fmt" "net/url" "regexp" + "strconv" "strings" "github.com/golang/glog" @@ -14,9 +15,11 @@ import ( "github.com/prebid/prebid-server/v2/adapters" "github.com/prebid/prebid-server/v2/config" "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/errortypes" "github.com/prebid/prebid-server/v2/exchange/entities" "github.com/prebid/prebid-server/v2/metrics" pubmaticstats "github.com/prebid/prebid-server/v2/metrics/pubmatic_stats" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/nbr" "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/prebid/prebid-server/v2/ortb" "github.com/prebid/prebid-server/v2/util/jsonutil" @@ -36,6 +39,11 @@ const ( VASTTypeInLineEndTag = "" ) +var validVastVersions = map[int]bool{ + 3: true, + 4: true, +} + // VASTTagType describes the allowed values for VASTTagType type VASTTagType string @@ -378,3 +386,48 @@ func (e exchange) updateSeatNonBidsPriceThreshold(seatNonBids *openrtb_ext.NonBi } } } + +func updateSeatNonBidsInvalidVastVersion(seatNonBids *openrtb_ext.NonBidCollection, seat string, rejectedBids []*entities.PbsOrtbBid) { + for _, pbsRejBid := range rejectedBids { + nonBidParams := entities.GetNonBidParamsFromPbsOrtbBid(pbsRejBid, seat) + nonBidParams.NonBidReason = int(nbr.LossBidLostInVastVersionValidation) + seatNonBids.AddBid(openrtb_ext.NewNonBid(nonBidParams), seat) + } +} + +func filterBidsByVastVersion(adapterBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, seatNonBid *openrtb_ext.NonBidCollection) []error { + errs := []error{} + for _, seatBid := range adapterBids { + rejectedBid := []*entities.PbsOrtbBid{} + validBids := make([]*entities.PbsOrtbBid, 0, len(seatBid.Bids)) + for _, pbsBid := range seatBid.Bids { + if pbsBid.BidType == openrtb_ext.BidTypeVideo && pbsBid.Bid.AdM != "" { + isValid, vastVersion := validateVastVersion(pbsBid.Bid.AdM) + if !isValid { + errs = append(errs, &errortypes.Warning{ + Message: fmt.Sprintf("%s Bid %s was filtered for Imp %s with Vast Version %s: Incompatible with GAM unwinding requirements", seatBid.Seat, pbsBid.Bid.ID, pbsBid.Bid.ImpID, vastVersion), + WarningCode: errortypes.InvalidVastVersionWarningCode, + }) + rejectedBid = append(rejectedBid, pbsBid) + continue + } + } + validBids = append(validBids, pbsBid) + } + updateSeatNonBidsInvalidVastVersion(seatNonBid, seatBid.Seat, rejectedBid) + seatBid.Bids = validBids + } + return errs +} + +func validateVastVersion(adM string) (bool, string) { + matches := vastVersionRegex.FindStringSubmatch(adM) + if len(matches) != 2 { + return false, "" + } + vastVersionFloat, err := strconv.ParseFloat(matches[1], 64) + if err != nil { + return false, matches[1] + } + return validVastVersions[int(vastVersionFloat)], matches[1] +} diff --git a/exchange/exchange_ow_test.go b/exchange/exchange_ow_test.go index 6a4337aad3c..2c3243bbbf4 100644 --- a/exchange/exchange_ow_test.go +++ b/exchange/exchange_ow_test.go @@ -15,9 +15,11 @@ import ( "github.com/prebid/prebid-server/v2/adapters" "github.com/prebid/prebid-server/v2/adapters/vastbidder" "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" "github.com/prebid/prebid-server/v2/exchange/entities" "github.com/prebid/prebid-server/v2/metrics" metricsConf "github.com/prebid/prebid-server/v2/metrics/config" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/nbr" "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/stretchr/testify/assert" @@ -1875,3 +1877,652 @@ func TestRecordFastXMLMetrics(t *testing.T) { }) } } + +func TestFilterBidsByVastVersion(t *testing.T) { + type args struct { + adapterBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid + seatNonBid *openrtb_ext.NonBidCollection + } + tests := []struct { + name string + args args + want map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid + errs []error + }{ + { + name: "valid_vast_version_banner", + args: args{ + adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "bidder1": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "bid1", + AdM: ``, + }, + BidType: openrtb_ext.BidTypeBanner, + }, + }, + Seat: "bidder1", + }, + }, + seatNonBid: &openrtb_ext.NonBidCollection{}, + }, + want: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "bidder1": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "bid1", + AdM: ``, + }, + BidType: openrtb_ext.BidTypeBanner, + }, + }, + Seat: "bidder1", + }, + }, + errs: []error{}, + }, + { + name: "invalid_vast_version_banner", + args: args{ + adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "bidder1": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "bid1", + AdM: ``, + }, + BidType: openrtb_ext.BidTypeBanner, + }, + }, + Seat: "bidder1", + }, + }, + seatNonBid: &openrtb_ext.NonBidCollection{}, + }, + want: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "bidder1": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "bid1", + AdM: ``, + }, + BidType: openrtb_ext.BidTypeBanner, + }, + }, + Seat: "bidder1", + }, + }, + errs: []error{}, + }, + { + name: "valid_vast_version_video", + args: args{ + adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "bidder1": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "bid1", + AdM: ``, + }, + BidType: openrtb_ext.BidTypeVideo, + }, + }, + Seat: "bidder1", + }, + }, + seatNonBid: &openrtb_ext.NonBidCollection{}, + }, + want: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "bidder1": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "bid1", + AdM: ``, + }, + BidType: openrtb_ext.BidTypeVideo, + }, + }, + Seat: "bidder1", + }, + }, + errs: []error{}, + }, + { + name: "multiple_bids_valid_vast_version", + args: args{ + adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "bidder1": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "bid1", + AdM: ``, + }, + BidType: openrtb_ext.BidTypeVideo, + }, + { + Bid: &openrtb2.Bid{ + ID: "bid2", + AdM: ``, + }, + BidType: openrtb_ext.BidTypeVideo, + }, + }, + Seat: "bidder1", + }, + "bidder2": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "bid1", + AdM: ``, + }, + BidType: openrtb_ext.BidTypeVideo, + }, + { + Bid: &openrtb2.Bid{ + ID: "bid2", + AdM: ``, + }, + BidType: openrtb_ext.BidTypeVideo, + }, + }, + Seat: "bidder2", + }, + }, + seatNonBid: &openrtb_ext.NonBidCollection{}, + }, + want: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "bidder1": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "bid1", + AdM: ``, + }, + BidType: openrtb_ext.BidTypeVideo, + }, + { + Bid: &openrtb2.Bid{ + ID: "bid2", + AdM: ``, + }, + BidType: openrtb_ext.BidTypeVideo, + }, + }, + Seat: "bidder1", + }, + "bidder2": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "bid1", + AdM: ``, + }, + BidType: openrtb_ext.BidTypeVideo, + }, + { + Bid: &openrtb2.Bid{ + ID: "bid2", + AdM: ``, + }, + BidType: openrtb_ext.BidTypeVideo, + }, + }, + Seat: "bidder2", + }, + }, + errs: []error{}, + }, + { + name: "invalid_vast_version", + args: args{ + adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "bidder1": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "bid1", + AdM: ``, + }, + BidType: openrtb_ext.BidTypeVideo, + }, + }, + Seat: "bidder1", + }, + }, + seatNonBid: &openrtb_ext.NonBidCollection{}, + }, + want: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "bidder1": { + Bids: []*entities.PbsOrtbBid{}, + Seat: "bidder1", + }, + }, + errs: []error{ + &errortypes.Warning{ + Message: "bidder1 Bid bid1 was filtered for Imp with Vast Version 2.0: Incompatible with GAM unwinding requirements", + WarningCode: errortypes.InvalidVastVersionWarningCode, + }, + }, + }, + { + name: "multiple_bids_invalid_vast_version", + args: args{ + adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "bidder1": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "bid1", + AdM: ``, + }, + BidType: openrtb_ext.BidTypeVideo, + }, + { + Bid: &openrtb2.Bid{ + ID: "bid2", + AdM: ``, + }, + BidType: openrtb_ext.BidTypeVideo, + }, + }, + Seat: "bidder1", + }, + "bidder2": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "bid1", + AdM: ``, + }, + BidType: openrtb_ext.BidTypeVideo, + }, + { + Bid: &openrtb2.Bid{ + ID: "bid2", + AdM: ``, + }, + BidType: openrtb_ext.BidTypeVideo, + }, + }, + Seat: "bidder2", + }, + }, + seatNonBid: &openrtb_ext.NonBidCollection{}, + }, + want: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "bidder1": { + Bids: []*entities.PbsOrtbBid{}, + Seat: "bidder1", + }, + "bidder2": { + Bids: []*entities.PbsOrtbBid{}, + Seat: "bidder2", + }, + }, + errs: []error{ + &errortypes.Warning{ + Message: "bidder1 Bid bid1 was filtered for Imp with Vast Version 0: Incompatible with GAM unwinding requirements", + WarningCode: errortypes.InvalidVastVersionWarningCode, + }, + &errortypes.Warning{ + Message: "bidder1 Bid bid2 was filtered for Imp with Vast Version 1.0: Incompatible with GAM unwinding requirements", + WarningCode: errortypes.InvalidVastVersionWarningCode, + }, + &errortypes.Warning{ + Message: "bidder2 Bid bid1 was filtered for Imp with Vast Version 2.0: Incompatible with GAM unwinding requirements", + WarningCode: errortypes.InvalidVastVersionWarningCode, + }, + &errortypes.Warning{ + Message: "bidder2 Bid bid2 was filtered for Imp with Vast Version 1.0: Incompatible with GAM unwinding requirements", + WarningCode: errortypes.InvalidVastVersionWarningCode, + }, + }, + }, + { + name: "multiple_bids_valid_invalid_vast_version", + args: args{ + adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "bidder1": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "bid1", + AdM: ``, + }, + BidType: openrtb_ext.BidTypeVideo, + }, + { + Bid: &openrtb2.Bid{ + ID: "bid2", + AdM: ``, + }, + BidType: openrtb_ext.BidTypeVideo, + }, + }, + Seat: "bidder1", + }, + "bidder2": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "bid1", + AdM: ``, + }, + BidType: openrtb_ext.BidTypeVideo, + }, + { + Bid: &openrtb2.Bid{ + ID: "bid2", + AdM: ``, + }, + BidType: openrtb_ext.BidTypeVideo, + }, + }, + Seat: "bidder2", + }, + }, + seatNonBid: &openrtb_ext.NonBidCollection{}, + }, + want: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "bidder1": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "bid1", + AdM: ``, + }, + BidType: openrtb_ext.BidTypeVideo, + }, + }, + Seat: "bidder1", + }, + "bidder2": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "bid2", + AdM: ``, + }, + BidType: openrtb_ext.BidTypeVideo, + }, + }, + Seat: "bidder2", + }, + }, + errs: []error{ + &errortypes.Warning{ + Message: "bidder1 Bid bid2 was filtered for Imp with Vast Version 1.0: Incompatible with GAM unwinding requirements", + WarningCode: errortypes.InvalidVastVersionWarningCode, + }, + &errortypes.Warning{ + Message: "bidder2 Bid bid1 was filtered for Imp with Vast Version 2.0: Incompatible with GAM unwinding requirements", + WarningCode: errortypes.InvalidVastVersionWarningCode, + }, + }, + }, + { + name: "non_video_bid", + args: args{ + adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "bidder1": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "bid1", + AdM: ``, + }, + BidType: openrtb_ext.BidTypeBanner, + }, + }, + Seat: "bidder1", + }, + }, + seatNonBid: &openrtb_ext.NonBidCollection{}, + }, + want: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "bidder1": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "bid1", + AdM: ``, + }, + BidType: openrtb_ext.BidTypeBanner, + }, + }, + Seat: "bidder1", + }, + }, + errs: []error{}, + }, + { + name: "empty_adm", + args: args{ + adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "bidder1": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "bid1", + AdM: "", + }, + BidType: openrtb_ext.BidTypeVideo, + }, + }, + Seat: "bidder1", + }, + }, + seatNonBid: &openrtb_ext.NonBidCollection{}, + }, + want: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "bidder1": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "bid1", + AdM: "", + }, + BidType: openrtb_ext.BidTypeVideo, + }, + }, + Seat: "bidder1", + }, + }, + errs: []error{}, + }, + { + name: "invalid_vast_version_format", + args: args{ + adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "bidder1": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "bid1", + AdM: ``, + }, + BidType: openrtb_ext.BidTypeVideo, + }, + }, + Seat: "bidder1", + }, + }, + seatNonBid: &openrtb_ext.NonBidCollection{}, + }, + want: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "bidder1": { + Bids: []*entities.PbsOrtbBid{}, + Seat: "bidder1", + }, + }, + errs: []error{ + &errortypes.Warning{ + Message: "bidder1 Bid bid1 was filtered for Imp with Vast Version : Incompatible with GAM unwinding requirements", + WarningCode: errortypes.InvalidVastVersionWarningCode, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + errs := filterBidsByVastVersion(tt.args.adapterBids, tt.args.seatNonBid) + want := mapToSlice(tt.want) + got := mapToSlice(tt.args.adapterBids) + assert.ElementsMatch(t, want, got) + assert.ElementsMatch(t, tt.errs, errs) + }) + } +} + +func mapToSlice(bidMap map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid) []*entities.PbsOrtbSeatBid { + var result []*entities.PbsOrtbSeatBid + for _, bid := range bidMap { + result = append(result, bid) + } + return result +} + +func TestValidateVastVersion(t *testing.T) { + tests := []struct { + name string + adM string + expectedValid bool + expectedVersion string + }{ + { + name: "Valid VAST version 3", + adM: ``, + expectedValid: true, + expectedVersion: "3.0", + }, + { + name: "Valid VAST version 4", + adM: ``, + expectedValid: true, + expectedVersion: "4.0", + }, + { + name: "Invalid VAST version 2", + adM: ``, + expectedValid: false, + expectedVersion: "2.0", + }, + { + name: "Invalid VAST version 5", + adM: ``, + expectedValid: false, + expectedVersion: "5.0", + }, + { + name: "No VAST version", + adM: ``, + expectedValid: false, + expectedVersion: "", + }, + { + name: "Malformed VAST tag", + adM: `"}, + }, + }, + }}, + expected: testResults{ + bidFloorCur: "USD", + resolvedReq: `{"id":"some-request-id","imp":[{"id":"some-impression-id","bidfloor":15,"bidfloorcur":"USD","ext":{"prebid":{"bidder":{"appnexus":{"placementId":1}}}}}],"site":{"domain":"www.website.com","page":"prebid.org","ext":{"amp":0}},"test":1,"cur":["USD"],"ext":{"prebid":{"strictvastmode":true}}}`, + }, + }, + { + desc: "requestext_has_strict_vast_mode_vast_version_2", + req: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + ID: "some-request-id", + Imp: []openrtb2.Imp{{ + ID: "some-impression-id", + BidFloor: 15, + BidFloorCur: "USD", + Ext: json.RawMessage(`{"prebid":{"bidder":{"appnexus": {"placementId": 1}}}}`), + }}, + Site: &openrtb2.Site{ + Page: "prebid.org", + Ext: json.RawMessage(`{"amp":0}`), + Domain: "www.website.com", + }, + Test: 1, + Cur: []string{"USD"}, + Ext: json.RawMessage(`{"prebid":{"strictvastmode":true}}`), + }}, + bidderImpl: &goodSingleBidder{ + httpRequest: &adapters.RequestData{ + Method: "POST", + Uri: server.URL, + Body: []byte(`{"key":"val"}`), + Headers: http.Header{}, + }, + bidResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + BidType: openrtb_ext.BidTypeVideo, + Bid: &openrtb2.Bid{ID: "some-bid-id", AdM: ""}, + }, + }, + }}, + expected: testResults{ + bidFloorCur: "USD", + resolvedReq: `{"id":"some-request-id","imp":[{"id":"some-impression-id","bidfloor":15,"bidfloorcur":"USD","ext":{"prebid":{"bidder":{"appnexus":{"placementId":1}}}}}],"site":{"domain":"www.website.com","page":"prebid.org","ext":{"amp":0}},"test":1,"cur":["USD"],"ext":{"prebid":{"strictvastmode":true}}}`, + errMessage: "appnexus Bid some-bid-id was filtered for Imp with Vast Version 2.0: Incompatible with GAM unwinding requirements", + errCode: 10014, + }, + }, + } + + for _, test := range testCases { + auctionRequest := &AuctionRequest{ + BidRequestWrapper: test.req, + Account: config.Account{DebugAllow: true}, + UserSyncs: &emptyUsersync{}, + HookExecutor: &hookexecution.EmptyHookExecutor{}, + TCF2Config: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + } + + e.adapterMap = map[openrtb_ext.BidderName]AdaptedBidder{ + openrtb_ext.BidderAppnexus: AdaptBidder(test.bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, ""), + } + outBidResponse, err := e.HoldAuction(context.Background(), auctionRequest, &DebugLog{}) + assert.Equal(t, test.expected.err, err, "Error") + actualExt := &openrtb_ext.ExtBidResponse{} + _ = jsonutil.UnmarshalValid(outBidResponse.Ext, actualExt) + if test.expected.errMessage != "" && actualExt.Warnings != nil { + assert.NotNil(t, actualExt.Warnings["prebid"], "prebid warning should be present") + assert.Equal(t, actualExt.Warnings["prebid"][0].Message, test.expected.errMessage, "Warning Message") + assert.Equal(t, actualExt.Warnings["prebid"][0].Code, test.expected.errCode, "Warning Code") + } + actualResolvedRequest, _, _, _ := jsonparser.Get(outBidResponse.Ext, "debug", "resolvedrequest") + assert.JSONEq(t, test.expected.resolvedReq, string(actualResolvedRequest), "Resolved request is incorrect") + } +} func TestReturnCreativeEndToEnd(t *testing.T) { sampleAd := "" @@ -4972,7 +5128,7 @@ func TestMakeBidWithValidation(t *testing.T) { seatNonBid := openrtb_ext.NonBidCollection{} nonBid := openrtb_ext.NewNonBid(openrtb_ext.NonBidParams{ Bid: &openrtb2.Bid{}, - NonBidReason: 300, + NonBidReason: 620, BidMeta: &openrtb_ext.ExtBidPrebidMeta{ AdapterCode: "pubmatic", }, diff --git a/go.mod b/go.mod index 5badc4d5deb..95df10bfc77 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/PubMatic-OpenWrap/prebid-server/v2 go 1.20 -replace git.pubmatic.com/vastunwrap => git.pubmatic.com/PubMatic/vastunwrap v0.0.0-20240319050712-0b288cbb5a5d +replace git.pubmatic.com/vastunwrap => git.pubmatic.com/PubMatic/vastunwrap v0.0.0-20240827084017-0e392d3beb8b require ( github.com/DATA-DOG/go-sqlmock v1.5.0 @@ -51,7 +51,8 @@ require ( git.pubmatic.com/PubMatic/go-common v0.0.0-20240313090142-97ff3d63b7c3 git.pubmatic.com/PubMatic/go-netacuity-client v0.0.0-20240104092757-5d6f15e25fe3 git.pubmatic.com/vastunwrap v0.0.0-00010101000000-000000000000 - github.com/PubMatic-OpenWrap/fastxml v0.0.0-20240826060652-d9d5d05fdad2 + github.com/PubMatic-OpenWrap/fastxml v0.0.0-20241125102315-0d8f851a6e52 + github.com/beevik/etree/110 v0.0.0-00010101000000-000000000000 github.com/diegoholiveira/jsonlogic/v3 v3.5.3 github.com/go-sql-driver/mysql v1.7.1 github.com/golang/mock v1.6.0 @@ -88,10 +89,8 @@ require ( github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect - github.com/yudai/pp v2.0.1+incompatible // indirect golang.org/x/crypto v0.21.0 // indirect golang.org/x/sys v0.18.0 // indirect - github.com/yudai/pp v2.0.1+incompatible // indirect google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect @@ -101,6 +100,6 @@ replace github.com/prebid/prebid-server/v2 => ./ replace github.com/prebid/openrtb/v20 => github.com/PubMatic-OpenWrap/prebid-openrtb/v20 v20.0.0-20240222072752-2d647d1707ef -replace github.com/beevik/etree v1.0.2 => github.com/PubMatic-OpenWrap/etree v1.0.2-0.20240914050009-a916f68552f5 +replace github.com/beevik/etree v1.0.2 => github.com/PubMatic-OpenWrap/etree v1.0.2-0.20241125102329-0b5c47d99ad5 replace github.com/beevik/etree/110 => github.com/beevik/etree v1.1.0 diff --git a/go.sum b/go.sum index 6d50b0b288f..b5d85f1ecb8 100644 --- a/go.sum +++ b/go.sum @@ -53,9 +53,8 @@ git.pubmatic.com/PubMatic/go-common v0.0.0-20240313090142-97ff3d63b7c3 h1:Ea8zwi git.pubmatic.com/PubMatic/go-common v0.0.0-20240313090142-97ff3d63b7c3/go.mod h1:c/I6IcDn4Mtq4mmw8wGJN3v0o10nIMX7VTuQnsalUw0= git.pubmatic.com/PubMatic/go-netacuity-client v0.0.0-20240104092757-5d6f15e25fe3 h1:zQUpPJOjTBGu2fIydrfRWphH7EWLlBE/Qgn64BSoccI= git.pubmatic.com/PubMatic/go-netacuity-client v0.0.0-20240104092757-5d6f15e25fe3/go.mod h1:w733mqJnHt0hLR9mIFMzyDR0D94qzc7mFHsuE0tFQho= -git.pubmatic.com/PubMatic/vastunwrap v0.0.0-20240319050712-0b288cbb5a5d h1:BgLUpJQ9Z89eDGz//voK74G/8FgjgVg2PWVbjgCJ4+A= -git.pubmatic.com/PubMatic/vastunwrap v0.0.0-20240319050712-0b288cbb5a5d/go.mod h1:kcoJf7k+xug8X8fLWmsiKhPnYP+k7RZkfUoUo5QF+KA= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +git.pubmatic.com/PubMatic/vastunwrap v0.0.0-20240827084017-0e392d3beb8b h1:7AsXylZJDwq514L8KE0Id079VNfUsDEMUIYMlRYH+0Y= +git.pubmatic.com/PubMatic/vastunwrap v0.0.0-20240827084017-0e392d3beb8b/go.mod h1:kcoJf7k+xug8X8fLWmsiKhPnYP+k7RZkfUoUo5QF+KA= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= @@ -66,10 +65,10 @@ github.com/IABTechLab/adscert v0.34.0/go.mod h1:pCLd3Up1kfTrH6kYFUGGeavxIc1f6Tvv github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/PubMatic-OpenWrap/etree v1.0.2-0.20240914050009-a916f68552f5 h1:qNRDZVW/TJI0O4hPVdk5YCl+WxD6alYdaCG0im73lNo= -github.com/PubMatic-OpenWrap/etree v1.0.2-0.20240914050009-a916f68552f5/go.mod h1:5Y8qgcuDoy3XXG907UXkGnVTwihF16rXyJa4zRT7hOE= -github.com/PubMatic-OpenWrap/fastxml v0.0.0-20240826060652-d9d5d05fdad2 h1:4zaGImZVnKCJudxKfsVNJAqGhCPxbjApAo0QvEThwpw= -github.com/PubMatic-OpenWrap/fastxml v0.0.0-20240826060652-d9d5d05fdad2/go.mod h1:TGGzSA5ziWpfLsKvqOzgdPGEZ7SJIQjHbcJw6lWoyHU= +github.com/PubMatic-OpenWrap/etree v1.0.2-0.20241125102329-0b5c47d99ad5 h1:uNJ9lOn3q677J2PbR9wbnHec8452lHYvUZCfqMUxk0s= +github.com/PubMatic-OpenWrap/etree v1.0.2-0.20241125102329-0b5c47d99ad5/go.mod h1:5Y8qgcuDoy3XXG907UXkGnVTwihF16rXyJa4zRT7hOE= +github.com/PubMatic-OpenWrap/fastxml v0.0.0-20241125102315-0d8f851a6e52 h1:n1pLyiO0A0dUwvYjktIIRd6Kikm//msfvV6JO7m3lIE= +github.com/PubMatic-OpenWrap/fastxml v0.0.0-20241125102315-0d8f851a6e52/go.mod h1:TGGzSA5ziWpfLsKvqOzgdPGEZ7SJIQjHbcJw6lWoyHU= github.com/PubMatic-OpenWrap/prebid-openrtb/v20 v20.0.0-20240222072752-2d647d1707ef h1:CXsyYtEgZz/0++fiug6QZXrRYG6BBrl9IGveCxsnhiE= github.com/PubMatic-OpenWrap/prebid-openrtb/v20 v20.0.0-20240222072752-2d647d1707ef/go.mod h1:hLBrA/APkSrxs5MaW639l+y/EAHivDfRagO2TX/wbSc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -520,7 +519,6 @@ github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FB github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI= -github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/hooks/hookexecution/executor.go b/hooks/hookexecution/executor.go index 0ba466acc8d..2249fd29d42 100644 --- a/hooks/hookexecution/executor.go +++ b/hooks/hookexecution/executor.go @@ -260,10 +260,10 @@ func (e *hookExecutor) ExecuteRawBidderResponseStage(response *adapters.BidderRe stageName := hooks.StageRawBidderResponse.String() executionCtx := e.newContext(stageName) - payload := hookstage.RawBidderResponsePayload{Bids: response.Bids, Bidder: bidder} + payload := hookstage.RawBidderResponsePayload{BidderResponse: response, Bidder: bidder} outcome, payload, contexts, reject := executeStage(executionCtx, plan, payload, handler, e.metricEngine) - response.Bids = payload.Bids + response = payload.BidderResponse outcome.Entity = entity(bidder) outcome.Stage = stageName diff --git a/hooks/hookexecution/mocks_test.go b/hooks/hookexecution/mocks_test.go index a52fdbe12a7..968b1a7540b 100644 --- a/hooks/hookexecution/mocks_test.go +++ b/hooks/hookexecution/mocks_test.go @@ -154,7 +154,7 @@ func (e mockTimeoutHook) HandleRawBidderResponseHook(_ context.Context, _ hookst time.Sleep(20 * time.Millisecond) c := hookstage.ChangeSet[hookstage.RawBidderResponsePayload]{} c.AddMutation(func(payload hookstage.RawBidderResponsePayload) (hookstage.RawBidderResponsePayload, error) { - payload.Bids[0].BidMeta = &openrtb_ext.ExtBidPrebidMeta{AdapterCode: "new-code"} + payload.BidderResponse.Bids[0].BidMeta = &openrtb_ext.ExtBidPrebidMeta{AdapterCode: "new-code"} return payload, nil }, hookstage.MutationUpdate, "bidderResponse", "bidMeta.AdapterCode") @@ -371,7 +371,7 @@ func (e mockUpdateBidderResponseHook) HandleRawBidderResponseHook(_ context.Cont c := hookstage.ChangeSet[hookstage.RawBidderResponsePayload]{} c.AddMutation( func(payload hookstage.RawBidderResponsePayload) (hookstage.RawBidderResponsePayload, error) { - payload.Bids[0].DealPriority = 10 + payload.BidderResponse.Bids[0].DealPriority = 10 return payload, nil }, hookstage.MutationUpdate, "bidderResponse", "bid.deal-priority", ) diff --git a/hooks/hookstage/rawbidderresponse.go b/hooks/hookstage/rawbidderresponse.go index 7d08a7d2e02..1f6aaaa64ef 100644 --- a/hooks/hookstage/rawbidderresponse.go +++ b/hooks/hookstage/rawbidderresponse.go @@ -25,6 +25,6 @@ type RawBidderResponse interface { // objects representing bids returned by a particular bidder. // Hooks are allowed to modify bids using mutations. type RawBidderResponsePayload struct { - Bids []*adapters.TypedBid - Bidder string + BidderResponse *adapters.BidderResponse + Bidder string } diff --git a/hooks/hookstage/rawbidderresponse_mutations.go b/hooks/hookstage/rawbidderresponse_mutations.go index efab874fa15..c5022c31218 100644 --- a/hooks/hookstage/rawbidderresponse_mutations.go +++ b/hooks/hookstage/rawbidderresponse_mutations.go @@ -33,7 +33,7 @@ func (c ChangeSetBids[T]) Update(bids []*adapters.TypedBid) { c.changeSetRawBidderResponse.changeSet.AddMutation(func(p T) (T, error) { bidderPayload, err := c.changeSetRawBidderResponse.castPayload(p) if err == nil { - bidderPayload.Bids = bids + bidderPayload.BidderResponse.Bids = bids } if payload, ok := any(bidderPayload).(T); ok { return payload, nil diff --git a/metrics/prometheus/prometheus.go b/metrics/prometheus/prometheus.go index 36958db156b..1035f358f8a 100644 --- a/metrics/prometheus/prometheus.go +++ b/metrics/prometheus/prometheus.go @@ -174,10 +174,11 @@ const ( // NewMetrics initializes a new Prometheus metrics instance with preloaded label values. func NewMetrics(cfg config.PrometheusMetrics, reg *prometheus.Registry, disabledMetrics config.DisabledMetrics, syncerKeys []string, moduleStageNames map[string][]string) *Metrics { standardTimeBuckets := []float64{0.05, 0.1, 0.15, 0.20, 0.25, 0.3, 0.4, 0.5, 0.75, 1} - cacheWriteTimeBuckets := []float64{0.001, 0.002, 0.005, 0.01, 0.025, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 1} - priceBuckets := []float64{250, 500, 750, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 100000, 1000000, 10000000} + cacheWriteTimeBuckets := []float64{0.002, 0.005, 0.01, 0.025, 0.05} + priceBuckets := []float64{50, 100, 250, 500, 1000} queuedRequestTimeBuckets := []float64{0, 1, 5, 30, 60, 120, 180, 240, 300} overheadTimeBuckets := []float64{0.05, 0.06, 0.07, 0.08, 0.09, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1} + adapterRequestsTimeBuckets := []float64{0.05, 0.1, 0.15, 0.25, 0.5, 1} metrics := Metrics{} if reg == nil { @@ -457,7 +458,7 @@ func NewMetrics(cfg config.PrometheusMetrics, reg *prometheus.Registry, disabled "adapter_request_time_seconds", "Seconds to resolve each successful request labeled by adapter.", []string{adapterLabel}, - standardTimeBuckets) + adapterRequestsTimeBuckets) metrics.bidderServerResponseTimer = newHistogram(cfg, reg, "bidder_server_response_time_seconds", diff --git a/modules/prebid/ortb2blocking/hook_raw_bidder_response.go b/modules/prebid/ortb2blocking/hook_raw_bidder_response.go index 215de260b09..e1a4d9c60dd 100644 --- a/modules/prebid/ortb2blocking/hook_raw_bidder_response.go +++ b/modules/prebid/ortb2blocking/hook_raw_bidder_response.go @@ -34,7 +34,7 @@ func handleRawBidderResponseHook( // allowedBids will store all bids that have passed the attribute check allowedBids := make([]*adapters.TypedBid, 0) - for _, bid := range payload.Bids { + for _, bid := range payload.BidderResponse.Bids { failedChecksData := make(map[string]interface{}) bidMediaTypes := mediaTypesFromBid(bid) @@ -77,7 +77,7 @@ func handleRawBidderResponseHook( } changeSet := hookstage.ChangeSet[hookstage.RawBidderResponsePayload]{} - if len(payload.Bids) != len(allowedBids) { + if len(payload.BidderResponse.Bids) != len(allowedBids) { changeSet.RawBidderResponse().Bids().Update(allowedBids) result.ChangeSet = changeSet } diff --git a/modules/prebid/ortb2blocking/module_test.go b/modules/prebid/ortb2blocking/module_test.go index 8178cba15fe..053fc9bfc75 100644 --- a/modules/prebid/ortb2blocking/module_test.go +++ b/modules/prebid/ortb2blocking/module_test.go @@ -614,9 +614,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }{ { description: "Payload not changed when empty account config and empty module contexts are provided. Analytic tags have successful records", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ADomain: []string{"foo"}, ImpID: impID1}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ADomain: []string{"foo"}, ImpID: impID1}, + }, }, }}, expectedBids: []*adapters.TypedBid{ @@ -643,9 +645,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Catch error if wrong data has been passed from previous hook. Payload not changed", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ADomain: []string{"foo"}, ImpID: impID1}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ADomain: []string{"foo"}, ImpID: impID1}, + }, }, }}, expectedBids: []*adapters.TypedBid{ @@ -658,12 +662,14 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid blocked by badv attribute check. Payload updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, + }, }, }}, config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": true}}}`), @@ -701,12 +707,14 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid not blocked because blocking conditions for current bidder do not exist. Payload not updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, + }, }, }}, config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": true}}}`), @@ -742,12 +750,14 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid not blocked because enforce blocking is disabled by account config. Payload not updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, + }, }, }}, config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": false}}}`), @@ -783,12 +793,14 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid not blocked because enforce blocking overridden for given bidder. Payload not updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, + }, }, }}, config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": true, "action_overrides": {"enforce_blocks": [{"conditions": {"bidders": ["appnexus"]}, "override": false}]}}}}`), @@ -824,12 +836,14 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid blocked by badv attribute check (block unknown attributes). Payload updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, + }, }, }}, config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": true, "block_unknown_adomain": true}}}`), @@ -867,12 +881,14 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid not blocked because block unknown overridden for given bidder. Payload not updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", ImpID: impID2}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", ImpID: impID2}, + }, }, }}, config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": true, "block_unknown_adomain": true, "action_overrides": {"block_unknown_adomain": [{"conditions": {"bidders": ["appnexus"]}, "override": false}]}}}}`), @@ -908,12 +924,14 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid not blocked due to deal exception. Payload not updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1, DealID: "acceptDealID"}, - }, - { - Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1, DealID: "acceptDealID"}, + }, + { + Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, + }, }, }}, config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": true, "action_overrides": {"allowed_adomain_for_deals": [{"conditions": {"deal_ids": ["acceptDealID"]}, "override": ["forbidden_domain"]}]}}}}`), @@ -949,9 +967,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if there is an issue processing enforce blocks overrides for badv attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}}, + }, }, }}, config: json.RawMessage(`{"attributes": {"badv": {"enforce_blocks": true, "action_overrides": {"enforce_blocks": [{"conditions": {}}]}}}}`), @@ -974,9 +994,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if there is an issue processing block unknown domains overrides for badv attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}}, + }, }, }}, config: json.RawMessage(`{"attributes": {"badv": {"enforce_blocks": true, "action_overrides": {"block_unknown_adomain": [{"conditions": {}}]}}}}`), @@ -999,9 +1021,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if deal_ids not defined in config override conditions for badv attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, DealID: "acceptDealID"}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, DealID: "acceptDealID"}, + }, }, }}, config: json.RawMessage(`{"attributes": {"badv": {"enforce_blocks": true, "action_overrides": {"allowed_adomain_for_deals": [{"conditions": {}}]}}}}`), @@ -1024,12 +1048,14 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid blocked by bcat attribute check. Payload updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", Cat: []string{"moto"}, ImpID: impID2}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", Cat: []string{"moto"}, ImpID: impID2}, + }, }, }}, config: json.RawMessage(`{"attributes":{"bcat":{"enforce_blocks": true}}}`), @@ -1067,9 +1093,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if there is an issue processing enforce blocks overrides for bcat attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1}, + }, }, }}, config: json.RawMessage(`{"attributes": {"bcat": {"enforce_blocks": true, "action_overrides": {"enforce_blocks": [{"conditions": {}}]}}}}`), @@ -1092,9 +1120,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if there is an issue processing block unknown domains overrides for bcat attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1}, + }, }, }}, config: json.RawMessage(`{"attributes": {"bcat": {"enforce_blocks": true, "action_overrides": {"block_unknown_adv_cat": [{"conditions": {}}]}}}}`), @@ -1117,9 +1147,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if deal_ids not defined in config override conditions for bcat attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1, DealID: "acceptDealID"}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1, DealID: "acceptDealID"}, + }, }, }}, config: json.RawMessage(`{"attributes": {"bcat": {"enforce_blocks": true, "action_overrides": {"allowed_adv_cat_for_deals": [{"conditions": {}}]}}}}`), @@ -1142,12 +1174,14 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid blocked by cattax attribute check. Payload updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", CatTax: 1, ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", CatTax: 2, ImpID: impID2}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", CatTax: 1, ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", CatTax: 2, ImpID: impID2}, + }, }, }}, config: json.RawMessage(`{"attributes":{"bcat":{"enforce_blocks": true}}}`), @@ -1185,12 +1219,14 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid blocked by cattax attribute check (the default value used if no blocking attribute passed). Payload updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", CatTax: 1, ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", CatTax: 2, ImpID: impID2}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", CatTax: 1, ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", CatTax: 2, ImpID: impID2}, + }, }, }}, config: json.RawMessage(`{"attributes":{"bcat":{"enforce_blocks": true}}}`), @@ -1227,12 +1263,14 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid blocked by bapp attribute check. Payload updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Bundle: "forbidden_bundle", ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", Bundle: "allowed_bundle", ImpID: impID2}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Bundle: "forbidden_bundle", ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", Bundle: "allowed_bundle", ImpID: impID2}, + }, }, }}, config: json.RawMessage(`{"attributes":{"bapp":{"enforce_blocks": true}}}`), @@ -1270,9 +1308,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if there is an issue processing enforce blocks overrides for bapp attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Bundle: "forbidden_bundle", ImpID: impID1}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Bundle: "forbidden_bundle", ImpID: impID1}, + }, }, }}, config: json.RawMessage(`{"attributes": {"bapp": {"enforce_blocks": true, "action_overrides": {"enforce_blocks": [{"conditions": {}}]}}}}`), @@ -1295,9 +1335,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if deal_ids not defined in config override conditions for bapp attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Bundle: "forbidden_bundle", ImpID: impID1, DealID: "acceptDealID"}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Bundle: "forbidden_bundle", ImpID: impID1, DealID: "acceptDealID"}, + }, }, }}, config: json.RawMessage(`{"attributes": {"bapp": {"enforce_blocks": true, "action_overrides": {"allowed_app_for_deals": [{"conditions": {}}]}}}}`), @@ -1320,12 +1362,14 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid blocked by battr attribute check. Payload updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Attr: []adcom1.CreativeAttribute{1}, ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", Attr: []adcom1.CreativeAttribute{2}, ImpID: impID2}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Attr: []adcom1.CreativeAttribute{1}, ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", Attr: []adcom1.CreativeAttribute{2}, ImpID: impID2}, + }, }, }}, config: json.RawMessage(`{"attributes":{"battr":{"enforce_blocks": true}}}`), @@ -1363,9 +1407,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if there is an issue processing enforce blocks overrides for battr attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Attr: []adcom1.CreativeAttribute{1}, ImpID: impID1}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Attr: []adcom1.CreativeAttribute{1}, ImpID: impID1}, + }, }, }}, config: json.RawMessage(`{"attributes": {"battr": {"enforce_blocks": true, "action_overrides": {"enforce_blocks": [{"conditions": {}}]}}}}`), @@ -1388,9 +1434,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if deal_ids not defined in config override conditions for battr attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Attr: []adcom1.CreativeAttribute{1}, ImpID: impID1, DealID: "acceptDealID"}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Attr: []adcom1.CreativeAttribute{1}, ImpID: impID1, DealID: "acceptDealID"}, + }, }, }}, config: json.RawMessage(`{"attributes": {"battr": {"enforce_blocks": true, "action_overrides": {"allowed_banner_attr_for_deals": [{"conditions": {}}]}}}}`), @@ -1413,27 +1461,29 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid blocked by multiple attribute check. Payload updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ - ID: "1", - ADomain: []string{"forbidden_domain"}, - Cat: []string{"fishing"}, - CatTax: 1, - Bundle: "forbidden_bundle", - Attr: []adcom1.CreativeAttribute{1}, - ImpID: impID1, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "1", + ADomain: []string{"forbidden_domain"}, + Cat: []string{"fishing"}, + CatTax: 1, + Bundle: "forbidden_bundle", + Attr: []adcom1.CreativeAttribute{1}, + ImpID: impID1, + }, }, - }, - { - Bid: &openrtb2.Bid{ - ID: "2", - ADomain: []string{"allowed_domain"}, - Cat: []string{"moto"}, - CatTax: 2, - Bundle: "allowed_bundle", - Attr: []adcom1.CreativeAttribute{2}, - ImpID: impID2, + { + Bid: &openrtb2.Bid{ + ID: "2", + ADomain: []string{"allowed_domain"}, + Cat: []string{"moto"}, + CatTax: 2, + Bundle: "allowed_bundle", + Attr: []adcom1.CreativeAttribute{2}, + ImpID: impID2, + }, }, }, }}, @@ -1514,7 +1564,7 @@ func TestHandleRawBidderResponseHook(t *testing.T) { assert.NoError(t, err) test.payload = newPayload } - assert.Equal(t, test.expectedBids, test.payload.Bids, "Invalid Bids returned after executing RawBidderResponse hook.") + assert.Equal(t, test.expectedBids, test.payload.BidderResponse.Bids, "Invalid Bids returned after executing RawBidderResponse hook.") // reset ChangeSet not to break hookResult assertion, we validated ChangeSet separately hookResult.ChangeSet = hookstage.ChangeSet[hookstage.RawBidderResponsePayload]{} diff --git a/modules/pubmatic/openwrap/adapters/bidder_alias.go b/modules/pubmatic/openwrap/adapters/bidder_alias.go index c07e8c31841..32fb416988c 100644 --- a/modules/pubmatic/openwrap/adapters/bidder_alias.go +++ b/modules/pubmatic/openwrap/adapters/bidder_alias.go @@ -17,6 +17,7 @@ func Alias() map[string]string { models.BidderDistrictmAlias: string(openrtb_ext.BidderAppnexus), models.BidderAndBeyondAlias: string(openrtb_ext.BidderAdkernel), models.BidderMediaFuseAlias: string(openrtb_ext.BidderAppnexus), + models.BidderAppStockAlias: string(openrtb_ext.BidderLimelightDigital), } } @@ -45,6 +46,8 @@ func ResolveOWBidder(bidderName string) string { coreBidderName = string(openrtb_ext.BidderImds) case models.BidderViewDeos: coreBidderName = string(openrtb_ext.BidderAdtelligent) + case models.BidderAppStockAlias: + coreBidderName = string(openrtb_ext.BidderLimelightDigital) } return coreBidderName } diff --git a/modules/pubmatic/openwrap/adapters/bidder_alias_test.go b/modules/pubmatic/openwrap/adapters/bidder_alias_test.go index 8605884c216..dc326feab2c 100644 --- a/modules/pubmatic/openwrap/adapters/bidder_alias_test.go +++ b/modules/pubmatic/openwrap/adapters/bidder_alias_test.go @@ -16,6 +16,7 @@ func TestAlias(t *testing.T) { models.BidderDistrictmAlias: string(openrtb_ext.BidderAppnexus), models.BidderAndBeyondAlias: string(openrtb_ext.BidderAdkernel), models.BidderMediaFuseAlias: string(openrtb_ext.BidderAppnexus), + models.BidderAppStockAlias: string(openrtb_ext.BidderLimelightDigital), } assert.Equal(t, expected, Alias()) } diff --git a/modules/pubmatic/openwrap/adapters/default_bidder_parameter_test.go b/modules/pubmatic/openwrap/adapters/default_bidder_parameter_test.go index 0b12962c5ba..ac7d1a78d34 100644 --- a/modules/pubmatic/openwrap/adapters/default_bidder_parameter_test.go +++ b/modules/pubmatic/openwrap/adapters/default_bidder_parameter_test.go @@ -111,7 +111,7 @@ func TestGetType(t *testing.T) { func TestParseBidderParams(t *testing.T) { parseBidderParams("../../static/bidder-params") - assert.Equal(t, 167, len(adapterParams), "Length of expected entries should match") + assert.Equal(t, 168, len(adapterParams), "Length of expected entries should match") // calculate this number using X-Y // where X is calculated using command - `ls -l | wc -l` (substract 1 from result) // Y is calculated using command `grep -EinR 'oneof|not|anyof|dependenc' static/bidder-params | grep -v "description" | grep -oE './.*.json' | uniq | wc -l` @@ -119,7 +119,7 @@ func TestParseBidderParams(t *testing.T) { func TestParseBidderSchemaDefinitions(t *testing.T) { schemaDefinitions, _ := parseBidderSchemaDefinitions("../../../../static/bidder-params") - assert.Equal(t, 208, len(schemaDefinitions), "Length of expected entries should match") + assert.Equal(t, 209, len(schemaDefinitions), "Length of expected entries should match") // calculate this number using command - `ls -l | wc -l` (substract 1 from result) } diff --git a/modules/pubmatic/openwrap/applovinmax.go b/modules/pubmatic/openwrap/applovinmax.go index 06fa6a8de00..e9707c414d0 100644 --- a/modules/pubmatic/openwrap/applovinmax.go +++ b/modules/pubmatic/openwrap/applovinmax.go @@ -5,6 +5,7 @@ import ( "strconv" "github.com/buger/jsonparser" + "github.com/golang/glog" "github.com/prebid/openrtb/v20/openrtb2" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" ) @@ -16,13 +17,13 @@ func getSignalData(requestBody []byte, rctx models.RequestCtx) *openrtb2.BidRequ if err == jsonparser.KeyPathNotFoundError { signalType = models.MissingSignal } - rctx.MetricsEngine.RecordSignalDataStatus(getAppPublisherID(requestBody), getProfileID(requestBody), signalType) + rctx.MetricsEngine.RecordSignalDataStatus(getAppPublisherID(requestBody), rctx.ProfileIDStr, signalType) return nil } signalData := &openrtb2.BidRequest{} if err := json.Unmarshal([]byte(signal), signalData); err != nil { - rctx.MetricsEngine.RecordSignalDataStatus(getAppPublisherID(requestBody), getProfileID(requestBody), models.InvalidSignal) + rctx.MetricsEngine.RecordSignalDataStatus(getAppPublisherID(requestBody), rctx.ProfileIDStr, models.InvalidSignal) return nil } return signalData @@ -186,7 +187,14 @@ func updateUser(signalUser *openrtb2.User, maxRequest *openrtb2.BidRequest) { } maxRequest.User.Data = signalUser.Data - maxRequest.User.Ext = setIfKeysExists(signalUser.Ext, maxRequest.User.Ext, "consent", "eids") + + if maxRequest.User.Ext != nil { + // Don’t pass sessionduration and impdepth parameter if present in the request + maxRequest.User.Ext = jsonparser.Delete(maxRequest.User.Ext, "sessionduration") + maxRequest.User.Ext = jsonparser.Delete(maxRequest.User.Ext, "impdepth") + } + //Pass user.ext.sessionduration and user.ext.impdepth to ow partners in case of ALMAX integration + maxRequest.User.Ext = setIfKeysExists(signalUser.Ext, maxRequest.User.Ext, "consent", "eids", "sessionduration", "impdepth") } func setIfKeysExists(source []byte, target []byte, keys ...string) []byte { @@ -234,6 +242,7 @@ func updateRequestWrapper(signalExt json.RawMessage, maxRequest *openrtb2.BidReq } func updateAppLovinMaxRequest(requestBody []byte, rctx models.RequestCtx) []byte { + requestBody, rctx.ProfileIDStr = setProfileID(requestBody) signalData := getSignalData(requestBody, rctx) if signalData == nil { return modifyRequestBody(requestBody) @@ -251,6 +260,29 @@ func updateAppLovinMaxRequest(requestBody []byte, rctx models.RequestCtx) []byte return requestBody } +func setProfileID(requestBody []byte) ([]byte, string) { + if profileId, err := jsonparser.GetInt(requestBody, "ext", "prebid", "bidderparams", "pubmatic", "wrapper", "profileid"); err == nil && profileId != 0 { + return requestBody, strconv.Itoa(int(profileId)) + } + + profIDStr, err := jsonparser.GetString(requestBody, "app", "id") + if err != nil || len(profIDStr) == 0 { + return requestBody, "" + } + + if _, err := strconv.Atoi(profIDStr); err != nil { + glog.Errorf("[AppLovinMax] [ProfileID]: %s [Error]: failed to convert app.id to integer %v", profIDStr, err) + return requestBody, "" + } + + requestBody = jsonparser.Delete(requestBody, "app", "id") + if newRequestBody, err := jsonparser.Set(requestBody, []byte(profIDStr), "ext", "prebid", "bidderparams", "pubmatic", "wrapper", "profileid"); err == nil { + return newRequestBody, profIDStr + } + glog.Errorf("[AppLovinMax] [ProfileID]: %s [Error]: failed to set profileid in wrapper %v", profIDStr, err) + return requestBody, "" +} + func updateAppLovinMaxResponse(rctx models.RequestCtx, bidResponse *openrtb2.BidResponse) models.AppLovinMax { rctx.AppLovinMax.Reject = false @@ -308,17 +340,6 @@ func getAppPublisherID(requestBody []byte) string { return "" } -func getProfileID(requestBody []byte) string { - if profileId, err := jsonparser.GetInt(requestBody, "ext", "prebid", "bidderparams", "pubmatic", "wrapper", "profileid"); err == nil { - profIDStr := strconv.Itoa(int(profileId)) - return profIDStr - } - if profIDStr, err := jsonparser.GetString(requestBody, "app", "id"); err == nil { - return profIDStr - } - return "" -} - // modifyRequestBody modifies displaymanger and banner object in req if signal is missing/invalid func modifyRequestBody(requestBody []byte) []byte { if body, err := jsonparser.Set(requestBody, []byte(strconv.Quote("PubMatic_OpenWrap_SDK")), "imp", "[0]", "displaymanager"); err == nil { diff --git a/modules/pubmatic/openwrap/applovinmax_test.go b/modules/pubmatic/openwrap/applovinmax_test.go index 6e1e1e78463..9b7a98ecaaf 100644 --- a/modules/pubmatic/openwrap/applovinmax_test.go +++ b/modules/pubmatic/openwrap/applovinmax_test.go @@ -439,6 +439,110 @@ func TestUpdateUser(t *testing.T) { }, want: &openrtb2.User{ID: "maxID", Yob: 1999, Gender: "M", Keywords: "k1=v2;k2=v2", Ext: json.RawMessage(`{"consent":"consent_string","eids":[{"source":"amxid","uids":[{"atype":1,"id":"88de601e-3d98-48e7-81d7-00000000"}]},{"source":"adserver.org","uids":[{"id":"1234567","ext":{"rtiPartner":"TDID"}}]}]}`)}, }, + { + name: "signalUserExt_and_request_both_has_sessionduration_and_impdepth", + args: args{ + signalUser: &openrtb2.User{ID: "sdkID", Yob: 1999, Gender: "M", Keywords: "k1=v2;k2=v2", Ext: json.RawMessage(`{"sessionduration":40,"impdepth":10}`)}, + maxRequest: &openrtb2.BidRequest{User: &openrtb2.User{ID: "maxID", Yob: 2000, Gender: "F", Keywords: "k52=v43", Ext: json.RawMessage(`{"sessionduration":50,"impdepth":15}`)}}, + }, + want: &openrtb2.User{ID: "maxID", Yob: 1999, Gender: "M", Keywords: "k1=v2;k2=v2", Ext: json.RawMessage(`{"sessionduration":40,"impdepth":10}`)}, + }, + { + name: "signalUserExt_has_sessionduration_and_impdepth_with_consent", + args: args{ + signalUser: &openrtb2.User{ID: "sdkID", Yob: 1999, Gender: "M", Keywords: "k1=v2;k2=v2", Ext: json.RawMessage(`{"sessionduration":40,"impdepth":10,"consent":"consent_string"}`)}, + maxRequest: &openrtb2.BidRequest{User: &openrtb2.User{ID: "maxID", Yob: 2000, Gender: "F", Keywords: "k52=v43"}}, + }, + want: &openrtb2.User{ID: "maxID", Yob: 1999, Gender: "M", Keywords: "k1=v2;k2=v2", Ext: json.RawMessage(`{"consent":"consent_string","sessionduration":40,"impdepth":10}`)}, + }, + { + name: "signalUserExt_has_invalid_sessionduration_and_impdepth_with_consent", + args: args{ + signalUser: &openrtb2.User{ID: "sdkID", Yob: 1999, Gender: "M", Keywords: "k1=v2;k2=v2", Ext: json.RawMessage(`{"sessionduration":-40,"impdepth":-10,"consent":"consent_string"}`)}, + maxRequest: &openrtb2.BidRequest{User: &openrtb2.User{ID: "maxID", Yob: 2000, Gender: "F", Keywords: "k52=v43"}}, + }, + want: &openrtb2.User{ID: "maxID", Yob: 1999, Gender: "M", Keywords: "k1=v2;k2=v2", Ext: json.RawMessage(`{"consent":"consent_string","sessionduration":-40,"impdepth":-10}`)}, + }, + { + name: "request_has_sessionduration_and_impdepth_with_consent", + args: args{ + signalUser: &openrtb2.User{ID: "sdkID", Yob: 1999, Gender: "M", Keywords: "k1=v2;k2=v2", Ext: json.RawMessage(`{"consent":"consent_string"}`)}, + maxRequest: &openrtb2.BidRequest{User: &openrtb2.User{ID: "maxID", Yob: 2000, Gender: "F", Keywords: "k52=v43"}, Ext: json.RawMessage(`{"sessionduration":40,"impdepth":10,"consent":"consent_string1"}`)}, + }, + want: &openrtb2.User{ID: "maxID", Yob: 1999, Gender: "M", Keywords: "k1=v2;k2=v2", Ext: json.RawMessage(`{"consent":"consent_string"}`)}, + }, + { + name: "request_has_invalid_sessionduration_and_impdepth_with_consent", + args: args{ + signalUser: &openrtb2.User{ID: "sdkID", Yob: 1999, Gender: "M", Keywords: "k1=v2;k2=v2", Ext: json.RawMessage(`{"consent":"consent_string"}`)}, + maxRequest: &openrtb2.BidRequest{User: &openrtb2.User{ID: "maxID", Yob: 2000, Gender: "F", Keywords: "k52=v43"}, Ext: json.RawMessage(`{"sessionduration":-40,"impdepth":-10,"consent":"consent_string1"}`)}, + }, + want: &openrtb2.User{ID: "maxID", Yob: 1999, Gender: "M", Keywords: "k1=v2;k2=v2", Ext: json.RawMessage(`{"consent":"consent_string"}`)}, + }, + { + name: "signalUserExt_has_invalid_and_request_has_valid_sessionduration_and_impdepth", + args: args{ + signalUser: &openrtb2.User{ID: "sdkID", Yob: 1999, Gender: "M", Keywords: "k1=v2;k2=v2", Ext: json.RawMessage(`{"sessionduration":-40,"impdepth":"impdepth"}`)}, + maxRequest: &openrtb2.BidRequest{User: &openrtb2.User{ID: "maxID", Yob: 2000, Gender: "F", Keywords: "k52=v43", Ext: json.RawMessage(`{"sessionduration":50,"impdepth":15}`)}}, + }, + want: &openrtb2.User{ID: "maxID", Yob: 1999, Gender: "M", Keywords: "k1=v2;k2=v2", Ext: json.RawMessage(`{"sessionduration":-40,"impdepth":"impdepth"}`)}, + }, + { + name: "signalUserExt_has_invalid_sessionduration_and_impdepth", + args: args{ + signalUser: &openrtb2.User{ID: "sdkID", Yob: 1999, Gender: "M", Keywords: "k1=v2;k2=v2", Ext: json.RawMessage(`{"sessionduration":-40,"impdepth":"impdepth"}`)}, + maxRequest: &openrtb2.BidRequest{User: &openrtb2.User{ID: "maxID", Yob: 2000, Gender: "F", Keywords: "k52=v43"}}, + }, + want: &openrtb2.User{ID: "maxID", Yob: 1999, Gender: "M", Keywords: "k1=v2;k2=v2", Ext: json.RawMessage(`{"sessionduration":-40,"impdepth":"impdepth"}`)}, + }, + { + name: "request_has_sessionduration_and_impdepth", + args: args{ + signalUser: &openrtb2.User{ID: "sdkID", Yob: 1999, Gender: "M", Keywords: "k1=v2;k2=v2"}, + maxRequest: &openrtb2.BidRequest{User: &openrtb2.User{ID: "maxID", Yob: 2000, Gender: "F", Keywords: "k52=v43", Ext: json.RawMessage(`{"sessionduration":50,"impdepth":15}`)}}, + }, + want: &openrtb2.User{ID: "maxID", Yob: 1999, Gender: "M", Keywords: "k1=v2;k2=v2", Ext: json.RawMessage(`{}`)}, + }, + { + name: "signalUserExt_has_sessionduration_and_impdepth", + args: args{ + signalUser: &openrtb2.User{ID: "sdkID", Yob: 1999, Gender: "M", Keywords: "k1=v2;k2=v2", Ext: json.RawMessage(`{"sessionduration":40,"impdepth":10}`)}, + maxRequest: &openrtb2.BidRequest{User: &openrtb2.User{ID: "maxID", Yob: 2000, Gender: "F", Keywords: "k52=v43"}}, + }, + want: &openrtb2.User{ID: "maxID", Yob: 1999, Gender: "M", Keywords: "k1=v2;k2=v2", Ext: json.RawMessage(`{"sessionduration":40,"impdepth":10}`)}, + }, + { + name: "signalUserExt_has_string_type_sessionduration_and_impdepth", + args: args{ + signalUser: &openrtb2.User{ID: "sdkID", Yob: 1999, Gender: "M", Keywords: "k1=v2;k2=v2", Ext: json.RawMessage(`{"sessionduration":"sessionduration","impdepth":"impdepth"}`)}, + maxRequest: &openrtb2.BidRequest{User: &openrtb2.User{ID: "maxID", Yob: 2000, Gender: "F", Keywords: "k52=v43"}}, + }, + want: &openrtb2.User{ID: "maxID", Yob: 1999, Gender: "M", Keywords: "k1=v2;k2=v2", Ext: json.RawMessage(`{"sessionduration":"sessionduration","impdepth":"impdepth"}`)}, + }, + { + name: "signalUserExt_has_zero_sessionduration_and_impdepth", + args: args{ + signalUser: &openrtb2.User{ID: "sdkID", Yob: 1999, Gender: "M", Keywords: "k1=v2;k2=v2", Ext: json.RawMessage(`{"sessionduration":0,"impdepth":0}`)}, + maxRequest: &openrtb2.BidRequest{User: &openrtb2.User{ID: "maxID", Yob: 2000, Gender: "F", Keywords: "k52=v43"}}, + }, + want: &openrtb2.User{ID: "maxID", Yob: 1999, Gender: "M", Keywords: "k1=v2;k2=v2", Ext: json.RawMessage(`{"sessionduration":0,"impdepth":0}`)}, + }, + { + name: "signalUserExt_has_sessionduration", + args: args{ + signalUser: &openrtb2.User{ID: "sdkID", Yob: 1999, Gender: "M", Keywords: "k1=v2;k2=v2", Ext: json.RawMessage(`{"sessionduration":40}`)}, + maxRequest: &openrtb2.BidRequest{User: &openrtb2.User{ID: "maxID", Yob: 2000, Gender: "F", Keywords: "k52=v43"}}, + }, + want: &openrtb2.User{ID: "maxID", Yob: 1999, Gender: "M", Keywords: "k1=v2;k2=v2", Ext: json.RawMessage(`{"sessionduration":40}`)}, + }, + { + name: "signalUserExt_has_impdepth", + args: args{ + signalUser: &openrtb2.User{ID: "sdkID", Yob: 1999, Gender: "M", Keywords: "k1=v2;k2=v2", Ext: json.RawMessage(`{"impdepth":10}`)}, + maxRequest: &openrtb2.BidRequest{User: &openrtb2.User{ID: "maxID", Yob: 2000, Gender: "F", Keywords: "k52=v43"}}, + }, + want: &openrtb2.User{ID: "maxID", Yob: 1999, Gender: "M", Keywords: "k1=v2;k2=v2", Ext: json.RawMessage(`{"impdepth":10}`)}, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -629,6 +733,7 @@ func TestGetSignalData(t *testing.T) { requestBody: []byte(`{"id":"123","app":{"publisher":{"id":"5890"}},"user":{"data":[{"id":"1","name":"Publisher Passed","segment":[{"signal":"{BIDDING_SIGNAL}"}]}]},"ext":{"prebid":{"bidderparams":{"pubmatic":{"wrapper":{"profileid":1234}}}}}}`), rctx: models.RequestCtx{ MetricsEngine: mockEngine, + ProfileIDStr: "1234", }, }, setup: func() { @@ -642,6 +747,7 @@ func TestGetSignalData(t *testing.T) { requestBody: []byte(`{"id":"123","user":{"data":[{"id":"1","name":"Publisher Passed","segment":[{"signal":{}}]}]},"ext":{"prebid":{"bidderparams":{"pubmatic":{"wrapper":{"profileid":1234}}}}}}`), rctx: models.RequestCtx{ MetricsEngine: mockEngine, + ProfileIDStr: "1234", }, }, setup: func() { @@ -1087,48 +1193,62 @@ func TestModifyRequestBody(t *testing.T) { } } -func TestGetProfileID(t *testing.T) { +func TestSetProfileID(t *testing.T) { type args struct { requestBody []byte } tests := []struct { - name string - args args - want string + name string + args args + wantProfile string + wantBody []byte }{ { name: "profileID present in request.ext.prebid.bidderparams.pubmatic.wrapper", args: args{ requestBody: []byte(`{"app":{"bundle":"com.pubmatic.openbid.app","name":"Sample","publisher":{"id":"156276"},"storeurl":"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098","ver":"1.0"},"at":1,"device":{"carrier":"MYTEL","connectiontype":2,"devicetype":4,"ext":{"atts":2},"geo":{"city":"Queens","country":"USA","dma":"501","ipservice":3,"lat":40.7429,"lon":-73.9392,"long":-73.9392,"metro":"501","region":"ny","type":2,"zip":"11101"},"h":2400,"hwv":"ruby","ifa":"497a10d6-c4dd-4e04-a986-c32b7180d462","ip":"38.158.207.171","js":1,"language":"en_US","make":"xiaomi","model":"22101316c","os":"android","osv":"13.0.0","ppi":440,"pxratio":2.75,"ua":"Mozilla/5.0 (Linux; Android 13; 22101316C Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.230 Mobile Safari/537.36","w":1080},"ext":{"prebid":{"bidderparams":{"pubmatic":{"wrapper":{"profileid":1234}}}}},"id":"95d6643c-3da6-40a2-b9ca-12279393ffbf","imp":[{"banner":{"format":[{"w":728,"h":90},{"w":320,"h":50}],"api":[5,2],"w":700,"h":900},"clickbrowser":0,"displaymanager":"OpenBid_SDK","displaymanagerver":"1.4.0","ext":{"reward":1},"id":"imp176227948","secure":0,"tagid":"OpenWrapBidderBannerAdUnit"}],"regs":{"ext":{"gdpr":0}},"source":{"ext":{"omidpn":"PubMatic","omidpv":"1.2.11-Pubmatic"}},"user":{"data":[{"id":"1","name":"Publisher Passed","segment":[{"signal":"{\"id\":\"95d6643c-3da6-40a2-b9ca-12279393ffbf\",\"at\":1,\"tmax\":500,\"cur\":[\"USD\"],\"imp\":[{\"id\":\"imp176227948\",\"clickbrowser\":0,\"displaymanager\":\"PubMatic_OpenBid_SDK\",\"displaymanagerver\":\"1.4.0\",\"tagid\":\"/43743431/DMDemo\",\"secure\":0,\"banner\":{\"pos\":7,\"format\":[{\"w\":300,\"h\":250}],\"api\":[5,6,7]},\"instl\":1}],\"app\":{\"name\":\"OpenWrapperSample\",\"bundle\":\"com.pubmatic.openbid.app\",\"storeurl\":\"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098?appnexus_banner_fixedbid=1&fixedbid=1\",\"ver\":\"1.0\",\"publisher\":{\"id\":\"5890\"}},\"device\":{\"ext\":{\"atts\":0},\"geo\":{\"type\":1,\"lat\":37.421998333333335,\"lon\":-122.08400000000002},\"pxratio\":2.625,\"mccmnc\":\"310-260\",\"lmt\":0,\"ifa\":\"07c387f2-e030-428f-8336-42f682150759\",\"connectiontype\":6,\"carrier\":\"Android\",\"js\":1,\"ua\":\"Mozilla/5.0(Linux;Android9;AndroidSDKbuiltforx86Build/PSR1.180720.075;wv)AppleWebKit/537.36(KHTML,likeGecko)Version/4.0Chrome/69.0.3497.100MobileSafari/537.36\",\"make\":\"Google\",\"model\":\"AndroidSDKbuiltforx86\",\"os\":\"Android\",\"osv\":\"9\",\"h\":1794,\"w\":1080,\"language\":\"en-US\",\"devicetype\":4},\"source\":{\"ext\":{\"omidpn\":\"PubMatic\",\"omidpv\":\"1.2.11-Pubmatic\"}},\"user\":{},\"ext\":{\"wrapper\":{\"ssauction\":0,\"sumry_disable\":0,\"profileid\":58135,\"versionid\":1,\"clientconfig\":1}}}"}]}],"ext":{"consent":"CP2KIMAP2KIgAEPgABBYJGNX_H__bX9j-Xr3"}}}`), }, - want: "1234", + wantProfile: "1234", + wantBody: []byte(`{"app":{"bundle":"com.pubmatic.openbid.app","name":"Sample","publisher":{"id":"156276"},"storeurl":"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098","ver":"1.0"},"at":1,"device":{"carrier":"MYTEL","connectiontype":2,"devicetype":4,"ext":{"atts":2},"geo":{"city":"Queens","country":"USA","dma":"501","ipservice":3,"lat":40.7429,"lon":-73.9392,"long":-73.9392,"metro":"501","region":"ny","type":2,"zip":"11101"},"h":2400,"hwv":"ruby","ifa":"497a10d6-c4dd-4e04-a986-c32b7180d462","ip":"38.158.207.171","js":1,"language":"en_US","make":"xiaomi","model":"22101316c","os":"android","osv":"13.0.0","ppi":440,"pxratio":2.75,"ua":"Mozilla/5.0 (Linux; Android 13; 22101316C Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.230 Mobile Safari/537.36","w":1080},"ext":{"prebid":{"bidderparams":{"pubmatic":{"wrapper":{"profileid":1234}}}}},"id":"95d6643c-3da6-40a2-b9ca-12279393ffbf","imp":[{"banner":{"format":[{"w":728,"h":90},{"w":320,"h":50}],"api":[5,2],"w":700,"h":900},"clickbrowser":0,"displaymanager":"OpenBid_SDK","displaymanagerver":"1.4.0","ext":{"reward":1},"id":"imp176227948","secure":0,"tagid":"OpenWrapBidderBannerAdUnit"}],"regs":{"ext":{"gdpr":0}},"source":{"ext":{"omidpn":"PubMatic","omidpv":"1.2.11-Pubmatic"}},"user":{"data":[{"id":"1","name":"Publisher Passed","segment":[{"signal":"{\"id\":\"95d6643c-3da6-40a2-b9ca-12279393ffbf\",\"at\":1,\"tmax\":500,\"cur\":[\"USD\"],\"imp\":[{\"id\":\"imp176227948\",\"clickbrowser\":0,\"displaymanager\":\"PubMatic_OpenBid_SDK\",\"displaymanagerver\":\"1.4.0\",\"tagid\":\"/43743431/DMDemo\",\"secure\":0,\"banner\":{\"pos\":7,\"format\":[{\"w\":300,\"h\":250}],\"api\":[5,6,7]},\"instl\":1}],\"app\":{\"name\":\"OpenWrapperSample\",\"bundle\":\"com.pubmatic.openbid.app\",\"storeurl\":\"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098?appnexus_banner_fixedbid=1&fixedbid=1\",\"ver\":\"1.0\",\"publisher\":{\"id\":\"5890\"}},\"device\":{\"ext\":{\"atts\":0},\"geo\":{\"type\":1,\"lat\":37.421998333333335,\"lon\":-122.08400000000002},\"pxratio\":2.625,\"mccmnc\":\"310-260\",\"lmt\":0,\"ifa\":\"07c387f2-e030-428f-8336-42f682150759\",\"connectiontype\":6,\"carrier\":\"Android\",\"js\":1,\"ua\":\"Mozilla/5.0(Linux;Android9;AndroidSDKbuiltforx86Build/PSR1.180720.075;wv)AppleWebKit/537.36(KHTML,likeGecko)Version/4.0Chrome/69.0.3497.100MobileSafari/537.36\",\"make\":\"Google\",\"model\":\"AndroidSDKbuiltforx86\",\"os\":\"Android\",\"osv\":\"9\",\"h\":1794,\"w\":1080,\"language\":\"en-US\",\"devicetype\":4},\"source\":{\"ext\":{\"omidpn\":\"PubMatic\",\"omidpv\":\"1.2.11-Pubmatic\"}},\"user\":{},\"ext\":{\"wrapper\":{\"ssauction\":0,\"sumry_disable\":0,\"profileid\":58135,\"versionid\":1,\"clientconfig\":1}}}"}]}],"ext":{"consent":"CP2KIMAP2KIgAEPgABBYJGNX_H__bX9j-Xr3"}}}`), }, { - name: "profileID not present in request.ext.prebid.bidderparams.pubmatic.wrapper but present in request.app.id", + name: "profileID not present in request.ext.prebid.bidderparams.pubmatic.wrapper but present in request.app.id, remove app.id", args: args{ requestBody: []byte(`{"app":{"bundle":"com.pubmatic.openbid.app","name":"Sample","publisher":{"id":"156276"},"id":"13137","storeurl":"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098","ver":"1.0"},"at":1,"device":{"carrier":"MYTEL","connectiontype":2,"devicetype":4,"ext":{"atts":2},"geo":{"city":"Queens","country":"USA","dma":"501","ipservice":3,"lat":40.7429,"lon":-73.9392,"long":-73.9392,"metro":"501","region":"ny","type":2,"zip":"11101"},"h":2400,"hwv":"ruby","ifa":"497a10d6-c4dd-4e04-a986-c32b7180d462","ip":"38.158.207.171","js":1,"language":"en_US","make":"xiaomi","model":"22101316c","os":"android","osv":"13.0.0","ppi":440,"pxratio":2.75,"ua":"Mozilla/5.0 (Linux; Android 13; 22101316C Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.230 Mobile Safari/537.36","w":1080},"ext":{"prebid":{"bidderparams":{"pubmatic":{"wrapper":{}}}}},"id":"95d6643c-3da6-40a2-b9ca-12279393ffbf","imp":[{"banner":{"format":[{"w":728,"h":90},{"w":320,"h":50}],"api":[5,2],"w":700,"h":900},"clickbrowser":0,"displaymanager":"OpenBid_SDK","displaymanagerver":"1.4.0","ext":{"reward":1},"id":"imp176227948","secure":0,"tagid":"OpenWrapBidderBannerAdUnit"}],"regs":{"ext":{"gdpr":0}},"source":{"ext":{"omidpn":"PubMatic","omidpv":"1.2.11-Pubmatic"}},"user":{"data":[{"id":"1","name":"Publisher Passed","segment":[{"signal":"{\"id\":\"95d6643c-3da6-40a2-b9ca-12279393ffbf\",\"at\":1,\"tmax\":500,\"cur\":[\"USD\"],\"imp\":[{\"id\":\"imp176227948\",\"clickbrowser\":0,\"displaymanager\":\"PubMatic_OpenBid_SDK\",\"displaymanagerver\":\"1.4.0\",\"tagid\":\"/43743431/DMDemo\",\"secure\":0,\"banner\":{\"pos\":7,\"format\":[{\"w\":300,\"h\":250}],\"api\":[5,6,7]},\"instl\":1}],\"app\":{\"name\":\"OpenWrapperSample\",\"bundle\":\"com.pubmatic.openbid.app\",\"storeurl\":\"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098?appnexus_banner_fixedbid=1&fixedbid=1\",\"ver\":\"1.0\",\"publisher\":{\"id\":\"5890\"}},\"device\":{\"ext\":{\"atts\":0},\"geo\":{\"type\":1,\"lat\":37.421998333333335,\"lon\":-122.08400000000002},\"pxratio\":2.625,\"mccmnc\":\"310-260\",\"lmt\":0,\"ifa\":\"07c387f2-e030-428f-8336-42f682150759\",\"connectiontype\":6,\"carrier\":\"Android\",\"js\":1,\"ua\":\"Mozilla/5.0(Linux;Android9;AndroidSDKbuiltforx86Build/PSR1.180720.075;wv)AppleWebKit/537.36(KHTML,likeGecko)Version/4.0Chrome/69.0.3497.100MobileSafari/537.36\",\"make\":\"Google\",\"model\":\"AndroidSDKbuiltforx86\",\"os\":\"Android\",\"osv\":\"9\",\"h\":1794,\"w\":1080,\"language\":\"en-US\",\"devicetype\":4},\"source\":{\"ext\":{\"omidpn\":\"PubMatic\",\"omidpv\":\"1.2.11-Pubmatic\"}},\"user\":{},\"ext\":{\"wrapper\":{\"ssauction\":0,\"sumry_disable\":0,\"profileid\":58135,\"versionid\":1,\"clientconfig\":1}}}"}]}],"ext":{"consent":"CP2KIMAP2KIgAEPgABBYJGNX_H__bX9j-Xr3"}}}`), }, - want: "13137", + wantProfile: "13137", + wantBody: []byte(`{"app":{"bundle":"com.pubmatic.openbid.app","name":"Sample","publisher":{"id":"156276"},"storeurl":"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098","ver":"1.0"},"at":1,"device":{"carrier":"MYTEL","connectiontype":2,"devicetype":4,"ext":{"atts":2},"geo":{"city":"Queens","country":"USA","dma":"501","ipservice":3,"lat":40.7429,"lon":-73.9392,"long":-73.9392,"metro":"501","region":"ny","type":2,"zip":"11101"},"h":2400,"hwv":"ruby","ifa":"497a10d6-c4dd-4e04-a986-c32b7180d462","ip":"38.158.207.171","js":1,"language":"en_US","make":"xiaomi","model":"22101316c","os":"android","osv":"13.0.0","ppi":440,"pxratio":2.75,"ua":"Mozilla/5.0 (Linux; Android 13; 22101316C Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.230 Mobile Safari/537.36","w":1080},"ext":{"prebid":{"bidderparams":{"pubmatic":{"wrapper":{"profileid":13137}}}}},"id":"95d6643c-3da6-40a2-b9ca-12279393ffbf","imp":[{"banner":{"format":[{"w":728,"h":90},{"w":320,"h":50}],"api":[5,2],"w":700,"h":900},"clickbrowser":0,"displaymanager":"OpenBid_SDK","displaymanagerver":"1.4.0","ext":{"reward":1},"id":"imp176227948","secure":0,"tagid":"OpenWrapBidderBannerAdUnit"}],"regs":{"ext":{"gdpr":0}},"source":{"ext":{"omidpn":"PubMatic","omidpv":"1.2.11-Pubmatic"}},"user":{"data":[{"id":"1","name":"Publisher Passed","segment":[{"signal":"{\"id\":\"95d6643c-3da6-40a2-b9ca-12279393ffbf\",\"at\":1,\"tmax\":500,\"cur\":[\"USD\"],\"imp\":[{\"id\":\"imp176227948\",\"clickbrowser\":0,\"displaymanager\":\"PubMatic_OpenBid_SDK\",\"displaymanagerver\":\"1.4.0\",\"tagid\":\"/43743431/DMDemo\",\"secure\":0,\"banner\":{\"pos\":7,\"format\":[{\"w\":300,\"h\":250}],\"api\":[5,6,7]},\"instl\":1}],\"app\":{\"name\":\"OpenWrapperSample\",\"bundle\":\"com.pubmatic.openbid.app\",\"storeurl\":\"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098?appnexus_banner_fixedbid=1&fixedbid=1\",\"ver\":\"1.0\",\"publisher\":{\"id\":\"5890\"}},\"device\":{\"ext\":{\"atts\":0},\"geo\":{\"type\":1,\"lat\":37.421998333333335,\"lon\":-122.08400000000002},\"pxratio\":2.625,\"mccmnc\":\"310-260\",\"lmt\":0,\"ifa\":\"07c387f2-e030-428f-8336-42f682150759\",\"connectiontype\":6,\"carrier\":\"Android\",\"js\":1,\"ua\":\"Mozilla/5.0(Linux;Android9;AndroidSDKbuiltforx86Build/PSR1.180720.075;wv)AppleWebKit/537.36(KHTML,likeGecko)Version/4.0Chrome/69.0.3497.100MobileSafari/537.36\",\"make\":\"Google\",\"model\":\"AndroidSDKbuiltforx86\",\"os\":\"Android\",\"osv\":\"9\",\"h\":1794,\"w\":1080,\"language\":\"en-US\",\"devicetype\":4},\"source\":{\"ext\":{\"omidpn\":\"PubMatic\",\"omidpv\":\"1.2.11-Pubmatic\"}},\"user\":{},\"ext\":{\"wrapper\":{\"ssauction\":0,\"sumry_disable\":0,\"profileid\":58135,\"versionid\":1,\"clientconfig\":1}}}"}]}],"ext":{"consent":"CP2KIMAP2KIgAEPgABBYJGNX_H__bX9j-Xr3"}}}`), }, { name: "profileID present in both request.ext.prebid.bidderparams.pubmatic.wrapper and request.app.id give priority to wrapper", args: args{ requestBody: []byte(`{"app":{"bundle":"com.pubmatic.openbid.app","name":"Sample","publisher":{"id":"156276"},"id":"13137","storeurl":"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098","ver":"1.0"},"at":1,"device":{"carrier":"MYTEL","connectiontype":2,"devicetype":4,"ext":{"atts":2},"geo":{"city":"Queens","country":"USA","dma":"501","ipservice":3,"lat":40.7429,"lon":-73.9392,"long":-73.9392,"metro":"501","region":"ny","type":2,"zip":"11101"},"h":2400,"hwv":"ruby","ifa":"497a10d6-c4dd-4e04-a986-c32b7180d462","ip":"38.158.207.171","js":1,"language":"en_US","make":"xiaomi","model":"22101316c","os":"android","osv":"13.0.0","ppi":440,"pxratio":2.75,"ua":"Mozilla/5.0 (Linux; Android 13; 22101316C Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.230 Mobile Safari/537.36","w":1080},"ext":{"prebid":{"bidderparams":{"pubmatic":{"wrapper":{"profileid":1234}}}}},"id":"95d6643c-3da6-40a2-b9ca-12279393ffbf","imp":[{"banner":{"format":[{"w":728,"h":90},{"w":320,"h":50}],"api":[5,2],"w":700,"h":900},"clickbrowser":0,"displaymanager":"OpenBid_SDK","displaymanagerver":"1.4.0","ext":{"reward":1},"id":"imp176227948","secure":0,"tagid":"OpenWrapBidderBannerAdUnit"}],"regs":{"ext":{"gdpr":0}},"source":{"ext":{"omidpn":"PubMatic","omidpv":"1.2.11-Pubmatic"}},"user":{"data":[{"id":"1","name":"Publisher Passed","segment":[{"signal":"{\"id\":\"95d6643c-3da6-40a2-b9ca-12279393ffbf\",\"at\":1,\"tmax\":500,\"cur\":[\"USD\"],\"imp\":[{\"id\":\"imp176227948\",\"clickbrowser\":0,\"displaymanager\":\"PubMatic_OpenBid_SDK\",\"displaymanagerver\":\"1.4.0\",\"tagid\":\"/43743431/DMDemo\",\"secure\":0,\"banner\":{\"pos\":7,\"format\":[{\"w\":300,\"h\":250}],\"api\":[5,6,7]},\"instl\":1}],\"app\":{\"name\":\"OpenWrapperSample\",\"bundle\":\"com.pubmatic.openbid.app\",\"storeurl\":\"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098?appnexus_banner_fixedbid=1&fixedbid=1\",\"ver\":\"1.0\",\"publisher\":{\"id\":\"5890\"}},\"device\":{\"ext\":{\"atts\":0},\"geo\":{\"type\":1,\"lat\":37.421998333333335,\"lon\":-122.08400000000002},\"pxratio\":2.625,\"mccmnc\":\"310-260\",\"lmt\":0,\"ifa\":\"07c387f2-e030-428f-8336-42f682150759\",\"connectiontype\":6,\"carrier\":\"Android\",\"js\":1,\"ua\":\"Mozilla/5.0(Linux;Android9;AndroidSDKbuiltforx86Build/PSR1.180720.075;wv)AppleWebKit/537.36(KHTML,likeGecko)Version/4.0Chrome/69.0.3497.100MobileSafari/537.36\",\"make\":\"Google\",\"model\":\"AndroidSDKbuiltforx86\",\"os\":\"Android\",\"osv\":\"9\",\"h\":1794,\"w\":1080,\"language\":\"en-US\",\"devicetype\":4},\"source\":{\"ext\":{\"omidpn\":\"PubMatic\",\"omidpv\":\"1.2.11-Pubmatic\"}},\"user\":{},\"ext\":{\"wrapper\":{\"ssauction\":0,\"sumry_disable\":0,\"profileid\":58135,\"versionid\":1,\"clientconfig\":1}}}"}]}],"ext":{"consent":"CP2KIMAP2KIgAEPgABBYJGNX_H__bX9j-Xr3"}}}`), }, - want: "1234", + wantProfile: "1234", + wantBody: []byte(`{"app":{"bundle":"com.pubmatic.openbid.app","name":"Sample","publisher":{"id":"156276"},"id":"13137","storeurl":"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098","ver":"1.0"},"at":1,"device":{"carrier":"MYTEL","connectiontype":2,"devicetype":4,"ext":{"atts":2},"geo":{"city":"Queens","country":"USA","dma":"501","ipservice":3,"lat":40.7429,"lon":-73.9392,"long":-73.9392,"metro":"501","region":"ny","type":2,"zip":"11101"},"h":2400,"hwv":"ruby","ifa":"497a10d6-c4dd-4e04-a986-c32b7180d462","ip":"38.158.207.171","js":1,"language":"en_US","make":"xiaomi","model":"22101316c","os":"android","osv":"13.0.0","ppi":440,"pxratio":2.75,"ua":"Mozilla/5.0 (Linux; Android 13; 22101316C Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.230 Mobile Safari/537.36","w":1080},"ext":{"prebid":{"bidderparams":{"pubmatic":{"wrapper":{"profileid":1234}}}}},"id":"95d6643c-3da6-40a2-b9ca-12279393ffbf","imp":[{"banner":{"format":[{"w":728,"h":90},{"w":320,"h":50}],"api":[5,2],"w":700,"h":900},"clickbrowser":0,"displaymanager":"OpenBid_SDK","displaymanagerver":"1.4.0","ext":{"reward":1},"id":"imp176227948","secure":0,"tagid":"OpenWrapBidderBannerAdUnit"}],"regs":{"ext":{"gdpr":0}},"source":{"ext":{"omidpn":"PubMatic","omidpv":"1.2.11-Pubmatic"}},"user":{"data":[{"id":"1","name":"Publisher Passed","segment":[{"signal":"{\"id\":\"95d6643c-3da6-40a2-b9ca-12279393ffbf\",\"at\":1,\"tmax\":500,\"cur\":[\"USD\"],\"imp\":[{\"id\":\"imp176227948\",\"clickbrowser\":0,\"displaymanager\":\"PubMatic_OpenBid_SDK\",\"displaymanagerver\":\"1.4.0\",\"tagid\":\"/43743431/DMDemo\",\"secure\":0,\"banner\":{\"pos\":7,\"format\":[{\"w\":300,\"h\":250}],\"api\":[5,6,7]},\"instl\":1}],\"app\":{\"name\":\"OpenWrapperSample\",\"bundle\":\"com.pubmatic.openbid.app\",\"storeurl\":\"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098?appnexus_banner_fixedbid=1&fixedbid=1\",\"ver\":\"1.0\",\"publisher\":{\"id\":\"5890\"}},\"device\":{\"ext\":{\"atts\":0},\"geo\":{\"type\":1,\"lat\":37.421998333333335,\"lon\":-122.08400000000002},\"pxratio\":2.625,\"mccmnc\":\"310-260\",\"lmt\":0,\"ifa\":\"07c387f2-e030-428f-8336-42f682150759\",\"connectiontype\":6,\"carrier\":\"Android\",\"js\":1,\"ua\":\"Mozilla/5.0(Linux;Android9;AndroidSDKbuiltforx86Build/PSR1.180720.075;wv)AppleWebKit/537.36(KHTML,likeGecko)Version/4.0Chrome/69.0.3497.100MobileSafari/537.36\",\"make\":\"Google\",\"model\":\"AndroidSDKbuiltforx86\",\"os\":\"Android\",\"osv\":\"9\",\"h\":1794,\"w\":1080,\"language\":\"en-US\",\"devicetype\":4},\"source\":{\"ext\":{\"omidpn\":\"PubMatic\",\"omidpv\":\"1.2.11-Pubmatic\"}},\"user\":{},\"ext\":{\"wrapper\":{\"ssauction\":0,\"sumry_disable\":0,\"profileid\":58135,\"versionid\":1,\"clientconfig\":1}}}"}]}],"ext":{"consent":"CP2KIMAP2KIgAEPgABBYJGNX_H__bX9j-Xr3"}}}`), }, { name: "profileID not present in both request.ext.prebid.bidderparams.pubmatic.wrapper and request.app.id", args: args{ requestBody: []byte(`{"app":{"bundle":"com.pubmatic.openbid.app","name":"Sample","publisher":{"id":"156276"},"storeurl":"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098","ver":"1.0"},"at":1,"device":{"carrier":"MYTEL","connectiontype":2,"devicetype":4,"ext":{"atts":2},"geo":{"city":"Queens","country":"USA","dma":"501","ipservice":3,"lat":40.7429,"lon":-73.9392,"long":-73.9392,"metro":"501","region":"ny","type":2,"zip":"11101"},"h":2400,"hwv":"ruby","ifa":"497a10d6-c4dd-4e04-a986-c32b7180d462","ip":"38.158.207.171","js":1,"language":"en_US","make":"xiaomi","model":"22101316c","os":"android","osv":"13.0.0","ppi":440,"pxratio":2.75,"ua":"Mozilla/5.0 (Linux; Android 13; 22101316C Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.230 Mobile Safari/537.36","w":1080},"ext":{"prebid":{"bidderparams":{"pubmatic":{"wrapper":{}}}}},"id":"95d6643c-3da6-40a2-b9ca-12279393ffbf","imp":[{"banner":{"format":[{"w":728,"h":90},{"w":320,"h":50}],"api":[5,2],"w":700,"h":900},"clickbrowser":0,"displaymanager":"OpenBid_SDK","displaymanagerver":"1.4.0","ext":{"reward":1},"id":"imp176227948","secure":0,"tagid":"OpenWrapBidderBannerAdUnit"}],"regs":{"ext":{"gdpr":0}},"source":{"ext":{"omidpn":"PubMatic","omidpv":"1.2.11-Pubmatic"}},"user":{"data":[{"id":"1","name":"Publisher Passed","segment":[{"signal":"{\"id\":\"95d6643c-3da6-40a2-b9ca-12279393ffbf\",\"at\":1,\"tmax\":500,\"cur\":[\"USD\"],\"imp\":[{\"id\":\"imp176227948\",\"clickbrowser\":0,\"displaymanager\":\"PubMatic_OpenBid_SDK\",\"displaymanagerver\":\"1.4.0\",\"tagid\":\"/43743431/DMDemo\",\"secure\":0,\"banner\":{\"pos\":7,\"format\":[{\"w\":300,\"h\":250}],\"api\":[5,6,7]},\"instl\":1}],\"app\":{\"name\":\"OpenWrapperSample\",\"bundle\":\"com.pubmatic.openbid.app\",\"storeurl\":\"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098?appnexus_banner_fixedbid=1&fixedbid=1\",\"ver\":\"1.0\",\"publisher\":{\"id\":\"5890\"}},\"device\":{\"ext\":{\"atts\":0},\"geo\":{\"type\":1,\"lat\":37.421998333333335,\"lon\":-122.08400000000002},\"pxratio\":2.625,\"mccmnc\":\"310-260\",\"lmt\":0,\"ifa\":\"07c387f2-e030-428f-8336-42f682150759\",\"connectiontype\":6,\"carrier\":\"Android\",\"js\":1,\"ua\":\"Mozilla/5.0(Linux;Android9;AndroidSDKbuiltforx86Build/PSR1.180720.075;wv)AppleWebKit/537.36(KHTML,likeGecko)Version/4.0Chrome/69.0.3497.100MobileSafari/537.36\",\"make\":\"Google\",\"model\":\"AndroidSDKbuiltforx86\",\"os\":\"Android\",\"osv\":\"9\",\"h\":1794,\"w\":1080,\"language\":\"en-US\",\"devicetype\":4},\"source\":{\"ext\":{\"omidpn\":\"PubMatic\",\"omidpv\":\"1.2.11-Pubmatic\"}},\"user\":{},\"ext\":{\"wrapper\":{\"ssauction\":0,\"sumry_disable\":0,\"profileid\":58135,\"versionid\":1,\"clientconfig\":1}}}"}]}],"ext":{"consent":"CP2KIMAP2KIgAEPgABBYJGNX_H__bX9j-Xr3"}}}`), }, - want: "", + wantProfile: "", + wantBody: []byte(`{"app":{"bundle":"com.pubmatic.openbid.app","name":"Sample","publisher":{"id":"156276"},"storeurl":"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098","ver":"1.0"},"at":1,"device":{"carrier":"MYTEL","connectiontype":2,"devicetype":4,"ext":{"atts":2},"geo":{"city":"Queens","country":"USA","dma":"501","ipservice":3,"lat":40.7429,"lon":-73.9392,"long":-73.9392,"metro":"501","region":"ny","type":2,"zip":"11101"},"h":2400,"hwv":"ruby","ifa":"497a10d6-c4dd-4e04-a986-c32b7180d462","ip":"38.158.207.171","js":1,"language":"en_US","make":"xiaomi","model":"22101316c","os":"android","osv":"13.0.0","ppi":440,"pxratio":2.75,"ua":"Mozilla/5.0 (Linux; Android 13; 22101316C Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.230 Mobile Safari/537.36","w":1080},"ext":{"prebid":{"bidderparams":{"pubmatic":{"wrapper":{}}}}},"id":"95d6643c-3da6-40a2-b9ca-12279393ffbf","imp":[{"banner":{"format":[{"w":728,"h":90},{"w":320,"h":50}],"api":[5,2],"w":700,"h":900},"clickbrowser":0,"displaymanager":"OpenBid_SDK","displaymanagerver":"1.4.0","ext":{"reward":1},"id":"imp176227948","secure":0,"tagid":"OpenWrapBidderBannerAdUnit"}],"regs":{"ext":{"gdpr":0}},"source":{"ext":{"omidpn":"PubMatic","omidpv":"1.2.11-Pubmatic"}},"user":{"data":[{"id":"1","name":"Publisher Passed","segment":[{"signal":"{\"id\":\"95d6643c-3da6-40a2-b9ca-12279393ffbf\",\"at\":1,\"tmax\":500,\"cur\":[\"USD\"],\"imp\":[{\"id\":\"imp176227948\",\"clickbrowser\":0,\"displaymanager\":\"PubMatic_OpenBid_SDK\",\"displaymanagerver\":\"1.4.0\",\"tagid\":\"/43743431/DMDemo\",\"secure\":0,\"banner\":{\"pos\":7,\"format\":[{\"w\":300,\"h\":250}],\"api\":[5,6,7]},\"instl\":1}],\"app\":{\"name\":\"OpenWrapperSample\",\"bundle\":\"com.pubmatic.openbid.app\",\"storeurl\":\"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098?appnexus_banner_fixedbid=1&fixedbid=1\",\"ver\":\"1.0\",\"publisher\":{\"id\":\"5890\"}},\"device\":{\"ext\":{\"atts\":0},\"geo\":{\"type\":1,\"lat\":37.421998333333335,\"lon\":-122.08400000000002},\"pxratio\":2.625,\"mccmnc\":\"310-260\",\"lmt\":0,\"ifa\":\"07c387f2-e030-428f-8336-42f682150759\",\"connectiontype\":6,\"carrier\":\"Android\",\"js\":1,\"ua\":\"Mozilla/5.0(Linux;Android9;AndroidSDKbuiltforx86Build/PSR1.180720.075;wv)AppleWebKit/537.36(KHTML,likeGecko)Version/4.0Chrome/69.0.3497.100MobileSafari/537.36\",\"make\":\"Google\",\"model\":\"AndroidSDKbuiltforx86\",\"os\":\"Android\",\"osv\":\"9\",\"h\":1794,\"w\":1080,\"language\":\"en-US\",\"devicetype\":4},\"source\":{\"ext\":{\"omidpn\":\"PubMatic\",\"omidpv\":\"1.2.11-Pubmatic\"}},\"user\":{},\"ext\":{\"wrapper\":{\"ssauction\":0,\"sumry_disable\":0,\"profileid\":58135,\"versionid\":1,\"clientconfig\":1}}}"}]}],"ext":{"consent":"CP2KIMAP2KIgAEPgABBYJGNX_H__bX9j-Xr3"}}}`), + }, + { + name: "profileID not present in request.ext.prebid.bidderparams.pubmatic.wrapper but invalid present in request.app.id", + args: args{ + requestBody: []byte(`{"app":{"bundle":"com.pubmatic.openbid.app","name":"Sample","publisher":{"id":"156276"},"id":"abcde","storeurl":"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098","ver":"1.0"},"at":1,"device":{"carrier":"MYTEL","connectiontype":2,"devicetype":4,"ext":{"atts":2},"geo":{"city":"Queens","country":"USA","dma":"501","ipservice":3,"lat":40.7429,"lon":-73.9392,"long":-73.9392,"metro":"501","region":"ny","type":2,"zip":"11101"},"h":2400,"hwv":"ruby","ifa":"497a10d6-c4dd-4e04-a986-c32b7180d462","ip":"38.158.207.171","js":1,"language":"en_US","make":"xiaomi","model":"22101316c","os":"android","osv":"13.0.0","ppi":440,"pxratio":2.75,"ua":"Mozilla/5.0 (Linux; Android 13; 22101316C Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.230 Mobile Safari/537.36","w":1080},"ext":{"prebid":{"bidderparams":{"pubmatic":{"wrapper":{}}}}},"id":"95d6643c-3da6-40a2-b9ca-12279393ffbf","imp":[{"banner":{"format":[{"w":728,"h":90},{"w":320,"h":50}],"api":[5,2],"w":700,"h":900},"clickbrowser":0,"displaymanager":"OpenBid_SDK","displaymanagerver":"1.4.0","ext":{"reward":1},"id":"imp176227948","secure":0,"tagid":"OpenWrapBidderBannerAdUnit"}],"regs":{"ext":{"gdpr":0}},"source":{"ext":{"omidpn":"PubMatic","omidpv":"1.2.11-Pubmatic"}},"user":{"data":[{"id":"1","name":"Publisher Passed","segment":[{"signal":"{\"id\":\"95d6643c-3da6-40a2-b9ca-12279393ffbf\",\"at\":1,\"tmax\":500,\"cur\":[\"USD\"],\"imp\":[{\"id\":\"imp176227948\",\"clickbrowser\":0,\"displaymanager\":\"PubMatic_OpenBid_SDK\",\"displaymanagerver\":\"1.4.0\",\"tagid\":\"/43743431/DMDemo\",\"secure\":0,\"banner\":{\"pos\":7,\"format\":[{\"w\":300,\"h\":250}],\"api\":[5,6,7]},\"instl\":1}],\"app\":{\"name\":\"OpenWrapperSample\",\"bundle\":\"com.pubmatic.openbid.app\",\"storeurl\":\"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098?appnexus_banner_fixedbid=1&fixedbid=1\",\"ver\":\"1.0\",\"publisher\":{\"id\":\"5890\"}},\"device\":{\"ext\":{\"atts\":0},\"geo\":{\"type\":1,\"lat\":37.421998333333335,\"lon\":-122.08400000000002},\"pxratio\":2.625,\"mccmnc\":\"310-260\",\"lmt\":0,\"ifa\":\"07c387f2-e030-428f-8336-42f682150759\",\"connectiontype\":6,\"carrier\":\"Android\",\"js\":1,\"ua\":\"Mozilla/5.0(Linux;Android9;AndroidSDKbuiltforx86Build/PSR1.180720.075;wv)AppleWebKit/537.36(KHTML,likeGecko)Version/4.0Chrome/69.0.3497.100MobileSafari/537.36\",\"make\":\"Google\",\"model\":\"AndroidSDKbuiltforx86\",\"os\":\"Android\",\"osv\":\"9\",\"h\":1794,\"w\":1080,\"language\":\"en-US\",\"devicetype\":4},\"source\":{\"ext\":{\"omidpn\":\"PubMatic\",\"omidpv\":\"1.2.11-Pubmatic\"}},\"user\":{},\"ext\":{\"wrapper\":{\"ssauction\":0,\"sumry_disable\":0,\"profileid\":58135,\"versionid\":1,\"clientconfig\":1}}}"}]}],"ext":{"consent":"CP2KIMAP2KIgAEPgABBYJGNX_H__bX9j-Xr3"}}}`), + }, + wantProfile: "", + wantBody: []byte(`{"app":{"bundle":"com.pubmatic.openbid.app","name":"Sample","publisher":{"id":"156276"},"id":"abcde","storeurl":"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098","ver":"1.0"},"at":1,"device":{"carrier":"MYTEL","connectiontype":2,"devicetype":4,"ext":{"atts":2},"geo":{"city":"Queens","country":"USA","dma":"501","ipservice":3,"lat":40.7429,"lon":-73.9392,"long":-73.9392,"metro":"501","region":"ny","type":2,"zip":"11101"},"h":2400,"hwv":"ruby","ifa":"497a10d6-c4dd-4e04-a986-c32b7180d462","ip":"38.158.207.171","js":1,"language":"en_US","make":"xiaomi","model":"22101316c","os":"android","osv":"13.0.0","ppi":440,"pxratio":2.75,"ua":"Mozilla/5.0 (Linux; Android 13; 22101316C Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.230 Mobile Safari/537.36","w":1080},"ext":{"prebid":{"bidderparams":{"pubmatic":{"wrapper":{}}}}},"id":"95d6643c-3da6-40a2-b9ca-12279393ffbf","imp":[{"banner":{"format":[{"w":728,"h":90},{"w":320,"h":50}],"api":[5,2],"w":700,"h":900},"clickbrowser":0,"displaymanager":"OpenBid_SDK","displaymanagerver":"1.4.0","ext":{"reward":1},"id":"imp176227948","secure":0,"tagid":"OpenWrapBidderBannerAdUnit"}],"regs":{"ext":{"gdpr":0}},"source":{"ext":{"omidpn":"PubMatic","omidpv":"1.2.11-Pubmatic"}},"user":{"data":[{"id":"1","name":"Publisher Passed","segment":[{"signal":"{\"id\":\"95d6643c-3da6-40a2-b9ca-12279393ffbf\",\"at\":1,\"tmax\":500,\"cur\":[\"USD\"],\"imp\":[{\"id\":\"imp176227948\",\"clickbrowser\":0,\"displaymanager\":\"PubMatic_OpenBid_SDK\",\"displaymanagerver\":\"1.4.0\",\"tagid\":\"/43743431/DMDemo\",\"secure\":0,\"banner\":{\"pos\":7,\"format\":[{\"w\":300,\"h\":250}],\"api\":[5,6,7]},\"instl\":1}],\"app\":{\"name\":\"OpenWrapperSample\",\"bundle\":\"com.pubmatic.openbid.app\",\"storeurl\":\"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098?appnexus_banner_fixedbid=1&fixedbid=1\",\"ver\":\"1.0\",\"publisher\":{\"id\":\"5890\"}},\"device\":{\"ext\":{\"atts\":0},\"geo\":{\"type\":1,\"lat\":37.421998333333335,\"lon\":-122.08400000000002},\"pxratio\":2.625,\"mccmnc\":\"310-260\",\"lmt\":0,\"ifa\":\"07c387f2-e030-428f-8336-42f682150759\",\"connectiontype\":6,\"carrier\":\"Android\",\"js\":1,\"ua\":\"Mozilla/5.0(Linux;Android9;AndroidSDKbuiltforx86Build/PSR1.180720.075;wv)AppleWebKit/537.36(KHTML,likeGecko)Version/4.0Chrome/69.0.3497.100MobileSafari/537.36\",\"make\":\"Google\",\"model\":\"AndroidSDKbuiltforx86\",\"os\":\"Android\",\"osv\":\"9\",\"h\":1794,\"w\":1080,\"language\":\"en-US\",\"devicetype\":4},\"source\":{\"ext\":{\"omidpn\":\"PubMatic\",\"omidpv\":\"1.2.11-Pubmatic\"}},\"user\":{},\"ext\":{\"wrapper\":{\"ssauction\":0,\"sumry_disable\":0,\"profileid\":58135,\"versionid\":1,\"clientconfig\":1}}}"}]}],"ext":{"consent":"CP2KIMAP2KIgAEPgABBYJGNX_H__bX9j-Xr3"}}}`), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := getProfileID(tt.args.requestBody) - assert.Equal(t, tt.want, got) + got, got1 := setProfileID(tt.args.requestBody) + assert.Equal(t, tt.wantBody, got) + assert.Equal(t, tt.wantProfile, got1) }) } } diff --git a/modules/pubmatic/openwrap/auctionresponsehook.go b/modules/pubmatic/openwrap/auctionresponsehook.go index cacda092cfa..481842d19ad 100644 --- a/modules/pubmatic/openwrap/auctionresponsehook.go +++ b/modules/pubmatic/openwrap/auctionresponsehook.go @@ -327,6 +327,7 @@ func (m OpenWrap) handleAuctionResponseHook( rctx.ResponseExt = responseExt rctx.DefaultBids = m.addDefaultBids(&rctx, payload.BidResponse, responseExt) + rctx.DefaultBids = m.addDefaultBidsForMultiFloorsConfig(&rctx, payload.BidResponse, responseExt) rctx.Trackers = tracker.CreateTrackers(rctx, payload.BidResponse) diff --git a/modules/pubmatic/openwrap/auctionresponsehook_test.go b/modules/pubmatic/openwrap/auctionresponsehook_test.go index 55d11e8c552..a8d7ea1ab9b 100644 --- a/modules/pubmatic/openwrap/auctionresponsehook_test.go +++ b/modules/pubmatic/openwrap/auctionresponsehook_test.go @@ -1724,6 +1724,9 @@ func TestOpenWrapHandleAuctionResponseHook(t *testing.T) { Banner: false, }, }, + PrebidBidderCode: map[string]string{ + "pubmatic": "pubmatic", + }, BidderResponseTimeMillis: map[string]int{}, SeatNonBids: map[string][]openrtb_ext.NonBid{}, ReturnAllBidStatus: true, @@ -1757,7 +1760,7 @@ func TestOpenWrapHandleAuctionResponseHook(t *testing.T) { ImpID: "Div1", Price: 5, AdM: "", - Ext: json.RawMessage(`{"bidtype":0,"deal_channel":1,"dspid":6,"origbidcpm":8,"origbidcur":"USD","prebid":{"bidid":"bb57a9e3-fdc2-4772-8071-112dd7f50a6a","meta":{"adaptercode":"pubmatic","advertiserId":4098,"agencyId":4098,"demandSource":"6","mediaType":"banner","networkId":6},"targeting":{"hb_bidder_pubmatic":"pubmatic","hb_deal_pubmatic":"PUBDEAL1","hb_pb_pubmatic":"8.00","hb_size_pubmatic":"728x90"},"type":"video","video":{"duration":0,"primary_category":"","vasttagid":""}}}`), + Ext: json.RawMessage(`{"bidtype":0,"deal_channel":1,"dspid":6,"mbmfv":4,"origbidcpm":8,"origbidcur":"USD","prebid":{"bidid":"bb57a9e3-fdc2-4772-8071-112dd7f50a6a","meta":{"adaptercode":"pubmatic","advertiserId":4098,"agencyId":4098,"demandSource":"6","mediaType":"banner","networkId":6},"targeting":{"hb_bidder_pubmatic":"pubmatic","hb_deal_pubmatic":"PUBDEAL1","hb_pb_pubmatic":"8.00","hb_size_pubmatic":"728x90"},"type":"video","video":{"duration":0,"primary_category":"","vasttagid":""}}}`), }, }, Seat: "pubmatic", @@ -1781,7 +1784,7 @@ func TestOpenWrapHandleAuctionResponseHook(t *testing.T) { want: want{ result: hookstage.HookResult[hookstage.AuctionResponsePayload]{}, err: nil, - bidResponse: json.RawMessage(`{"id":"12345","seatbid":[{"bid":[{"id":"bid-id-1","impid":"Div1","price":5,"adm":"\u003cVAST version=\"3.0\"\u003e\u003cAd\u003e\u003cWrapper\u003e\u003cImpression\u003e\u003c![CDATA[https:?adv=\u0026af=video\u0026aps=0\u0026au=\u0026bc=pubmatic\u0026bidid=bb57a9e3-fdc2-4772-8071-112dd7f50a6a\u0026di=-1\u0026dur=20\u0026eg=0\u0026en=0\u0026ft=0\u0026iid=\u0026kgpv=\u0026orig=\u0026origbidid=bid-id-1\u0026pdvid=0\u0026pid=0\u0026plt=0\u0026pn=pubmatic\u0026psz=0x0\u0026pubid=5890\u0026purl=\u0026sl=1\u0026slot=\u0026ss=1\u0026tgid=0\u0026tst=0]]\u003e\u003c/Impression\u003e\u003cExtensions\u003e\u003cExtension\u003e\u003cPricing model=\"CPM\" currency=\"USD\"\u003e\u003c![CDATA[5]]\u003e\u003c/Pricing\u003e\u003c/Extension\u003e\u003c/Extensions\u003e\u003c/Wrapper\u003e\u003c/Ad\u003e\u003c/VAST\u003e","ext":{"prebid":{"meta":{"adaptercode":"pubmatic","advertiserId":4098,"agencyId":4098,"demandSource":"6","mediaType":"banner","networkId":6},"type":"video","video":{"duration":20,"primary_category":"","vasttagid":""},"bidid":"bb57a9e3-fdc2-4772-8071-112dd7f50a6a"},"refreshInterval":30,"crtype":"video","video":{"minduration":10,"maxduration":20,"skip":1,"skipmin":1,"skipafter":2,"battr":[1],"playbackmethod":[1]},"dspid":6,"netecpm":5,"origbidcpm":8,"origbidcur":"USD"}}],"seat":"pubmatic"}],"ext":{"responsetimemillis":{"pubmatic":8},"matchedimpression":{"pubmatic":0}}}`), + bidResponse: json.RawMessage(`{"id":"12345","seatbid":[{"bid":[{"id":"bid-id-1","impid":"Div1","price":5,"adm":"\u003cVAST version=\"3.0\"\u003e\u003cAd\u003e\u003cWrapper\u003e\u003cImpression\u003e\u003c![CDATA[https:?adv=\u0026af=video\u0026aps=0\u0026au=\u0026bc=pubmatic\u0026bidid=bb57a9e3-fdc2-4772-8071-112dd7f50a6a\u0026di=-1\u0026dur=20\u0026eg=0\u0026en=0\u0026frv=4\u0026ft=0\u0026fv=4\u0026iid=\u0026kgpv=\u0026orig=\u0026origbidid=bid-id-1\u0026pdvid=0\u0026pid=0\u0026plt=0\u0026pn=pubmatic\u0026psz=0x0\u0026pubid=5890\u0026purl=\u0026sl=1\u0026slot=\u0026ss=1\u0026tgid=0\u0026tst=0]]\u003e\u003c/Impression\u003e\u003cExtensions\u003e\u003cExtension\u003e\u003cPricing model=\"CPM\" currency=\"USD\"\u003e\u003c![CDATA[5]]\u003e\u003c/Pricing\u003e\u003c/Extension\u003e\u003c/Extensions\u003e\u003c/Wrapper\u003e\u003c/Ad\u003e\u003c/VAST\u003e","ext":{"prebid":{"meta":{"adaptercode":"pubmatic","advertiserId":4098,"agencyId":4098,"demandSource":"6","mediaType":"banner","networkId":6},"type":"video","video":{"duration":20,"primary_category":"","vasttagid":""},"bidid":"bb57a9e3-fdc2-4772-8071-112dd7f50a6a"},"refreshInterval":30,"crtype":"video","video":{"minduration":10,"maxduration":20,"skip":1,"skipmin":1,"skipafter":2,"battr":[1],"playbackmethod":[1]},"dspid":6,"netecpm":5,"origbidcpm":8,"origbidcur":"USD","mbmfv":4}}],"seat":"pubmatic"}],"ext":{"responsetimemillis":{"pubmatic":8},"matchedimpression":{"pubmatic":0}}}`), }, }, } diff --git a/modules/pubmatic/openwrap/beforevalidationhook.go b/modules/pubmatic/openwrap/beforevalidationhook.go index 9da5cc366f3..332756ad242 100644 --- a/modules/pubmatic/openwrap/beforevalidationhook.go +++ b/modules/pubmatic/openwrap/beforevalidationhook.go @@ -54,7 +54,7 @@ func (m OpenWrap) handleBeforeValidationHook( defer func() { moduleCtx.ModuleContext["rctx"] = rCtx if result.Reject { - m.metricEngine.RecordBadRequests(rCtx.Endpoint, getPubmaticErrorCode(openrtb3.NoBidReason(result.NbrCode))) + m.metricEngine.RecordBadRequests(rCtx.Endpoint, rCtx.PubIDStr, getPubmaticErrorCode(openrtb3.NoBidReason(result.NbrCode))) m.metricEngine.RecordNobidErrPrebidServerRequests(rCtx.PubIDStr, result.NbrCode) if rCtx.IsCTVRequest { m.metricEngine.RecordCTVInvalidReasonCount(getPubmaticErrorCode(openrtb3.NoBidReason(result.NbrCode)), rCtx.PubIDStr) @@ -86,7 +86,7 @@ func (m OpenWrap) handleBeforeValidationHook( // return prebid validation error if len(payload.BidRequest.Imp) == 0 || (payload.BidRequest.Site == nil && payload.BidRequest.App == nil) { result.Reject = false - m.metricEngine.RecordBadRequests(rCtx.Endpoint, getPubmaticErrorCode(nbr.InvalidRequestExt)) + m.metricEngine.RecordBadRequests(rCtx.Endpoint, rCtx.PubIDStr, getPubmaticErrorCode(nbr.InvalidRequestExt)) m.metricEngine.RecordNobidErrPrebidServerRequests(rCtx.PubIDStr, int(nbr.InvalidRequestExt)) return result, nil } @@ -139,7 +139,6 @@ func (m OpenWrap) handleBeforeValidationHook( } rCtx.ImpBidCtx = getDefaultImpBidCtx(*payload.BidRequest) // for wrapper logger sz m.metricEngine.RecordPublisherInvalidProfileRequests(rCtx.Endpoint, rCtx.PubIDStr, rCtx.ProfileIDStr) - m.metricEngine.RecordPublisherInvalidProfileImpressions(rCtx.PubIDStr, rCtx.ProfileIDStr, len(payload.BidRequest.Imp)) return result, err } @@ -190,7 +189,6 @@ func (m OpenWrap) handleBeforeValidationHook( result.NbrCode = int(nbr.InvalidPlatform) rCtx.ImpBidCtx = getDefaultImpBidCtx(*payload.BidRequest) // for wrapper logger sz m.metricEngine.RecordPublisherInvalidProfileRequests(rCtx.Endpoint, rCtx.PubIDStr, rCtx.ProfileIDStr) - m.metricEngine.RecordPublisherInvalidProfileImpressions(rCtx.PubIDStr, rCtx.ProfileIDStr, len(payload.BidRequest.Imp)) return result, errors.New("failed to get platform data") } rCtx.Platform = platform @@ -468,7 +466,7 @@ func (m OpenWrap) handleBeforeValidationHook( // prebidBidderCode is equivalent of PBS-Core's bidderCode prebidBidderCode := partnerConfig[models.PREBID_PARTNER_NAME] // - rCtx.PrebidBidderCode[prebidBidderCode] = bidderCode + rCtx.PrebidBidderCode[bidderCode] = prebidBidderCode if _, ok := rCtx.AdapterFilteredMap[bidderCode]; ok { result.Warnings = append(result.Warnings, "Dropping adapter due to bidder filtering: "+bidderCode) @@ -569,7 +567,10 @@ func (m OpenWrap) handleBeforeValidationHook( } if rCtx.Endpoint == models.EndpointAppLovinMax && payload.BidRequest.App != nil && payload.BidRequest.App.StoreURL == "" { - rCtx.AppLovinMax.AppStoreUrl = getProfileAppStoreUrlAndUpdateItunesID(rCtx, payload.BidRequest, impExt) + var isValidAppStoreUrl bool + if rCtx.AppLovinMax.AppStoreUrl, isValidAppStoreUrl = getProfileAppStoreUrl(rCtx); isValidAppStoreUrl { + m.updateSkadnSourceapp(rCtx, payload.BidRequest, impExt) + } rCtx.PageURL = rCtx.AppLovinMax.AppStoreUrl } impExt.Wrapper = nil @@ -650,13 +651,13 @@ func (m OpenWrap) handleBeforeValidationHook( requestExt.Prebid.AliasGVLIDs = aliasgvlids if _, ok := rCtx.AdapterThrottleMap[string(openrtb_ext.BidderPubmatic)]; !ok { - requestExt.Prebid.BidderParams, _ = updateRequestExtBidderParamsPubmatic(requestExt.Prebid.BidderParams, rCtx.Cookies, rCtx.LoggerImpressionID, string(openrtb_ext.BidderPubmatic)) + requestExt.Prebid.BidderParams, _ = updateRequestExtBidderParamsPubmatic(requestExt.Prebid.BidderParams, rCtx.Cookies, rCtx.LoggerImpressionID, string(openrtb_ext.BidderPubmatic), rCtx.SendBurl) } for bidderCode, coreBidder := range rCtx.Aliases { if coreBidder == string(openrtb_ext.BidderPubmatic) { if _, ok := rCtx.AdapterThrottleMap[bidderCode]; !ok { - requestExt.Prebid.BidderParams, _ = updateRequestExtBidderParamsPubmatic(requestExt.Prebid.BidderParams, rCtx.Cookies, rCtx.LoggerImpressionID, bidderCode) + requestExt.Prebid.BidderParams, _ = updateRequestExtBidderParamsPubmatic(requestExt.Prebid.BidderParams, rCtx.Cookies, rCtx.LoggerImpressionID, bidderCode, rCtx.SendBurl) } } } @@ -718,7 +719,18 @@ func (m *OpenWrap) applyProfileChanges(rctx models.RequestCtx, bidRequest *openr bidRequest.App.StoreURL = rctx.AppLovinMax.AppStoreUrl } } - + strictVastMode := models.GetVersionLevelPropertyFromPartnerConfig(rctx.PartnerConfigMap, models.StrictVastModeKey) == models.Enabled + if strictVastMode { + if rctx.NewReqExt == nil { + rctx.NewReqExt = &models.RequestExt{} + } + rctx.NewReqExt.Prebid.StrictVastMode = strictVastMode + for i := 0; i < len(bidRequest.Imp); i++ { + if bidRequest.Imp[i].Video != nil { + bidRequest.Imp[i].Video.Protocols = UpdateImpProtocols(bidRequest.Imp[i].Video.Protocols) + } + } + } if cur, ok := rctx.PartnerConfigMap[models.VersionLevelConfigID][models.AdServerCurrency]; ok { bidRequest.Cur = append(bidRequest.Cur, cur) } @@ -760,7 +772,7 @@ func (m *OpenWrap) applyProfileChanges(rctx models.RequestCtx, bidRequest *openr if bidRequest.User.CustomData == "" && rctx.KADUSERCookie != nil { bidRequest.User.CustomData = rctx.KADUSERCookie.Value } - ctv.UpdateUserEidsWithValidValues(bidRequest.User) + UpdateUserExtWithValidValues(bidRequest.User) for i := 0; i < len(bidRequest.WLang); i++ { bidRequest.WLang[i] = getValidLanguage(bidRequest.WLang[i]) @@ -963,7 +975,7 @@ func getDomainFromUrl(pageUrl string) string { // } // NYC: make this generic. Do we need this?. PBS now has auto_gen_source_tid generator. We can make it to wiid for pubmatic adapter in pubmatic.go -func updateRequestExtBidderParamsPubmatic(bidderParams json.RawMessage, cookie, loggerID, bidderCode string) (json.RawMessage, error) { +func updateRequestExtBidderParamsPubmatic(bidderParams json.RawMessage, cookie, loggerID, bidderCode string, sendBurl bool) (json.RawMessage, error) { bidderParamsMap := make(map[string]map[string]interface{}) _ = json.Unmarshal(bidderParams, &bidderParamsMap) // ignore error, incoming might be nil for now but we still have data to put @@ -975,6 +987,10 @@ func updateRequestExtBidderParamsPubmatic(bidderParams json.RawMessage, cookie, bidderParamsMap[bidderCode][models.COOKIE] = cookie } + if sendBurl { + bidderParamsMap[bidderCode][models.SendBurl] = true + } + return json.Marshal(bidderParamsMap) } @@ -1344,36 +1360,44 @@ func isValidURL(urlVal string) bool { return validator.IsRequestURL(urlVal) && validator.IsURL(urlVal) } -func getProfileAppStoreUrlAndUpdateItunesID(rctx models.RequestCtx, bidRequest *openrtb2.BidRequest, impExt *models.ImpExtension) string { +func getProfileAppStoreUrl(rctx models.RequestCtx) (string, bool) { + isValidAppStoreUrl := false appStoreUrl := rctx.PartnerConfigMap[models.VersionLevelConfigID][models.AppStoreUrl] if appStoreUrl == "" { - glog.Errorf("[AppLovinMax] [PubID]: %d [ProfileID]: %d [Error]: app storeurl not present in DB", rctx.PubID, rctx.ProfileID) - return appStoreUrl + glog.Errorf("[AppLovinMax] [PubID]: %d [ProfileID]: %d [Error]: app store url not present in DB", rctx.PubID, rctx.ProfileID) + return appStoreUrl, isValidAppStoreUrl } appStoreUrl = strings.TrimSpace(appStoreUrl) if !isValidURL(appStoreUrl) { - glog.Errorf("[AppLovinMax] [PubID]: %d [ProfileID]: %d [AppStoreUrl]: %s [Error]: Invalid app storeurl", rctx.PubID, rctx.ProfileID, appStoreUrl) - return appStoreUrl + glog.Errorf("[AppLovinMax] [PubID]: %d [ProfileID]: %d [AppStoreUrl]: %s [Error]: Invalid app store url", rctx.PubID, rctx.ProfileID, appStoreUrl) + return appStoreUrl, isValidAppStoreUrl } - var err error - if bidRequest.Device != nil && strings.ToLower(bidRequest.Device.OS) == "ios" { - //no multiple imp supported for AppLovinMax - if impExt != nil { - if impExt.SKAdnetwork == nil { - glog.Errorf("[AppLovinMax] [PubID]: %d [ProfileID]: %d [Error]: skadn is missing in imp.ext", rctx.PubID, rctx.ProfileID, appStoreUrl) - return appStoreUrl - } - var itunesID string - if itunesID = extractItunesIdFromAppStoreUrl(appStoreUrl); itunesID == "" { - glog.Errorf("[AppLovinMax] [PubID]: %d [ProfileID]: %d [AppStoreUrl]: %s [Error]: itunes id is missing in app store url", rctx.PubID, rctx.ProfileID, appStoreUrl) - return appStoreUrl - } - if impExt.SKAdnetwork, err = jsonparser.Set(impExt.SKAdnetwork, []byte(strconv.Quote(itunesID)), "sourceapp"); err != nil { - glog.Errorf("[AppLovinMax] [PubID]: %d [ProfileID]: %d [AppStoreUrl]: %s [Error]: %s", rctx.PubID, rctx.ProfileID, appStoreUrl, err.Error()) - } - } + isValidAppStoreUrl = true + return appStoreUrl, isValidAppStoreUrl +} + +func (m *OpenWrap) updateSkadnSourceapp(rctx models.RequestCtx, bidRequest *openrtb2.BidRequest, impExt *models.ImpExtension) { + if bidRequest.Device == nil || strings.ToLower(bidRequest.Device.OS) != "ios" { + return + } + + if impExt == nil || impExt.SKAdnetwork == nil { + glog.Errorf("[AppLovinMax] [PubID]: %d [ProfileID]: %d [Error]: skadn is missing in imp.ext", rctx.PubID, rctx.ProfileID) + return + } + + itunesID := extractItunesIdFromAppStoreUrl(rctx.AppLovinMax.AppStoreUrl) + if itunesID == "" { + m.metricEngine.RecordFailedParsingItuneID(rctx.PubIDStr, rctx.ProfileIDStr) + glog.Errorf("[AppLovinMax] [PubID]: %d [ProfileID]: %d [AppStoreUrl]: %s [Error]: itunes id is missing in app store url", rctx.PubID, rctx.ProfileID, rctx.AppLovinMax.AppStoreUrl) + return + } + + if updatedSKAdnetwork, err := jsonparser.Set(impExt.SKAdnetwork, []byte(strconv.Quote(itunesID)), "sourceapp"); err != nil { + glog.Errorf("[AppLovinMax] [PubID]: %d [ProfileID]: %d [AppStoreUrl]: %s [Error]: %s", rctx.PubID, rctx.ProfileID, rctx.AppLovinMax.AppStoreUrl, err.Error()) + } else { + impExt.SKAdnetwork = updatedSKAdnetwork } - return appStoreUrl } func extractItunesIdFromAppStoreUrl(url string) string { diff --git a/modules/pubmatic/openwrap/beforevalidationhook_test.go b/modules/pubmatic/openwrap/beforevalidationhook_test.go index 4b3de0603c8..878768573e0 100644 --- a/modules/pubmatic/openwrap/beforevalidationhook_test.go +++ b/modules/pubmatic/openwrap/beforevalidationhook_test.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "errors" + "net/http" "sort" "testing" @@ -1436,14 +1437,520 @@ func TestOpenWrapApplyProfileChanges(t *testing.T) { wantErr: false, }, { - name: "Valid_EIDs_in_User_object_and_User_Ext", + name: "Valid_EIDs_in_User_object_and_User_Ext", + args: args{ + rctx: models.RequestCtx{ + IsTestRequest: 1, + PartnerConfigMap: map[int]map[string]string{ + -1: { + models.AdServerCurrency: "USD", + models.SChainDBKey: "1", + }, + }, + TMax: 500, + IP: "127.0.0.1", + Platform: models.PLATFORM_APP, + KADUSERCookie: &http.Cookie{ + Name: "KADUSERCOOKIE", + Value: "123456789", + }, + }, + bidRequest: &openrtb2.BidRequest{ + ID: "testID", + Test: 1, + Cur: []string{"EUR"}, + TMax: 500, + Source: &openrtb2.Source{ + TID: "testID", + }, + Imp: []openrtb2.Imp{ + { + ID: "testImp1", + Video: &openrtb2.Video{ + W: ptrutil.ToPtr[int64](200), + H: ptrutil.ToPtr[int64](300), + Plcmt: 1, + }, + }, + }, + Device: &openrtb2.Device{ + IP: "127.0.0.1", + Language: "en", + DeviceType: 1, + }, + WLang: []string{"en", "hi"}, + User: &openrtb2.User{ + CustomData: "123456789", + Ext: json.RawMessage(`{"eids":[{"source":"uidapi.com","uids":[{"id":"testUID"},{"id":"testUID2"}]},{"source":"euid.eu","uids":[{"id":"testeuid"}]}]}`), + EIDs: []openrtb2.EID{ + { + Source: "uidapi.com", + UIDs: []openrtb2.UID{ + {ID: "UID2:testUID"}, + {ID: "testUID2"}, + }, + }, + { + Source: "euid.eu", + UIDs: []openrtb2.UID{ + {ID: "euid:testeuid"}}, + }, + }, + }, + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ + ID: "1010", + }, + Content: &openrtb2.Content{ + Language: "en", + }, + }, + }, + }, + want: &openrtb2.BidRequest{ + ID: "testID", + Test: 1, + Cur: []string{"EUR", "USD"}, + TMax: 500, + Source: &openrtb2.Source{ + TID: "testID", + }, + Imp: []openrtb2.Imp{ + { + ID: "testImp1", + Video: &openrtb2.Video{ + W: ptrutil.ToPtr[int64](200), + H: ptrutil.ToPtr[int64](300), + Plcmt: 1, + }, + }, + }, + Device: &openrtb2.Device{ + IP: "127.0.0.1", + Language: "en", + DeviceType: 1, + }, + WLang: []string{"en", "hi"}, + User: &openrtb2.User{ + CustomData: "123456789", + Ext: json.RawMessage(`{"eids":[{"source":"uidapi.com","uids":[{"id":"testUID"},{"id":"testUID2"}]},{"source":"euid.eu","uids":[{"id":"testeuid"}]}]}`), + EIDs: []openrtb2.EID{ + { + Source: "uidapi.com", + UIDs: []openrtb2.UID{ + {ID: "testUID"}, + {ID: "testUID2"}, + }, + }, + { + Source: "euid.eu", + UIDs: []openrtb2.UID{ + {ID: "testeuid"}, + }, + }, + }, + }, + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ + ID: "1010", + }, + Content: &openrtb2.Content{ + Language: "en", + }, + }, + }, + wantErr: false, + }, + { + name: "Invalid_EIDs_in_User_object", + args: args{ + rctx: models.RequestCtx{ + IsTestRequest: 1, + PartnerConfigMap: map[int]map[string]string{ + -1: { + models.AdServerCurrency: "USD", + models.SChainDBKey: "1", + }, + }, + TMax: 500, + IP: "127.0.0.1", + Platform: models.PLATFORM_APP, + KADUSERCookie: &http.Cookie{ + Name: "KADUSERCOOKIE", + Value: "123456789", + }, + }, + bidRequest: &openrtb2.BidRequest{ + ID: "testID", + Test: 1, + Cur: []string{"EUR"}, + TMax: 500, + Source: &openrtb2.Source{ + TID: "testID", + }, + Imp: []openrtb2.Imp{ + { + ID: "testImp1", + Video: &openrtb2.Video{ + W: ptrutil.ToPtr[int64](200), + H: ptrutil.ToPtr[int64](300), + Plcmt: 1, + }, + }, + }, + Device: &openrtb2.Device{ + IP: "127.0.0.1", + Language: "en", + DeviceType: 1, + }, + WLang: []string{"en", "hi"}, + User: &openrtb2.User{ + CustomData: "123456789", + EIDs: []openrtb2.EID{ + { + Source: "uidapi.com", + UIDs: []openrtb2.UID{ + {ID: "UID2:"}, + {ID: ""}, + }, + }, + { + Source: "euid.eu", + UIDs: []openrtb2.UID{ + {ID: "euid:"}}, + }, + { + Source: "liveramp.com", + UIDs: []openrtb2.UID{ + {ID: "IDL:"}}, + }, + }, + }, + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ + ID: "1010", + }, + Content: &openrtb2.Content{ + Language: "en", + }, + }, + }, + }, + want: &openrtb2.BidRequest{ + ID: "testID", + Test: 1, + Cur: []string{"EUR", "USD"}, + TMax: 500, + Source: &openrtb2.Source{ + TID: "testID", + }, + Imp: []openrtb2.Imp{ + { + ID: "testImp1", + Video: &openrtb2.Video{ + W: ptrutil.ToPtr[int64](200), + H: ptrutil.ToPtr[int64](300), + Plcmt: 1, + }, + }, + }, + Device: &openrtb2.Device{ + IP: "127.0.0.1", + Language: "en", + DeviceType: 1, + }, + WLang: []string{"en", "hi"}, + User: &openrtb2.User{ + CustomData: "123456789", + }, + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ + ID: "1010", + }, + Content: &openrtb2.Content{ + Language: "en", + }, + }, + }, + wantErr: false, + }, + { + name: "Request_with_User_Ext_Eids_Invalid", + args: args{ + rctx: models.RequestCtx{ + IsTestRequest: 1, + PartnerConfigMap: map[int]map[string]string{ + -1: { + models.AdServerCurrency: "USD", + models.SChainDBKey: "1", + }, + }, + TMax: 500, + IP: "127.0.0.1", + Platform: models.PLATFORM_APP, + KADUSERCookie: &http.Cookie{ + Name: "KADUSERCOOKIE", + Value: "123456789", + }, + }, + bidRequest: &openrtb2.BidRequest{ + ID: "testID", + Test: 1, + Cur: []string{"EUR"}, + TMax: 500, + Source: &openrtb2.Source{ + TID: "testID", + }, + Imp: []openrtb2.Imp{ + { + ID: "testImp1", + Video: &openrtb2.Video{ + W: ptrutil.ToPtr[int64](200), + H: ptrutil.ToPtr[int64](300), + Plcmt: 1, + }, + }, + }, + Device: &openrtb2.Device{ + IP: "127.0.0.1", + Language: "en", + DeviceType: 1, + }, + WLang: []string{"en", "hi"}, + User: &openrtb2.User{ + CustomData: "123456789", + Ext: json.RawMessage(`{"eids":[{"source":"uidapi.com","uids":[{"id":"UID2:"},{"id":""}]},{"source":"euid.eu","uids":[{"id":""}]},{"source":"liveramp.com","uids":[{"id":"IDL:"}]}]}`), + }, + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ + ID: "1010", + }, + Content: &openrtb2.Content{ + Language: "en", + }, + }, + }, + }, + want: &openrtb2.BidRequest{ + ID: "testID", + Test: 1, + Cur: []string{"EUR", "USD"}, + TMax: 500, + Source: &openrtb2.Source{ + TID: "testID", + }, + Imp: []openrtb2.Imp{ + { + ID: "testImp1", + Video: &openrtb2.Video{ + W: ptrutil.ToPtr[int64](200), + H: ptrutil.ToPtr[int64](300), + Plcmt: 1, + }, + }, + }, + Device: &openrtb2.Device{ + IP: "127.0.0.1", + Language: "en", + DeviceType: 1, + }, + WLang: []string{"en", "hi"}, + User: &openrtb2.User{ + CustomData: "123456789", + Ext: json.RawMessage(`{}`), + }, + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ + ID: "1010", + }, + Content: &openrtb2.Content{ + Language: "en", + }, + }, + }, + wantErr: false, + }, + { + name: "AppLovinMax_request_with_storeurl_and_sourceapp_updated_from_DB", + args: args{ + rctx: models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + -1: { + models.AppStoreUrl: "https://itunes.apple.com/us/app/angry-birds/id343200656", + }, + }, + Endpoint: models.EndpointAppLovinMax, + AppLovinMax: models.AppLovinMax{ + AppStoreUrl: "https://itunes.apple.com/us/app/angry-birds/id343200656", + }, + ImpBidCtx: map[string]models.ImpCtx{ + "testImp1": { + NewExt: json.RawMessage(`{"skadn":{"sourceapp":"343200656"}}`), + }, + }, + }, + bidRequest: &openrtb2.BidRequest{ + ID: "testID", + Imp: []openrtb2.Imp{ + { + ID: "testImp1", + Video: &openrtb2.Video{ + W: ptrutil.ToPtr[int64](200), + H: ptrutil.ToPtr[int64](300), + Plcmt: 1, + }, + Ext: json.RawMessage(`{"skadn": {}}`), + }, + }, + Device: &openrtb2.Device{ + OS: "iOS", + }, + App: &openrtb2.App{ + Publisher: &openrtb2.Publisher{ + ID: "1010", + }, + }, + }, + }, + want: &openrtb2.BidRequest{ + ID: "testID", + Imp: []openrtb2.Imp{ + { + ID: "testImp1", + Video: &openrtb2.Video{ + W: ptrutil.ToPtr[int64](200), + H: ptrutil.ToPtr[int64](300), + Plcmt: 1, + }, + Ext: json.RawMessage(`{"skadn":{"sourceapp":"343200656"}}`), + }, + }, + Device: &openrtb2.Device{ + OS: "iOS", + }, + User: &openrtb2.User{}, + App: &openrtb2.App{ + Publisher: &openrtb2.Publisher{ + ID: "1010", + }, + StoreURL: "https://itunes.apple.com/us/app/angry-birds/id343200656", + }, + Source: &openrtb2.Source{ + TID: "testID", + }, + }, + wantErr: false, + }, + { + name: "GAM_Unwinding_Enabled", + args: args{ + rctx: models.RequestCtx{ + IsTestRequest: 1, + PartnerConfigMap: map[int]map[string]string{ + -1: { + models.AdServerCurrency: "USD", + models.SChainDBKey: "1", + models.StrictVastModeKey: models.Enabled, + }, + }, + TMax: 500, + IP: "127.0.0.1", + Platform: models.PLATFORM_APP, + KADUSERCookie: &http.Cookie{ + Name: "KADUSERCOOKIE", + Value: "123456789", + }, + }, + bidRequest: &openrtb2.BidRequest{ + ID: "testID", + Test: 1, + Cur: []string{"EUR"}, + TMax: 500, + Source: &openrtb2.Source{ + TID: "testID", + }, + Imp: []openrtb2.Imp{ + { + ID: "testImp1", + Video: &openrtb2.Video{ + W: ptrutil.ToPtr[int64](200), + H: ptrutil.ToPtr[int64](300), + Plcmt: 1, + Protocols: []adcom1.MediaCreativeSubtype{1, 2, 3}, + }, + }, + }, + Device: &openrtb2.Device{ + IP: "127.0.0.1", + Language: "en", + DeviceType: 1, + }, + WLang: []string{"en", "hi"}, + User: &openrtb2.User{ + CustomData: "123456789", + Ext: json.RawMessage(`{"eids":[{"source":"uidapi.com","uids":[{"id":"UID2:"},{"id":""}]},{"source":"euid.eu","uids":[{"id":""}]},{"source":"liveramp.com","uids":[{"id":"IDL:"}]}]}`), + }, + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ + ID: "1010", + }, + Content: &openrtb2.Content{ + Language: "en", + }, + }, + }, + }, + want: &openrtb2.BidRequest{ + ID: "testID", + Test: 1, + Cur: []string{"EUR", "USD"}, + TMax: 500, + Source: &openrtb2.Source{ + TID: "testID", + }, + Imp: []openrtb2.Imp{ + { + ID: "testImp1", + Video: &openrtb2.Video{ + W: ptrutil.ToPtr[int64](200), + H: ptrutil.ToPtr[int64](300), + Plcmt: 1, + Protocols: []adcom1.MediaCreativeSubtype{1, 2, 3, 6, 7, 8}, + }, + }, + }, + Device: &openrtb2.Device{ + IP: "127.0.0.1", + Language: "en", + DeviceType: 1, + }, + WLang: []string{"en", "hi"}, + User: &openrtb2.User{ + CustomData: "123456789", + Ext: json.RawMessage(`{}`), + }, + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ + ID: "1010", + }, + Content: &openrtb2.Content{ + Language: "en", + }, + }, + Ext: json.RawMessage(`{"prebid":{"strictvastmode":true}}`), + }, + wantErr: false, + }, + { + name: "GAM_Unwinding_Enabled_Multi_Imp", args: args{ rctx: models.RequestCtx{ IsTestRequest: 1, PartnerConfigMap: map[int]map[string]string{ -1: { - models.AdServerCurrency: "USD", - models.SChainDBKey: "1", + models.AdServerCurrency: "USD", + models.SChainDBKey: "1", + models.StrictVastModeKey: models.Enabled, }, }, TMax: 500, @@ -1466,9 +1973,19 @@ func TestOpenWrapApplyProfileChanges(t *testing.T) { { ID: "testImp1", Video: &openrtb2.Video{ - W: ptrutil.ToPtr[int64](200), - H: ptrutil.ToPtr[int64](300), - Plcmt: 1, + W: ptrutil.ToPtr[int64](200), + H: ptrutil.ToPtr[int64](300), + Plcmt: 1, + Protocols: []adcom1.MediaCreativeSubtype{1, 2, 3}, + }, + }, + { + ID: "testImp2", + Video: &openrtb2.Video{ + W: ptrutil.ToPtr[int64](200), + H: ptrutil.ToPtr[int64](300), + Plcmt: 1, + Protocols: []adcom1.MediaCreativeSubtype{1}, }, }, }, @@ -1480,21 +1997,7 @@ func TestOpenWrapApplyProfileChanges(t *testing.T) { WLang: []string{"en", "hi"}, User: &openrtb2.User{ CustomData: "123456789", - Ext: json.RawMessage(`{"eids":[{"source":"uidapi.com","uids":[{"id":"testUID"},{"id":"testUID2"}]},{"source":"euid.eu","uids":[{"id":"testeuid"}]}]}`), - EIDs: []openrtb2.EID{ - { - Source: "uidapi.com", - UIDs: []openrtb2.UID{ - {ID: "UID2:testUID"}, - {ID: "testUID2"}, - }, - }, - { - Source: "euid.eu", - UIDs: []openrtb2.UID{ - {ID: "euid:testeuid"}}, - }, - }, + Ext: json.RawMessage(`{"eids":[{"source":"uidapi.com","uids":[{"id":"UID2:"},{"id":""}]},{"source":"euid.eu","uids":[{"id":""}]},{"source":"liveramp.com","uids":[{"id":"IDL:"}]}]}`), }, Site: &openrtb2.Site{ Publisher: &openrtb2.Publisher{ @@ -1518,9 +2021,19 @@ func TestOpenWrapApplyProfileChanges(t *testing.T) { { ID: "testImp1", Video: &openrtb2.Video{ - W: ptrutil.ToPtr[int64](200), - H: ptrutil.ToPtr[int64](300), - Plcmt: 1, + W: ptrutil.ToPtr[int64](200), + H: ptrutil.ToPtr[int64](300), + Plcmt: 1, + Protocols: []adcom1.MediaCreativeSubtype{1, 2, 3, 6, 7, 8}, + }, + }, + { + ID: "testImp2", + Video: &openrtb2.Video{ + W: ptrutil.ToPtr[int64](200), + H: ptrutil.ToPtr[int64](300), + Plcmt: 1, + Protocols: []adcom1.MediaCreativeSubtype{1, 3, 6, 7, 8}, }, }, }, @@ -1532,22 +2045,7 @@ func TestOpenWrapApplyProfileChanges(t *testing.T) { WLang: []string{"en", "hi"}, User: &openrtb2.User{ CustomData: "123456789", - Ext: json.RawMessage(`{"eids":[{"source":"uidapi.com","uids":[{"id":"testUID"},{"id":"testUID2"}]},{"source":"euid.eu","uids":[{"id":"testeuid"}]}]}`), - EIDs: []openrtb2.EID{ - { - Source: "uidapi.com", - UIDs: []openrtb2.UID{ - {ID: "testUID"}, - {ID: "testUID2"}, - }, - }, - { - Source: "euid.eu", - UIDs: []openrtb2.UID{ - {ID: "testeuid"}, - }, - }, - }, + Ext: json.RawMessage(`{}`), }, Site: &openrtb2.Site{ Publisher: &openrtb2.Publisher{ @@ -1557,18 +2055,20 @@ func TestOpenWrapApplyProfileChanges(t *testing.T) { Language: "en", }, }, + Ext: json.RawMessage(`{"prebid":{"strictvastmode":true}}`), }, wantErr: false, }, { - name: "Invalid_EIDs_in_User_object", + name: "GAM_Unwinding_Enabled_Empty_Protocols", args: args{ rctx: models.RequestCtx{ IsTestRequest: 1, PartnerConfigMap: map[int]map[string]string{ -1: { - models.AdServerCurrency: "USD", - models.SChainDBKey: "1", + models.AdServerCurrency: "USD", + models.SChainDBKey: "1", + models.StrictVastModeKey: models.Enabled, }, }, TMax: 500, @@ -1591,9 +2091,10 @@ func TestOpenWrapApplyProfileChanges(t *testing.T) { { ID: "testImp1", Video: &openrtb2.Video{ - W: ptrutil.ToPtr[int64](200), - H: ptrutil.ToPtr[int64](300), - Plcmt: 1, + W: ptrutil.ToPtr[int64](200), + H: ptrutil.ToPtr[int64](300), + Plcmt: 1, + Protocols: []adcom1.MediaCreativeSubtype{}, }, }, }, @@ -1605,25 +2106,7 @@ func TestOpenWrapApplyProfileChanges(t *testing.T) { WLang: []string{"en", "hi"}, User: &openrtb2.User{ CustomData: "123456789", - EIDs: []openrtb2.EID{ - { - Source: "uidapi.com", - UIDs: []openrtb2.UID{ - {ID: "UID2:"}, - {ID: ""}, - }, - }, - { - Source: "euid.eu", - UIDs: []openrtb2.UID{ - {ID: "euid:"}}, - }, - { - Source: "liveramp.com", - UIDs: []openrtb2.UID{ - {ID: "IDL:"}}, - }, - }, + Ext: json.RawMessage(`{"eids":[{"source":"uidapi.com","uids":[{"id":"UID2:"},{"id":""}]},{"source":"euid.eu","uids":[{"id":""}]},{"source":"liveramp.com","uids":[{"id":"IDL:"}]}]}`), }, Site: &openrtb2.Site{ Publisher: &openrtb2.Publisher{ @@ -1647,9 +2130,10 @@ func TestOpenWrapApplyProfileChanges(t *testing.T) { { ID: "testImp1", Video: &openrtb2.Video{ - W: ptrutil.ToPtr[int64](200), - H: ptrutil.ToPtr[int64](300), - Plcmt: 1, + W: ptrutil.ToPtr[int64](200), + H: ptrutil.ToPtr[int64](300), + Plcmt: 1, + Protocols: []adcom1.MediaCreativeSubtype{3, 6, 7, 8}, }, }, }, @@ -1661,6 +2145,7 @@ func TestOpenWrapApplyProfileChanges(t *testing.T) { WLang: []string{"en", "hi"}, User: &openrtb2.User{ CustomData: "123456789", + Ext: json.RawMessage(`{}`), }, Site: &openrtb2.Site{ Publisher: &openrtb2.Publisher{ @@ -1670,18 +2155,20 @@ func TestOpenWrapApplyProfileChanges(t *testing.T) { Language: "en", }, }, + Ext: json.RawMessage(`{"prebid":{"strictvastmode":true}}`), }, wantErr: false, }, { - name: "Request_with_User_Ext_Eids_Invalid", + name: "GAM_Unwinding_Enabled_Protocols_Not_Present", args: args{ rctx: models.RequestCtx{ IsTestRequest: 1, PartnerConfigMap: map[int]map[string]string{ -1: { - models.AdServerCurrency: "USD", - models.SChainDBKey: "1", + models.AdServerCurrency: "USD", + models.SChainDBKey: "1", + models.StrictVastModeKey: models.Enabled, }, }, TMax: 500, @@ -1742,9 +2229,10 @@ func TestOpenWrapApplyProfileChanges(t *testing.T) { { ID: "testImp1", Video: &openrtb2.Video{ - W: ptrutil.ToPtr[int64](200), - H: ptrutil.ToPtr[int64](300), - Plcmt: 1, + W: ptrutil.ToPtr[int64](200), + H: ptrutil.ToPtr[int64](300), + Plcmt: 1, + Protocols: []adcom1.MediaCreativeSubtype{3, 6, 7, 8}, }, }, }, @@ -1766,76 +2254,105 @@ func TestOpenWrapApplyProfileChanges(t *testing.T) { Language: "en", }, }, + Ext: json.RawMessage(`{"prebid":{"strictvastmode":true}}`), }, wantErr: false, }, { - name: "AppLovinMax_request_with_storeurl_and_sourceapp_updated_from_DB", + name: "GAM_Unwinding_Disabled", args: args{ rctx: models.RequestCtx{ + IsTestRequest: 1, PartnerConfigMap: map[int]map[string]string{ -1: { - models.AppStoreUrl: "https://itunes.apple.com/us/app/angry-birds/id343200656", + models.AdServerCurrency: "USD", + models.SChainDBKey: "1", + models.StrictVastModeKey: "0", }, }, - Endpoint: models.EndpointAppLovinMax, - AppLovinMax: models.AppLovinMax{ - AppStoreUrl: "https://itunes.apple.com/us/app/angry-birds/id343200656", - }, - ImpBidCtx: map[string]models.ImpCtx{ - "testImp1": { - NewExt: json.RawMessage(`{"skadn":{"sourceapp":"343200656"}}`), - }, + TMax: 500, + IP: "127.0.0.1", + Platform: models.PLATFORM_APP, + KADUSERCookie: &http.Cookie{ + Name: "KADUSERCOOKIE", + Value: "123456789", }, }, bidRequest: &openrtb2.BidRequest{ - ID: "testID", + ID: "testID", + Test: 1, + Cur: []string{"EUR"}, + TMax: 500, + Source: &openrtb2.Source{ + TID: "testID", + }, Imp: []openrtb2.Imp{ { ID: "testImp1", Video: &openrtb2.Video{ - W: ptrutil.ToPtr[int64](200), - H: ptrutil.ToPtr[int64](300), - Plcmt: 1, + W: ptrutil.ToPtr[int64](200), + H: ptrutil.ToPtr[int64](300), + Plcmt: 1, + Protocols: []adcom1.MediaCreativeSubtype{1, 2, 3}, }, - Ext: json.RawMessage(`{"skadn": {}}`), }, }, Device: &openrtb2.Device{ - OS: "iOS", + IP: "127.0.0.1", + Language: "en", + DeviceType: 1, }, - App: &openrtb2.App{ + WLang: []string{"en", "hi"}, + User: &openrtb2.User{ + CustomData: "123456789", + Ext: json.RawMessage(`{"eids":[{"source":"uidapi.com","uids":[{"id":"UID2:"},{"id":""}]},{"source":"euid.eu","uids":[{"id":""}]},{"source":"liveramp.com","uids":[{"id":"IDL:"}]}]}`), + }, + Site: &openrtb2.Site{ Publisher: &openrtb2.Publisher{ ID: "1010", }, + Content: &openrtb2.Content{ + Language: "en", + }, }, }, }, want: &openrtb2.BidRequest{ - ID: "testID", + ID: "testID", + Test: 1, + Cur: []string{"EUR", "USD"}, + TMax: 500, + Source: &openrtb2.Source{ + TID: "testID", + }, Imp: []openrtb2.Imp{ { ID: "testImp1", Video: &openrtb2.Video{ - W: ptrutil.ToPtr[int64](200), - H: ptrutil.ToPtr[int64](300), - Plcmt: 1, + W: ptrutil.ToPtr[int64](200), + H: ptrutil.ToPtr[int64](300), + Plcmt: 1, + Protocols: []adcom1.MediaCreativeSubtype{1, 2, 3}, }, - Ext: json.RawMessage(`{"skadn":{"sourceapp":"343200656"}}`), }, }, Device: &openrtb2.Device{ - OS: "iOS", + IP: "127.0.0.1", + Language: "en", + DeviceType: 1, }, - User: &openrtb2.User{}, - App: &openrtb2.App{ + WLang: []string{"en", "hi"}, + User: &openrtb2.User{ + CustomData: "123456789", + Ext: json.RawMessage(`{}`), + }, + Site: &openrtb2.Site{ Publisher: &openrtb2.Publisher{ ID: "1010", }, - StoreURL: "https://itunes.apple.com/us/app/angry-birds/id343200656", - }, - Source: &openrtb2.Source{ - TID: "testID", + Content: &openrtb2.Content{ + Language: "en", + }, }, }, wantErr: false, @@ -3019,6 +3536,7 @@ func TestUpdateRequestExtBidderParamsPubmatic(t *testing.T) { cookie string loggerID string bidderCode string + sendBurl bool } tests := []struct { name string @@ -3065,10 +3583,43 @@ func TestUpdateRequestExtBidderParamsPubmatic(t *testing.T) { }, want: json.RawMessage(`{"pubmatic":{"Cookie":"test_cookie","wiid":"b441a46e-8c1f-428b-9c29-44e2a408a954"}}`), }, + { + name: "sendburl is true and both cookie and loggerID are present", + args: args{ + bidderParams: json.RawMessage(`{"pubmatic":{"pmzoneid":"zone1","adSlot":"38519891"}}`), + cookie: "test_cookie", + loggerID: "b441a46e-8c1f-428b-9c29-44e2a408a954", + bidderCode: "pubmatic", + sendBurl: true, + }, + want: json.RawMessage(`{"pubmatic":{"Cookie":"test_cookie","sendburl":true,"wiid":"b441a46e-8c1f-428b-9c29-44e2a408a954"}}`), + }, + { + name: "sendburl is true and both cookie and loggerID are empty", + args: args{ + bidderParams: json.RawMessage(`{"pubmatic":{"pmzoneid":"zone1","adSlot":"38519891"}}`), + cookie: "", + loggerID: "", + bidderCode: "pubmatic", + sendBurl: true, + }, + want: json.RawMessage(`{"pubmatic":{"sendburl":true,"wiid":""}}`), + }, + { + name: "sendburl is false and both cookie and loggerID are present", + args: args{ + bidderParams: json.RawMessage(`{"pubmatic":{"pmzoneid":"zone1","adSlot":"38519891"}}`), + cookie: "test_cookie", + loggerID: "b441a46e-8c1f-428b-9c29-44e2a408a954", + bidderCode: "pubmatic", + sendBurl: false, + }, + want: json.RawMessage(`{"pubmatic":{"Cookie":"test_cookie","wiid":"b441a46e-8c1f-428b-9c29-44e2a408a954"}}`), + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := updateRequestExtBidderParamsPubmatic(tt.args.bidderParams, tt.args.cookie, tt.args.loggerID, tt.args.bidderCode) + got, err := updateRequestExtBidderParamsPubmatic(tt.args.bidderParams, tt.args.cookie, tt.args.loggerID, tt.args.bidderCode, tt.args.sendBurl) if (err != nil) != tt.wantErr { assert.Equal(t, tt.wantErr, err != nil) return @@ -3216,7 +3767,7 @@ func TestOpenWrapHandleBeforeValidationHook(t *testing.T) { }, setup: func() { mockEngine.EXPECT().RecordPublisherProfileRequests("5890", "1234") - mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, getPubmaticErrorCode(nbr.InvalidRequestExt)) + mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, "5890", getPubmaticErrorCode(nbr.InvalidRequestExt)) mockEngine.EXPECT().RecordNobidErrPrebidServerRequests("5890", int(nbr.InvalidRequestExt)) mockFeature.EXPECT().IsTBFFeatureEnabled(gomock.Any(), gomock.Any()).Return(false) }, @@ -3262,10 +3813,9 @@ func TestOpenWrapHandleBeforeValidationHook(t *testing.T) { }, errors.New("test")) //prometheus metrics mockEngine.EXPECT().RecordPublisherProfileRequests("5890", "1234") - mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, getPubmaticErrorCode(nbr.InvalidProfileConfiguration)) + mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, rctx.PubIDStr, getPubmaticErrorCode(nbr.InvalidProfileConfiguration)) mockEngine.EXPECT().RecordNobidErrPrebidServerRequests("5890", int(nbr.InvalidProfileConfiguration)) mockEngine.EXPECT().RecordPublisherInvalidProfileRequests(rctx.Endpoint, "5890", rctx.ProfileIDStr) - mockEngine.EXPECT().RecordPublisherInvalidProfileImpressions("5890", rctx.ProfileIDStr, gomock.Any()) mockFeature.EXPECT().IsTBFFeatureEnabled(gomock.Any(), gomock.Any()).Return(false) mockFeature.EXPECT().IsAnalyticsTrackingThrottled(gomock.Any(), gomock.Any()).Return(false, false) }, @@ -3298,10 +3848,9 @@ func TestOpenWrapHandleBeforeValidationHook(t *testing.T) { mockCache.EXPECT().GetPartnerConfigMap(gomock.Any(), gomock.Any(), gomock.Any()).Return(map[int]map[string]string{}, nil) //prometheus metrics mockEngine.EXPECT().RecordPublisherProfileRequests("5890", "1234") - mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, getPubmaticErrorCode(nbr.InvalidProfileConfiguration)) + mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, rctx.PubIDStr, getPubmaticErrorCode(nbr.InvalidProfileConfiguration)) mockEngine.EXPECT().RecordNobidErrPrebidServerRequests("5890", int(nbr.InvalidProfileConfiguration)) mockEngine.EXPECT().RecordPublisherInvalidProfileRequests(rctx.Endpoint, "5890", rctx.ProfileIDStr) - mockEngine.EXPECT().RecordPublisherInvalidProfileImpressions("5890", rctx.ProfileIDStr, gomock.Any()) mockFeature.EXPECT().IsTBFFeatureEnabled(gomock.Any(), gomock.Any()).Return(false) mockFeature.EXPECT().IsAnalyticsTrackingThrottled(gomock.Any(), gomock.Any()).Return(false, false) }, @@ -3346,10 +3895,9 @@ func TestOpenWrapHandleBeforeValidationHook(t *testing.T) { }, nil) //prometheus metrics mockEngine.EXPECT().RecordPublisherProfileRequests("5890", "1234") - mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, getPubmaticErrorCode(nbr.InvalidPlatform)) + mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, rctx.PubIDStr, getPubmaticErrorCode(nbr.InvalidPlatform)) mockEngine.EXPECT().RecordNobidErrPrebidServerRequests("5890", int(nbr.InvalidPlatform)) mockEngine.EXPECT().RecordPublisherInvalidProfileRequests(rctx.Endpoint, "5890", rctx.ProfileIDStr) - mockEngine.EXPECT().RecordPublisherInvalidProfileImpressions("5890", rctx.ProfileIDStr, gomock.Any()) mockFeature.EXPECT().IsTBFFeatureEnabled(gomock.Any(), gomock.Any()).Return(false) mockFeature.EXPECT().IsAnalyticsTrackingThrottled(gomock.Any(), gomock.Any()).Return(false, false) }, @@ -3396,7 +3944,7 @@ func TestOpenWrapHandleBeforeValidationHook(t *testing.T) { }, nil) //prometheus metrics mockEngine.EXPECT().RecordPublisherProfileRequests("5890", "1234") - mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, getPubmaticErrorCode(nbr.AllPartnerThrottled)) + mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, rctx.PubIDStr, getPubmaticErrorCode(nbr.AllPartnerThrottled)) mockEngine.EXPECT().RecordNobidErrPrebidServerRequests("5890", int(nbr.AllPartnerThrottled)) mockEngine.EXPECT().RecordPublisherRequests(rctx.Endpoint, "5890", rctx.Platform) mockFeature.EXPECT().IsTBFFeatureEnabled(gomock.Any(), gomock.Any()).Return(false) @@ -3457,7 +4005,7 @@ func TestOpenWrapHandleBeforeValidationHook(t *testing.T) { mockCache.EXPECT().GetAdunitConfigFromCache(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&adunitconfig.AdUnitConfig{}) //prometheus metrics mockEngine.EXPECT().RecordPublisherProfileRequests("5890", "1234") - mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, getPubmaticErrorCode(nbr.InvalidImpressionTagID)) + mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, rctx.PubIDStr, getPubmaticErrorCode(nbr.InvalidImpressionTagID)) mockEngine.EXPECT().RecordNobidErrPrebidServerRequests("5890", int(nbr.InvalidImpressionTagID)) mockEngine.EXPECT().RecordPublisherRequests(rctx.Endpoint, "5890", rctx.Platform) mockFeature.EXPECT().IsTBFFeatureEnabled(gomock.Any(), gomock.Any()).Return(false) @@ -3508,7 +4056,7 @@ func TestOpenWrapHandleBeforeValidationHook(t *testing.T) { }, nil) //prometheus metrics mockEngine.EXPECT().RecordPublisherProfileRequests("5890", "1234") - mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, getPubmaticErrorCode(nbr.AllPartnersFiltered)) + mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, rctx.PubIDStr, getPubmaticErrorCode(nbr.AllPartnersFiltered)) mockEngine.EXPECT().RecordNobidErrPrebidServerRequests("5890", int(nbr.AllPartnersFiltered)) mockEngine.EXPECT().RecordPublisherRequests(rctx.Endpoint, "5890", rctx.Platform) mockFeature.EXPECT().IsTBFFeatureEnabled(gomock.Any(), gomock.Any()).Return(false) @@ -3559,7 +4107,7 @@ func TestOpenWrapHandleBeforeValidationHook(t *testing.T) { mockCache.EXPECT().GetAdunitConfigFromCache(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&adunitconfig.AdUnitConfig{}) //prometheus metrics mockEngine.EXPECT().RecordPublisherProfileRequests("5890", "1234") - mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, getPubmaticErrorCode(nbr.InvalidImpressionTagID)) + mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, rctx.PubIDStr, getPubmaticErrorCode(nbr.InvalidImpressionTagID)) mockEngine.EXPECT().RecordNobidErrPrebidServerRequests("5890", int(nbr.InvalidImpressionTagID)) mockEngine.EXPECT().RecordPublisherRequests(rctx.Endpoint, "5890", rctx.Platform) mockFeature.EXPECT().IsTBFFeatureEnabled(gomock.Any(), gomock.Any()).Return(false) @@ -3613,7 +4161,7 @@ func TestOpenWrapHandleBeforeValidationHook(t *testing.T) { mockCache.EXPECT().GetAdunitConfigFromCache(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&adunitconfig.AdUnitConfig{}) //prometheus metrics mockEngine.EXPECT().RecordPublisherProfileRequests("5890", "1234") - mockEngine.EXPECT().RecordBadRequests(models.EndpointWebS2S, getPubmaticErrorCode(nbr.InvalidImpressionTagID)) + mockEngine.EXPECT().RecordBadRequests(models.EndpointWebS2S, "5890", getPubmaticErrorCode(nbr.InvalidImpressionTagID)) mockEngine.EXPECT().RecordNobidErrPrebidServerRequests("5890", int(nbr.InvalidImpressionTagID)) mockEngine.EXPECT().RecordPublisherRequests(models.EndpointWebS2S, "5890", rctx.Platform) mockFeature.EXPECT().IsTBFFeatureEnabled(gomock.Any(), gomock.Any()).Return(false) @@ -3663,7 +4211,7 @@ func TestOpenWrapHandleBeforeValidationHook(t *testing.T) { mockCache.EXPECT().GetAdunitConfigFromCache(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&adunitconfig.AdUnitConfig{}) //prometheus metrics mockEngine.EXPECT().RecordPublisherProfileRequests("5890", "1234") - mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, getPubmaticErrorCode(openrtb3.NoBidInvalidRequest)) + mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, rctx.PubIDStr, getPubmaticErrorCode(openrtb3.NoBidInvalidRequest)) mockEngine.EXPECT().RecordNobidErrPrebidServerRequests("5890", int(openrtb3.NoBidInvalidRequest)) mockEngine.EXPECT().RecordPublisherRequests(rctx.Endpoint, "5890", rctx.Platform) mockFeature.EXPECT().IsTBFFeatureEnabled(gomock.Any(), gomock.Any()).Return(false) @@ -3757,7 +4305,7 @@ func TestOpenWrapHandleBeforeValidationHook(t *testing.T) { }) //prometheus metrics mockEngine.EXPECT().RecordPublisherProfileRequests("5890", "1234") - mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, getPubmaticErrorCode(nbr.AllSlotsDisabled)) + mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, rctx.PubIDStr, getPubmaticErrorCode(nbr.AllSlotsDisabled)) mockEngine.EXPECT().RecordNobidErrPrebidServerRequests("5890", int(nbr.AllSlotsDisabled)) mockEngine.EXPECT().RecordPublisherRequests(rctx.Endpoint, "5890", rctx.Platform) mockEngine.EXPECT().RecordImpDisabledViaConfigStats(models.ImpTypeVideo, "5890", "1234") @@ -3953,7 +4501,7 @@ func TestOpenWrapHandleBeforeValidationHook(t *testing.T) { mockCache.EXPECT().GetAdunitConfigFromCache(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&adunitconfig.AdUnitConfig{}) //prometheus metrics mockEngine.EXPECT().RecordPublisherProfileRequests("5890", "1234") - mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, getPubmaticErrorCode(nbr.ServerSidePartnerNotConfigured)) + mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, rctx.PubIDStr, getPubmaticErrorCode(nbr.ServerSidePartnerNotConfigured)) mockEngine.EXPECT().RecordNobidErrPrebidServerRequests("5890", int(nbr.ServerSidePartnerNotConfigured)) mockEngine.EXPECT().RecordPublisherRequests(rctx.Endpoint, "5890", rctx.Platform) mockFeature.EXPECT().IsTBFFeatureEnabled(gomock.Any(), gomock.Any()).Return(false) @@ -4209,7 +4757,7 @@ func TestOpenWrapHandleBeforeValidationHook(t *testing.T) { metricEngine: mockEngine, }, setup: func() { - mockEngine.EXPECT().RecordBadRequests(models.EndpointV25, 18) + mockEngine.EXPECT().RecordBadRequests(models.EndpointV25, "1234", 18) mockEngine.EXPECT().RecordNobidErrPrebidServerRequests("1234", 604) }, want: want{ @@ -4237,7 +4785,7 @@ func TestOpenWrapHandleBeforeValidationHook(t *testing.T) { metricEngine: mockEngine, }, setup: func() { - mockEngine.EXPECT().RecordBadRequests(models.EndpointV25, 18) + mockEngine.EXPECT().RecordBadRequests(models.EndpointV25, "1234", 18) mockEngine.EXPECT().RecordNobidErrPrebidServerRequests("1234", 604) }, want: want{ @@ -4740,7 +5288,7 @@ func TestCurrencyConverion(t *testing.T) { setup: func() { mockEngine.EXPECT().RecordPublisherRequests(models.EndpointV25, "5890", "amp") mockEngine.EXPECT().RecordPublisherProfileRequests("5890", "1234") - mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, getPubmaticErrorCode(openrtb3.NoBidInvalidRequest)) + mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, rctx.PubIDStr, getPubmaticErrorCode(openrtb3.NoBidInvalidRequest)) mockEngine.EXPECT().RecordNobidErrPrebidServerRequests("5890", int(openrtb3.NoBidInvalidRequest)) mockFeature.EXPECT().IsTBFFeatureEnabled(5890, 1234).Return(false) mockFeature.EXPECT().IsAnalyticsTrackingThrottled(5890, 1234).Return(false, false) @@ -4788,7 +5336,7 @@ func TestCurrencyConverion(t *testing.T) { setup: func() { mockEngine.EXPECT().RecordPublisherRequests(models.EndpointV25, "5890", "amp") mockEngine.EXPECT().RecordPublisherProfileRequests("5890", "1234") - mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, getPubmaticErrorCode(openrtb3.NoBidInvalidRequest)) + mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, rctx.PubIDStr, getPubmaticErrorCode(openrtb3.NoBidInvalidRequest)) mockEngine.EXPECT().RecordNobidErrPrebidServerRequests("5890", int(openrtb3.NoBidInvalidRequest)) mockFeature.EXPECT().IsTBFFeatureEnabled(5890, 1234).Return(false) mockFeature.EXPECT().IsAnalyticsTrackingThrottled(5890, 1234).Return(false, false) @@ -4887,7 +5435,7 @@ func TestUserAgent_handleBeforeValidationHook(t *testing.T) { }, setup: func() { mockEngine.EXPECT().RecordPublisherProfileRequests("5890", "1234") - mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, getPubmaticErrorCode(nbr.InvalidRequestExt)) + mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, rctx.PubIDStr, getPubmaticErrorCode(nbr.InvalidRequestExt)) mockEngine.EXPECT().RecordNobidErrPrebidServerRequests("5890", int(nbr.InvalidRequestExt)) mockFeature.EXPECT().IsTBFFeatureEnabled(gomock.Any(), gomock.Any()).Return(false) @@ -4917,7 +5465,7 @@ func TestUserAgent_handleBeforeValidationHook(t *testing.T) { }, setup: func() { mockEngine.EXPECT().RecordPublisherProfileRequests("5890", "1234") - mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, getPubmaticErrorCode(nbr.InvalidRequestExt)) + mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, rctx.PubIDStr, getPubmaticErrorCode(nbr.InvalidRequestExt)) mockEngine.EXPECT().RecordNobidErrPrebidServerRequests("5890", int(nbr.InvalidRequestExt)) mockFeature.EXPECT().IsTBFFeatureEnabled(gomock.Any(), gomock.Any()).Return(false) }, @@ -5408,10 +5956,9 @@ func TestImpBidCtx_handleBeforeValidationHook(t *testing.T) { }, }, errors.New("test")) mockEngine.EXPECT().RecordPublisherProfileRequests("5890", "1234") - mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, getPubmaticErrorCode(nbr.InvalidProfileConfiguration)) + mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, rctx.PubIDStr, getPubmaticErrorCode(nbr.InvalidProfileConfiguration)) mockEngine.EXPECT().RecordNobidErrPrebidServerRequests("5890", int(nbr.InvalidProfileConfiguration)) mockEngine.EXPECT().RecordPublisherInvalidProfileRequests(rctx.Endpoint, "5890", rctx.ProfileIDStr) - mockEngine.EXPECT().RecordPublisherInvalidProfileImpressions("5890", rctx.ProfileIDStr, gomock.Any()) mockFeature.EXPECT().IsTBFFeatureEnabled(gomock.Any(), gomock.Any()).Return(false) mockFeature.EXPECT().IsAnalyticsTrackingThrottled(gomock.Any(), gomock.Any()).Return(false, false) mockProfileMetaData.EXPECT().GetProfileTypePlatform(gomock.Any()).Return(0, false) @@ -5462,10 +6009,9 @@ func TestImpBidCtx_handleBeforeValidationHook(t *testing.T) { }, nil) //prometheus metrics mockEngine.EXPECT().RecordPublisherProfileRequests("5890", "1234") - mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, getPubmaticErrorCode(nbr.InvalidPlatform)) + mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, rctx.PubIDStr, getPubmaticErrorCode(nbr.InvalidPlatform)) mockEngine.EXPECT().RecordNobidErrPrebidServerRequests("5890", int(nbr.InvalidPlatform)) mockEngine.EXPECT().RecordPublisherInvalidProfileRequests(rctx.Endpoint, "5890", rctx.ProfileIDStr) - mockEngine.EXPECT().RecordPublisherInvalidProfileImpressions("5890", rctx.ProfileIDStr, gomock.Any()) mockFeature.EXPECT().IsTBFFeatureEnabled(gomock.Any(), gomock.Any()).Return(false) mockFeature.EXPECT().IsAnalyticsTrackingThrottled(gomock.Any(), gomock.Any()).Return(false, false) mockProfileMetaData.EXPECT().GetProfileTypePlatform(gomock.Any()).Return(0, false) @@ -5518,7 +6064,7 @@ func TestImpBidCtx_handleBeforeValidationHook(t *testing.T) { }, nil) //prometheus metrics mockEngine.EXPECT().RecordPublisherProfileRequests("5890", "1234") - mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, getPubmaticErrorCode(nbr.AllPartnerThrottled)) + mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, rctx.PubIDStr, getPubmaticErrorCode(nbr.AllPartnerThrottled)) mockEngine.EXPECT().RecordNobidErrPrebidServerRequests("5890", int(nbr.AllPartnerThrottled)) mockEngine.EXPECT().RecordPublisherRequests(rctx.Endpoint, "5890", rctx.Platform) mockFeature.EXPECT().IsTBFFeatureEnabled(gomock.Any(), gomock.Any()).Return(false) @@ -5573,7 +6119,7 @@ func TestImpBidCtx_handleBeforeValidationHook(t *testing.T) { mockCache.EXPECT().GetAdunitConfigFromCache(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&adunitconfig.AdUnitConfig{}) //prometheus metrics mockEngine.EXPECT().RecordPublisherProfileRequests("5890", "1234") - mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, getPubmaticErrorCode(nbr.InvalidImpressionTagID)) + mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, rctx.PubIDStr, getPubmaticErrorCode(nbr.InvalidImpressionTagID)) mockEngine.EXPECT().RecordNobidErrPrebidServerRequests("5890", int(nbr.InvalidImpressionTagID)) mockEngine.EXPECT().RecordPublisherRequests(rctx.Endpoint, "5890", rctx.Platform) mockFeature.EXPECT().IsTBFFeatureEnabled(gomock.Any(), gomock.Any()).Return(false) @@ -5619,7 +6165,7 @@ func TestImpBidCtx_handleBeforeValidationHook(t *testing.T) { mockCache.EXPECT().GetAdunitConfigFromCache(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&adunitconfig.AdUnitConfig{}) //prometheus metrics mockEngine.EXPECT().RecordPublisherProfileRequests("5890", "1234") - mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, getPubmaticErrorCode(openrtb3.NoBidInvalidRequest)) + mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, rctx.PubIDStr, getPubmaticErrorCode(openrtb3.NoBidInvalidRequest)) mockEngine.EXPECT().RecordNobidErrPrebidServerRequests("5890", int(openrtb3.NoBidInvalidRequest)) mockEngine.EXPECT().RecordPublisherRequests(rctx.Endpoint, "5890", rctx.Platform) mockFeature.EXPECT().IsTBFFeatureEnabled(gomock.Any(), gomock.Any()).Return(false) @@ -6572,37 +7118,83 @@ func TestSetImpBidFloorParams(t *testing.T) { } } -func TestUpdateProfileAppStoreUrl(t *testing.T) { +func TestGetProfileAppStoreUrl(t *testing.T) { + type args struct { + rctx models.RequestCtx + } tests := []struct { - name string - rctx models.RequestCtx - bidRequest *openrtb2.BidRequest - impExt *models.ImpExtension - wantAppStoreURL string - wantSourceApp string + name string + args args + want string + want1 bool }{ { name: "AppStoreUrl missing in DB", - rctx: models.RequestCtx{ - PartnerConfigMap: map[int]map[string]string{ - models.VersionLevelConfigID: {}, + args: args{ + rctx: models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + models.VersionLevelConfigID: {}, + }, }, }, - bidRequest: &openrtb2.BidRequest{App: &openrtb2.App{}}, - wantAppStoreURL: "", + want: "", + want1: false, }, { name: "Invalid AppStoreUrl", - rctx: models.RequestCtx{ - PartnerConfigMap: map[int]map[string]string{ - models.VersionLevelConfigID: { - models.AppStoreUrl: "invalid-url", + args: args{ + rctx: models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + models.VersionLevelConfigID: { + models.AppStoreUrl: "invalid-url", + }, + }, + }, + }, + want: "invalid-url", + want1: false, + }, + { + name: "Valid AppStoreUrl", + args: args{ + rctx: models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + models.VersionLevelConfigID: { + models.AppStoreUrl: "https://apps.apple.com/app/id123456789", + }, }, }, }, - bidRequest: &openrtb2.BidRequest{App: &openrtb2.App{}}, - wantAppStoreURL: "invalid-url", + want: "https://apps.apple.com/app/id123456789", + want1: true, }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, got1 := getProfileAppStoreUrl(tt.args.rctx) + assert.Equal(t, tt.want, got) + assert.Equal(t, tt.want1, got1) + }) + } +} + +func TestUpdateSkadnSourceapp(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + type feilds struct { + m *OpenWrap + } + tests := []struct { + name string + rctx models.RequestCtx + bidRequest *openrtb2.BidRequest + impExt *models.ImpExtension + wantAppStoreURL string + wantSourceApp string + fe feilds + setup func() *mock_metrics.MockMetricsEngine + }{ { name: "Valid AppStoreUrl os is ios and SKAdnetwork is present in imp.ext", rctx: models.RequestCtx{ @@ -6611,6 +7203,9 @@ func TestUpdateProfileAppStoreUrl(t *testing.T) { models.AppStoreUrl: "https://apps.apple.com/app/id123456789", }, }, + AppLovinMax: models.AppLovinMax{ + AppStoreUrl: "https://apps.apple.com/app/id123456789", + }, }, bidRequest: &openrtb2.BidRequest{ App: &openrtb2.App{}, @@ -6626,6 +7221,9 @@ func TestUpdateProfileAppStoreUrl(t *testing.T) { impExt: &models.ImpExtension{ SKAdnetwork: json.RawMessage(`{}`), }, + setup: func() *mock_metrics.MockMetricsEngine { + return mock_metrics.NewMockMetricsEngine(ctrl) + }, wantAppStoreURL: "https://apps.apple.com/app/id123456789", wantSourceApp: "123456789", }, @@ -6634,9 +7232,12 @@ func TestUpdateProfileAppStoreUrl(t *testing.T) { rctx: models.RequestCtx{ PartnerConfigMap: map[int]map[string]string{ models.VersionLevelConfigID: { - models.AppStoreUrl: "https://apps.apple.com/app/id123456789", + models.AppStoreUrl: "https://apps.apple.com/app/id", }, }, + AppLovinMax: models.AppLovinMax{ + AppStoreUrl: "https://apps.apple.com/app/id", + }, }, bidRequest: &openrtb2.BidRequest{ App: &openrtb2.App{}, @@ -6649,7 +7250,10 @@ func TestUpdateProfileAppStoreUrl(t *testing.T) { }, }, }, - wantAppStoreURL: "https://apps.apple.com/app/id123456789", + setup: func() *mock_metrics.MockMetricsEngine { + return mock_metrics.NewMockMetricsEngine(ctrl) + }, + wantAppStoreURL: "https://apps.apple.com/app/id", }, { name: "Valid AppStoreUrl os is ios but SKAdnetwork missing in imp.ext", @@ -6659,6 +7263,9 @@ func TestUpdateProfileAppStoreUrl(t *testing.T) { models.AppStoreUrl: "https://apps.apple.com/app/id123456789", }, }, + AppLovinMax: models.AppLovinMax{ + AppStoreUrl: "https://apps.apple.com/app/id123456789", + }, }, bidRequest: &openrtb2.BidRequest{ App: &openrtb2.App{}, @@ -6671,17 +7278,25 @@ func TestUpdateProfileAppStoreUrl(t *testing.T) { }, }, }, + setup: func() *mock_metrics.MockMetricsEngine { + return mock_metrics.NewMockMetricsEngine(ctrl) + }, impExt: &models.ImpExtension{}, wantAppStoreURL: "https://apps.apple.com/app/id123456789", }, { - name: "Valid AppStoreUrl os is ios but Itunes ID missing in AppStoreUrl", + name: "Valid AppStoreUrl os is ios but Itunes ID missing in AppStoreUrl(url is of Android)", rctx: models.RequestCtx{ PartnerConfigMap: map[int]map[string]string{ models.VersionLevelConfigID: { models.AppStoreUrl: "https://apps.apple.com/app/", }, }, + AppLovinMax: models.AppLovinMax{ + AppStoreUrl: "https://apps.apple.com/app/", + }, + PubIDStr: "5890", + ProfileIDStr: "1234", }, bidRequest: &openrtb2.BidRequest{ App: &openrtb2.App{}, @@ -6694,6 +7309,11 @@ func TestUpdateProfileAppStoreUrl(t *testing.T) { }, }, }, + setup: func() *mock_metrics.MockMetricsEngine { + mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) + mockEngine.EXPECT().RecordFailedParsingItuneID("5890", "1234") + return mockEngine + }, impExt: &models.ImpExtension{ SKAdnetwork: json.RawMessage(`{}`), }, @@ -6703,8 +7323,11 @@ func TestUpdateProfileAppStoreUrl(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - gotAppStoreURL := getProfileAppStoreUrlAndUpdateItunesID(tt.rctx, tt.bidRequest, tt.impExt) - assert.Equal(t, tt.wantAppStoreURL, gotAppStoreURL) + metricsEngine := tt.setup() + tt.fe.m = &OpenWrap{ + metricEngine: metricsEngine, + } + tt.fe.m.updateSkadnSourceapp(tt.rctx, tt.bidRequest, tt.impExt) if tt.impExt != nil { if tt.impExt.SKAdnetwork != nil { var skAdnetwork map[string]interface{} diff --git a/modules/pubmatic/openwrap/bidderparams/pubmatic.go b/modules/pubmatic/openwrap/bidderparams/pubmatic.go index e1583092c34..57e8a0a0741 100644 --- a/modules/pubmatic/openwrap/bidderparams/pubmatic.go +++ b/modules/pubmatic/openwrap/bidderparams/pubmatic.go @@ -16,7 +16,6 @@ func PreparePubMaticParamsV25(rctx models.RequestCtx, cache cache.Cache, bidRequ WrapExt: getPubMaticWrapperExt(rctx, partnerID), Keywords: getImpExtPubMaticKeyWords(impExt, rctx.PartnerConfigMap[partnerID][models.BidderCode]), Floors: getApplovinBidFloors(rctx, imp), - SendBurl: rctx.SendBurl, } slots, slotMap, slotMappingInfo, _ := getSlotMeta(rctx, cache, bidRequest, imp, impExt, partnerID) diff --git a/modules/pubmatic/openwrap/bidderparams/pubmatic_test.go b/modules/pubmatic/openwrap/bidderparams/pubmatic_test.go index 14b004fd4f1..4ab88fd2fa5 100644 --- a/modules/pubmatic/openwrap/bidderparams/pubmatic_test.go +++ b/modules/pubmatic/openwrap/bidderparams/pubmatic_test.go @@ -1609,7 +1609,6 @@ func TestPreparePubMaticParamsV25(t *testing.T) { }, }, }, - SendBurl: true, }, cache: mockCache, impExt: models.ImpExtension{ @@ -1655,7 +1654,7 @@ func TestPreparePubMaticParamsV25(t *testing.T) { matchedSlot: "/Test_Adunit1234@Div1@200x300", matchedPattern: "", isRegexSlot: false, - params: []byte(`{"publisherId":"5890","adSlot":"/Test_Adunit1234@DIV1@200x300","wrapper":{"version":1,"profile":123},"keywords":[{"key":"test_key1","value":["test_value1","test_value2"]},{"key":"test_key2","value":["test_value1","test_value2"]}],"floors":[1.5,1.2,2.2],"sendburl":true}`), + params: []byte(`{"publisherId":"5890","adSlot":"/Test_Adunit1234@DIV1@200x300","wrapper":{"version":1,"profile":123},"keywords":[{"key":"test_key1","value":["test_value1","test_value2"]},{"key":"test_key2","value":["test_value1","test_value2"]}],"floors":[1.5,1.2,2.2]}`), wantErr: false, }, }, diff --git a/modules/pubmatic/openwrap/cache/gocache/partner_config.go b/modules/pubmatic/openwrap/cache/gocache/partner_config.go index 6ca2ea02c49..76d1ed676d1 100644 --- a/modules/pubmatic/openwrap/cache/gocache/partner_config.go +++ b/modules/pubmatic/openwrap/cache/gocache/partner_config.go @@ -72,6 +72,7 @@ func (c *cache) getActivePartnerConfigAndPopulateWrapperMappings(pubID, profileI } if len(partnerConfigMap) == 0 { + c.cache.Set(cacheKey, partnerConfigMap, getSeconds(c.cfg.CacheDefaultExpiry)) return fmt.Errorf("there are no active partners for pubId:%d, profileId:%d, displayVersion:%d", pubID, profileID, displayVersion) } diff --git a/modules/pubmatic/openwrap/cache/gocache/partner_config_test.go b/modules/pubmatic/openwrap/cache/gocache/partner_config_test.go index e7ce337d00a..e1281244a28 100644 --- a/modules/pubmatic/openwrap/cache/gocache/partner_config_test.go +++ b/modules/pubmatic/openwrap/cache/gocache/partner_config_test.go @@ -420,7 +420,7 @@ func Test_cache_getActivePartnerConfigAndPopulateWrapperMappings(t *testing.T) { displayVersion: testVersionID, }, want: want{ - cacheEntry: false, + cacheEntry: true, err: fmt.Errorf("there are no active partners for pubId:%d, profileId:%d, displayVersion:%d", testPubID, testProfileID, testVersionID), }, setup: func() { diff --git a/modules/pubmatic/openwrap/config/config.go b/modules/pubmatic/openwrap/config/config.go index 0a634af2f62..112a50a2770 100755 --- a/modules/pubmatic/openwrap/config/config.go +++ b/modules/pubmatic/openwrap/config/config.go @@ -101,7 +101,6 @@ type PixelView struct { type FeatureToggle struct { VASTUnwrapPercent int - VASTUnwrapStatsPercent int AnalyticsThrottlingPercentage string } diff --git a/modules/pubmatic/openwrap/defaultbids.go b/modules/pubmatic/openwrap/defaultbids.go index af2a3fedb08..c7f128e3ce2 100644 --- a/modules/pubmatic/openwrap/defaultbids.go +++ b/modules/pubmatic/openwrap/defaultbids.go @@ -2,6 +2,7 @@ package openwrap import ( "encoding/json" + "fmt" "strconv" "github.com/prebid/openrtb/v20/openrtb2" @@ -55,8 +56,7 @@ func (m *OpenWrap) addDefaultBids(rctx *models.RequestCtx, bidResponse *openrtb2 if defaultBids[impID] == nil { defaultBids[impID] = make(map[string][]openrtb2.Bid) } - - uuid := uuid.NewV4().String() + uuid, _ := m.uuidGenerator.Generate() bidExt := newDefaultBidExt(*rctx, impID, bidder, bidResponseExt) bidExtJson, _ := json.Marshal(bidExt) @@ -128,6 +128,12 @@ func (m *OpenWrap) addDefaultBids(rctx *models.RequestCtx, bidResponse *openrtb2 } } + //Do not add nobids in default bids for throttled adapter and non-mapped bidders in case of web-s2s + //as we are forming forming seatNonBids from defaultBids which is used for owlogger + if rctx.Endpoint == models.EndpointWebS2S { + return defaultBids + } + // add nobids for throttled adapter to all the impressions (how do we set profile with custom list of bidders at impression level?) for bidder := range rctx.AdapterThrottleMap { for impID := range rctx.ImpBidCtx { // ImpBidCtx is used only for list of impID, it does not have data of throttled adapters @@ -173,6 +179,67 @@ func (m *OpenWrap) addDefaultBids(rctx *models.RequestCtx, bidResponse *openrtb2 return defaultBids } +func (m *OpenWrap) addDefaultBidsForMultiFloorsConfig(rctx *models.RequestCtx, bidResponse *openrtb2.BidResponse, bidResponseExt openrtb_ext.ExtBidResponse) map[string]map[string][]openrtb2.Bid { + + // MultiBidMultiFloor is only supported for AppLovinMax + if rctx.Endpoint != models.EndpointAppLovinMax || !rctx.AppLovinMax.MultiFloorsConfig.Enabled { + return rctx.DefaultBids + } + + defaultBids := rctx.DefaultBids + bidderExcludeFloors := make(map[string]struct{}, len(bidResponse.SeatBid)) //exclude floors which are already present in bidresponse + var adunitFloors []float64 //floors for each adunit + + for _, seatBid := range bidResponse.SeatBid { + if rctx.PrebidBidderCode[seatBid.Seat] == models.BidderPubMatic || rctx.PrebidBidderCode[seatBid.Seat] == models.BidderPubMaticSecondaryAlias { + for _, bid := range seatBid.Bid { + impId, _ := models.GetImpressionID(bid.ImpID) + floorValue := rctx.ImpBidCtx[impId].BidCtx[bid.ID].BidExt.MultiBidMultiFloorValue + if floorValue > 0 { + key := fmt.Sprintf("%s-%s-%.2f", impId, seatBid.Seat, floorValue) + bidderExcludeFloors[key] = struct{}{} + } + } + } + } + + for impID, impCtx := range rctx.ImpBidCtx { + adunitFloors = rctx.AppLovinMax.MultiFloorsConfig.Config[impCtx.TagID] + for bidder := range impCtx.Bidders { + if prebidBidderCode := rctx.PrebidBidderCode[bidder]; prebidBidderCode != models.BidderPubMatic && prebidBidderCode != models.BidderPubMaticSecondaryAlias { + continue + } + + if defaultBids[impID] == nil { + defaultBids[impID] = make(map[string][]openrtb2.Bid) + } + + //if defaultbid is already present for pubmatic, then reset it, as we are adding new defaultbids with MultiBidMultiFloor + if _, ok := defaultBids[impID][bidder]; ok { + defaultBids[impID][bidder] = make([]openrtb2.Bid, 0) + } + + //exclude floors which are already present in bidresponse for defaultbids + for _, floor := range adunitFloors { + key := fmt.Sprintf("%s-%s-%.2f", impID, bidder, floor) + if _, ok := bidderExcludeFloors[key]; !ok { + uuid, _ := m.uuidGenerator.Generate() + bidExt := newDefaultBidExtMultiFloors(floor, bidder, bidResponseExt) + bidExtJson, _ := json.Marshal(bidExt) + defaultBids[impID][bidder] = append(defaultBids[impID][bidder], openrtb2.Bid{ + ID: uuid, + ImpID: impID, + Ext: bidExtJson, + }) + } + + } + + } + } + return defaultBids +} + // getNonBRCodeFromBidRespExt maps the error-code present in prebid partner response with standard nonBR code func getNonBRCodeFromBidRespExt(bidder string, bidResponseExt openrtb_ext.ExtBidResponse) *openrtb3.NoBidReason { errs := bidResponseExt.Errors[openrtb_ext.BidderName(bidder)] @@ -218,6 +285,13 @@ func newDefaultBidExt(rctx models.RequestCtx, impID, bidder string, bidResponseE return &bidExt } +func newDefaultBidExtMultiFloors(floor float64, bidder string, bidResponseExt openrtb_ext.ExtBidResponse) *models.BidExt { + return &models.BidExt{ + Nbr: getNonBRCodeFromBidRespExt(bidder, bidResponseExt), + MultiBidMultiFloorValue: floor, + } +} + // TODO : Check if we need this? // func newDefaultVastTagBidExt(rctx models.RequestCtx, impID, bidder, vastTag string, bidResponseExt openrtb_ext.ExtBidResponse) *models.BidExt { // bidExt := models.BidExt{ diff --git a/modules/pubmatic/openwrap/defaultbids_test.go b/modules/pubmatic/openwrap/defaultbids_test.go index 7e493d4c373..8452d7c2c89 100644 --- a/modules/pubmatic/openwrap/defaultbids_test.go +++ b/modules/pubmatic/openwrap/defaultbids_test.go @@ -3,13 +3,33 @@ package openwrap import ( "testing" + "github.com/golang/mock/gomock" + "github.com/prebid/openrtb/v20/openrtb2" "github.com/prebid/openrtb/v20/openrtb3" + "github.com/prebid/prebid-server/v2/currency" "github.com/prebid/prebid-server/v2/errortypes" "github.com/prebid/prebid-server/v2/exchange" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/config" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/geodb" + metrics "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics" + mock_metrics "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics/mock" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/profilemetadata" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/publisherfeature" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/unwrap" "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/uuidutil" "github.com/stretchr/testify/assert" ) +const fakeUuid = "30470a14-2949-4110-abce-b62d57304ad5" + +type TestUUIDGenerator struct{} + +func (TestUUIDGenerator) Generate() (string, error) { + return fakeUuid, nil +} + func TestGetNonBRCodeFromBidRespExt(t *testing.T) { type args struct { bidder string @@ -86,3 +106,587 @@ func TestGetNonBRCodeFromBidRespExt(t *testing.T) { }) } } + +func TestOpenWrap_addDefaultBids(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) + type fields struct { + cfg config.Config + rateConvertor *currency.RateConverter + metricEngine metrics.MetricsEngine + geoInfoFetcher geodb.Geography + pubFeatures publisherfeature.Feature + unwrap unwrap.Unwrap + profileMetaData profilemetadata.ProfileMetaData + uuidGenerator uuidutil.UUIDGenerator + } + type args struct { + rctx *models.RequestCtx + bidResponse *openrtb2.BidResponse + bidResponseExt openrtb_ext.ExtBidResponse + } + tests := []struct { + name string + fields fields + args args + setup func() + want map[string]map[string][]openrtb2.Bid + }{ + { + name: "EndpointWebS2S do not add default bids for slot-not-mapped and partner-throttled", + fields: fields{ + metricEngine: mockEngine, + uuidGenerator: TestUUIDGenerator{}, + }, + args: args{ + rctx: &models.RequestCtx{ + Endpoint: models.EndpointWebS2S, + ImpBidCtx: map[string]models.ImpCtx{ + "imp-1": { + Bidders: map[string]models.PartnerData{ + "pubmatic": { + PrebidBidderCode: "pubmatic", + }, + "openx": { + PrebidBidderCode: "openx", + }, + }, + NonMapped: map[string]struct{}{ + "appnexus": {}, + }, + BidCtx: map[string]models.BidCtx{ + "pubmatic-bid-1": { + BidExt: models.BidExt{}, + }, + }, + }, + }, + AdapterThrottleMap: map[string]struct{}{ + "rubicon": {}, + }, + }, + bidResponse: &openrtb2.BidResponse{ + ID: "bid-1", + SeatBid: []openrtb2.SeatBid{ + { + Seat: "pubmatic", + Bid: []openrtb2.Bid{ + { + ID: "pubmatic-bid-1", + ImpID: "imp-1", + Price: 1.0, + }, + }, + }, + }, + }, + }, + setup: func() { + mockEngine.EXPECT().RecordPartnerResponseErrors(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + }, + want: map[string]map[string][]openrtb2.Bid{ + "imp-1": { + "openx": { + { + ID: "30470a14-2949-4110-abce-b62d57304ad5", + ImpID: "imp-1", + Ext: []byte(`{}`), + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } + m := &OpenWrap{ + cfg: tt.fields.cfg, + metricEngine: tt.fields.metricEngine, + rateConvertor: tt.fields.rateConvertor, + geoInfoFetcher: tt.fields.geoInfoFetcher, + pubFeatures: tt.fields.pubFeatures, + unwrap: tt.fields.unwrap, + profileMetaData: tt.fields.profileMetaData, + uuidGenerator: tt.fields.uuidGenerator, + } + got := m.addDefaultBids(tt.args.rctx, tt.args.bidResponse, tt.args.bidResponseExt) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestOpenWrap_addDefaultBidsForMultiFloorsConfig(t *testing.T) { + type fields struct { + cfg config.Config + metricEngine metrics.MetricsEngine + rateConvertor *currency.RateConverter + geoInfoFetcher geodb.Geography + pubFeatures publisherfeature.Feature + unwrap unwrap.Unwrap + profileMetaData profilemetadata.ProfileMetaData + uuidGenerator uuidutil.UUIDGenerator + } + type args struct { + rctx *models.RequestCtx + bidResponse *openrtb2.BidResponse + bidResponseExt openrtb_ext.ExtBidResponse + } + tests := []struct { + name string + fields fields + args args + want map[string]map[string][]openrtb2.Bid + }{ + { + name: "request is other than applovinmax", + args: args{ + rctx: &models.RequestCtx{ + Endpoint: models.EndpointWebS2S, + DefaultBids: map[string]map[string][]openrtb2.Bid{}, + }, + bidResponse: &openrtb2.BidResponse{ + ID: "bid-1", + SeatBid: []openrtb2.SeatBid{}, + }, + }, + want: map[string]map[string][]openrtb2.Bid{}, + }, + { + name: "request is applovinmax but the multi-floors config is not enabled from DB", + args: args{ + rctx: &models.RequestCtx{ + Endpoint: models.EndpointAppLovinMax, + DefaultBids: map[string]map[string][]openrtb2.Bid{}, + AppLovinMax: models.AppLovinMax{ + MultiFloorsConfig: models.MultiFloorsConfig{ + Enabled: false, + }, + }, + }, + }, + want: map[string]map[string][]openrtb2.Bid{}, + }, + { + name: "mulit-floors config have three floors and no bids in the response", + args: args{ + rctx: &models.RequestCtx{ + Endpoint: models.EndpointAppLovinMax, + DefaultBids: map[string]map[string][]openrtb2.Bid{ + "test-impID-1": { + "pubmatic": { + { + ID: "dbbsdhkldks1234", + ImpID: "test-impID-1", + Ext: []byte(`{}`), + }, + }, + }, + }, + AppLovinMax: models.AppLovinMax{ + MultiFloorsConfig: models.MultiFloorsConfig{ + Enabled: true, + Config: models.ApplovinAdUnitFloors{ + "adunit-1": []float64{1.1, 2.1, 3.1}, + }, + }, + }, + ImpBidCtx: map[string]models.ImpCtx{ + "test-impID-1": { + TagID: "adunit-1", + Bidders: map[string]models.PartnerData{ + "pubmatic": { + PrebidBidderCode: "pubmatic", + }, + }, + }, + }, + PrebidBidderCode: map[string]string{ + "pubmatic": "pubmatic", + }, + }, + bidResponse: &openrtb2.BidResponse{ + ID: "bid-1", + }, + }, + fields: fields{ + uuidGenerator: TestUUIDGenerator{}, + }, + want: map[string]map[string][]openrtb2.Bid{ + "test-impID-1": { + "pubmatic": { + { + ID: "30470a14-2949-4110-abce-b62d57304ad5", + ImpID: "test-impID-1", + Ext: []byte(`{"mbmfv":1.1}`), + }, + { + ID: "30470a14-2949-4110-abce-b62d57304ad5", + ImpID: "test-impID-1", + Ext: []byte(`{"mbmfv":2.1}`), + }, + { + ID: "30470a14-2949-4110-abce-b62d57304ad5", + ImpID: "test-impID-1", + Ext: []byte(`{"mbmfv":3.1}`), + }, + }, + }, + }, + }, + { + name: "mulit-floors config have three floors and only one bid in the response", + args: args{ + rctx: &models.RequestCtx{ + Endpoint: models.EndpointAppLovinMax, + DefaultBids: map[string]map[string][]openrtb2.Bid{}, + AppLovinMax: models.AppLovinMax{ + MultiFloorsConfig: models.MultiFloorsConfig{ + Enabled: true, + Config: models.ApplovinAdUnitFloors{ + "adunit-1": []float64{1.1, 2.1, 3.1}, + }, + }, + }, + ImpBidCtx: map[string]models.ImpCtx{ + "test-impID-1": { + TagID: "adunit-1", + Bidders: map[string]models.PartnerData{ + "pubmatic": { + PrebidBidderCode: "pubmatic", + }, + }, + BidCtx: map[string]models.BidCtx{ + "pubmatic-bid-1": { + BidExt: models.BidExt{ + MultiBidMultiFloorValue: 1.1, + }, + }, + }, + }, + }, + PrebidBidderCode: map[string]string{ + "pubmatic": "pubmatic", + }, + }, + bidResponse: &openrtb2.BidResponse{ + ID: "bid-1", + SeatBid: []openrtb2.SeatBid{ + { + Seat: "pubmatic", + Bid: []openrtb2.Bid{ + { + ID: "pubmatic-bid-1", + ImpID: "test-impID-1", + Price: 1.5, + Ext: []byte(`{"mbmfv":1.1}`), + }, + }, + }, + }, + }, + }, + fields: fields{ + uuidGenerator: TestUUIDGenerator{}, + }, + want: map[string]map[string][]openrtb2.Bid{ + "test-impID-1": { + "pubmatic": { + { + ID: "30470a14-2949-4110-abce-b62d57304ad5", + ImpID: "test-impID-1", + Ext: []byte(`{"mbmfv":2.1}`), + }, + { + ID: "30470a14-2949-4110-abce-b62d57304ad5", + ImpID: "test-impID-1", + Ext: []byte(`{"mbmfv":3.1}`), + }, + }, + }, + }, + }, + { + name: "mulit-floors config have three floors and all three bid in the response", + args: args{ + rctx: &models.RequestCtx{ + Endpoint: models.EndpointAppLovinMax, + DefaultBids: map[string]map[string][]openrtb2.Bid{}, + AppLovinMax: models.AppLovinMax{ + MultiFloorsConfig: models.MultiFloorsConfig{ + Enabled: true, + Config: models.ApplovinAdUnitFloors{ + "adunit-1": []float64{1.1, 2.1, 3.1}, + }, + }, + }, + ImpBidCtx: map[string]models.ImpCtx{ + "test-impID-1": { + TagID: "adunit-1", + Bidders: map[string]models.PartnerData{ + "pubmatic": { + PrebidBidderCode: "pubmatic", + }, + }, + BidCtx: map[string]models.BidCtx{ + "pubmatic-bid-1": { + BidExt: models.BidExt{ + MultiBidMultiFloorValue: 1.1, + }, + }, + "pubmatic-bid-2": { + BidExt: models.BidExt{ + MultiBidMultiFloorValue: 2.1, + }, + }, + "pubmatic-bid-3": { + BidExt: models.BidExt{ + MultiBidMultiFloorValue: 3.1, + }, + }, + }, + }, + }, + PrebidBidderCode: map[string]string{ + "pubmatic": "pubmatic", + }, + }, + bidResponse: &openrtb2.BidResponse{ + ID: "bid-1", + SeatBid: []openrtb2.SeatBid{ + { + Seat: "pubmatic", + Bid: []openrtb2.Bid{ + { + ID: "pubmatic-bid-1", + ImpID: "test-impID-1", + Price: 1.5, + Ext: []byte(`{"mbmfv":1.1}`), + }, + { + ID: "pubmatic-bid-2", + ImpID: "test-impID-1", + Price: 2.5, + Ext: []byte(`{"mbmfv":2.1}`), + }, + { + ID: "pubmatic-bid-3", + ImpID: "test-impID-1", + Price: 3.5, + }, + }, + }, + }, + }, + }, + fields: fields{ + uuidGenerator: TestUUIDGenerator{}, + }, + want: map[string]map[string][]openrtb2.Bid{ + "test-impID-1": {}, + }, + }, + { + name: "mulit-floors config have three floors and only one bid in the response for both partner pubmatic and pubmatic_1123", + args: args{ + rctx: &models.RequestCtx{ + Endpoint: models.EndpointAppLovinMax, + DefaultBids: map[string]map[string][]openrtb2.Bid{}, + AppLovinMax: models.AppLovinMax{ + MultiFloorsConfig: models.MultiFloorsConfig{ + Enabled: true, + Config: models.ApplovinAdUnitFloors{ + "adunit-1": []float64{1.1, 2.1, 3.1}, + }, + }, + }, + ImpBidCtx: map[string]models.ImpCtx{ + "test-impID-1": { + TagID: "adunit-1", + Bidders: map[string]models.PartnerData{ + "pubmatic": { + PrebidBidderCode: "pubmatic", + }, + "pubmatic_1123": { + PrebidBidderCode: "pubmatic", + }, + }, + BidCtx: map[string]models.BidCtx{ + "pubmatic-bid-1": { + BidExt: models.BidExt{ + MultiBidMultiFloorValue: 1.1, + }, + }, + "pubmatic-bid-2": { + BidExt: models.BidExt{ + MultiBidMultiFloorValue: 1.1, + }, + }, + }, + }, + }, + PrebidBidderCode: map[string]string{ + "pubmatic_1123": "pubmatic", + "pubmatic": "pubmatic", + }, + }, + bidResponse: &openrtb2.BidResponse{ + ID: "bid-1", + SeatBid: []openrtb2.SeatBid{ + { + Seat: "pubmatic", + Bid: []openrtb2.Bid{ + { + ID: "pubmatic-bid-1", + ImpID: "test-impID-1", + Price: 1.5, + Ext: []byte(`{"mbmfv":1.1}`), + }, + }, + }, + { + Seat: "pubmatic_1123", + Bid: []openrtb2.Bid{ + { + ID: "pubmatic-bid-2", + ImpID: "test-impID-1", + Price: 1.6, + Ext: []byte(`{"mbmfv":1.1}`), + }, + }, + }, + }, + }, + }, + fields: fields{ + uuidGenerator: TestUUIDGenerator{}, + }, + want: map[string]map[string][]openrtb2.Bid{ + "test-impID-1": { + "pubmatic": { + { + ID: "30470a14-2949-4110-abce-b62d57304ad5", + ImpID: "test-impID-1", + Ext: []byte(`{"mbmfv":2.1}`), + }, + { + ID: "30470a14-2949-4110-abce-b62d57304ad5", + ImpID: "test-impID-1", + Ext: []byte(`{"mbmfv":3.1}`), + }, + }, + "pubmatic_1123": { + { + ID: "30470a14-2949-4110-abce-b62d57304ad5", + ImpID: "test-impID-1", + Ext: []byte(`{"mbmfv":2.1}`), + }, + { + ID: "30470a14-2949-4110-abce-b62d57304ad5", + ImpID: "test-impID-1", + Ext: []byte(`{"mbmfv":3.1}`), + }, + }, + }, + }, + }, + // { + // name: "mulit-floors config have three floors and no bid in the response for both partner pubmatic and pubmatic_1123", + // args: args{ + // rctx: &models.RequestCtx{ + // Endpoint: models.EndpointAppLovinMax, + // DefaultBids: map[string]map[string][]openrtb2.Bid{}, + // AppLovinMax: models.AppLovinMax{ + // MultiFloorsConfig: models.MultiFloorsConfig{ + // Enabled: true, + // Config: models.ApplovinAdUnitFloors{ + // "adunit-1": []float64{1.1, 2.1, 3.1}, + // }, + // }, + // }, + // ImpBidCtx: map[string]models.ImpCtx{ + // "test-impID-1": { + // TagID: "adunit-1", + // Bidders: map[string]models.PartnerData{ + // "pubmatic": { + // PrebidBidderCode: "pubmatic", + // }, + // "pubmatic_1123": { + // PrebidBidderCode: "pubmatic", + // }, + // }, + // }, + // }, + // PrebidBidderCode: map[string]string{ + // "pubmatic_1123": "pubmatic", + // "pubmatic": "pubmatic", + // }, + // }, + // bidResponse: &openrtb2.BidResponse{ + // ID: "bid-1", + // SeatBid: []openrtb2.SeatBid{}, + // }, + // }, + // fields: fields{ + // uuidGenerator: TestUUIDGenerator{}, + // }, + // want: map[string]map[string][]openrtb2.Bid{ + // "test-impID-1": { + // "pubmatic": { + // { + // ID: "30470a14-2949-4110-abce-b62d57304ad5", + // ImpID: "test-impID-1", + // Ext: []byte(`{"mbmfv":1.1}`), + // }, + // { + // ID: "30470a14-2949-4110-abce-b62d57304ad5", + // ImpID: "test-impID-1", + // Ext: []byte(`{"mbmfv":2.1}`), + // }, + // { + // ID: "30470a14-2949-4110-abce-b62d57304ad5", + // ImpID: "test-impID-1", + // Ext: []byte(`{"mbmfv":3.1}`), + // }, + // }, + // "pubmatic_1123": { + // { + // ID: "30470a14-2949-4110-abce-b62d57304ad5", + // ImpID: "test-impID-1", + // Ext: []byte(`{"mbmfv":1.1}`), + // }, + // { + // ID: "30470a14-2949-4110-abce-b62d57304ad5", + // ImpID: "test-impID-1", + // Ext: []byte(`{"mbmfv":2.1}`), + // }, + // { + // ID: "30470a14-2949-4110-abce-b62d57304ad5", + // ImpID: "test-impID-1", + // Ext: []byte(`{"mbmfv":3.1}`), + // }, + // }, + // }, + // }, + // }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &OpenWrap{ + cfg: tt.fields.cfg, + metricEngine: tt.fields.metricEngine, + rateConvertor: tt.fields.rateConvertor, + geoInfoFetcher: tt.fields.geoInfoFetcher, + pubFeatures: tt.fields.pubFeatures, + unwrap: tt.fields.unwrap, + profileMetaData: tt.fields.profileMetaData, + uuidGenerator: tt.fields.uuidGenerator, + } + got := m.addDefaultBidsForMultiFloorsConfig(tt.args.rctx, tt.args.bidResponse, tt.args.bidResponseExt) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/modules/pubmatic/openwrap/device.go b/modules/pubmatic/openwrap/device.go index 82dbd9acf2b..73132639bf0 100644 --- a/modules/pubmatic/openwrap/device.go +++ b/modules/pubmatic/openwrap/device.go @@ -14,6 +14,8 @@ func populateDeviceContext(dvc *models.DeviceCtx, device *openrtb2.Device) { } //this is needed in determine ifa_type parameter dvc.DeviceIFA = strings.TrimSpace(device.IFA) + dvc.Model = device.Model + dvc.ID = getDeviceID(dvc, device) if device.Ext == nil { return @@ -26,10 +28,39 @@ func populateDeviceContext(dvc *models.DeviceCtx, device *openrtb2.Device) { } dvc.Ext = &deviceExt + if dvc.ID == "" { + dvc.ID, _ = dvc.Ext.GetSessionID() + } //update device IFA Details updateDeviceIFADetails(dvc) } +// getDeviceID retrieves deviceID for logging purpose +func getDeviceID(dvc *models.DeviceCtx, device *openrtb2.Device) string { + if dvc.DeviceIFA != "" { + return dvc.DeviceIFA + } + if device.DIDSHA1 != "" { + return device.DIDSHA1 + } + if device.DIDMD5 != "" { + return device.DIDMD5 + } + if device.DPIDSHA1 != "" { + return device.DPIDSHA1 + } + if device.DPIDMD5 != "" { + return device.DPIDMD5 + } + if device.MACSHA1 != "" { + return device.MACSHA1 + } + if device.MACMD5 != "" { + return device.MACMD5 + } + return "" +} + func updateDeviceIFADetails(dvc *models.DeviceCtx) { if dvc == nil || dvc.Ext == nil { return diff --git a/modules/pubmatic/openwrap/device_test.go b/modules/pubmatic/openwrap/device_test.go index c9b48999951..765dc5ffae7 100644 --- a/modules/pubmatic/openwrap/device_test.go +++ b/modules/pubmatic/openwrap/device_test.go @@ -53,6 +53,7 @@ func TestPopulateDeviceExt(t *testing.T) { want: want{ deviceCtx: models.DeviceCtx{ DeviceIFA: `test_ifa`, + ID: "test_ifa", }, }, }, @@ -67,6 +68,7 @@ func TestPopulateDeviceExt(t *testing.T) { want: want{ deviceCtx: models.DeviceCtx{ DeviceIFA: `test_ifa`, + ID: "test_ifa", Ext: func() *models.ExtDevice { deviceExt := &models.ExtDevice{} deviceExt.UnmarshalJSON([]byte(`{"anykey": "anyval"}`)) @@ -85,6 +87,7 @@ func TestPopulateDeviceExt(t *testing.T) { want: want{ deviceCtx: models.DeviceCtx{ DeviceIFA: `test_ifa`, + ID: "test_ifa", Ext: models.NewExtDevice(), }, }, @@ -101,6 +104,7 @@ func TestPopulateDeviceExt(t *testing.T) { /* removed_invalid_ifatype */ deviceCtx: models.DeviceCtx{ DeviceIFA: `test_ifa`, + ID: "test_ifa", Ext: models.NewExtDevice(), }, }, @@ -116,6 +120,7 @@ func TestPopulateDeviceExt(t *testing.T) { want: want{ deviceCtx: models.DeviceCtx{ DeviceIFA: `test_ifa`, + ID: "test_ifa", IFATypeID: ptrutil.ToPtr(models.DeviceIFATypeID[models.DeviceIFATypeDPID]), Ext: func() *models.ExtDevice { deviceExt := &models.ExtDevice{} @@ -136,6 +141,7 @@ func TestPopulateDeviceExt(t *testing.T) { want: want{ deviceCtx: models.DeviceCtx{ DeviceIFA: `test_ifa`, + ID: "test_ifa", IFATypeID: ptrutil.ToPtr(models.DeviceIFATypeID[models.DeviceIFATypeSESSIONID]), Ext: func() *models.ExtDevice { deviceExt := &models.ExtDevice{} @@ -169,6 +175,7 @@ func TestPopulateDeviceExt(t *testing.T) { want: want{ deviceCtx: models.DeviceCtx{ DeviceIFA: `test_ifa`, + ID: "test_ifa", Ext: func() *models.ExtDevice { deviceExt := &models.ExtDevice{} deviceExt.UnmarshalJSON([]byte(`{"atts":"invalid_value"}`)) @@ -194,6 +201,29 @@ func TestPopulateDeviceExt(t *testing.T) { }, }, }, + { + name: `deviceID_as_sessionid_incase_ifa_not_present`, + args: args{ + device: &openrtb2.Device{ + Model: "iphone,11", + Ext: json.RawMessage(`{"atts": 1,"session_id": "sample_session_id"}`), + }, + }, + want: want{ + deviceCtx: models.DeviceCtx{ + DeviceIFA: `sample_session_id`, + ID: "sample_session_id", + Model: "iphone,11", + IFATypeID: ptrutil.ToPtr(models.DeviceIFATypeID[models.DeviceIFATypeSESSIONID]), + Ext: func() *models.ExtDevice { + deviceExt := &models.ExtDevice{} + deviceExt.UnmarshalJSON([]byte(`{"atts":1,"session_id": "sample_session_id"}`)) + deviceExt.SetIFAType("sessionid") + return deviceExt + }(), + }, + }, + }, { name: `all_valid_ext_parameters`, args: args{ @@ -205,6 +235,7 @@ func TestPopulateDeviceExt(t *testing.T) { want: want{ deviceCtx: models.DeviceCtx{ DeviceIFA: `test_ifa`, + ID: "test_ifa", IFATypeID: ptrutil.ToPtr(models.DeviceIFATypeID[models.DeviceIFATypeSESSIONID]), Ext: func() *models.ExtDevice { deviceExt := &models.ExtDevice{} @@ -555,3 +586,96 @@ func TestAmendDeviceObject(t *testing.T) { }) } } + +func TestGetDeviceID(t *testing.T) { + tests := []struct { + name string + dvc *models.DeviceCtx + device *openrtb2.Device + expected string + }{ + { + name: "Empty input", + dvc: &models.DeviceCtx{}, + device: &openrtb2.Device{}, + expected: "", + }, + { + name: "DeviceIFA present", + dvc: &models.DeviceCtx{DeviceIFA: "test-ifa"}, + device: &openrtb2.Device{IFA: "test-ifa"}, + expected: "test-ifa", + }, + { + name: "DIDSHA1 present", + dvc: &models.DeviceCtx{}, + device: &openrtb2.Device{DIDSHA1: "test-didsha1"}, + expected: "test-didsha1", + }, + { + name: "DIDMD5 present", + dvc: &models.DeviceCtx{}, + device: &openrtb2.Device{DIDMD5: "test-didmd5"}, + expected: "test-didmd5", + }, + { + name: "DPIDSHA1 present", + dvc: &models.DeviceCtx{}, + device: &openrtb2.Device{DPIDSHA1: "test-dpidsha1"}, + expected: "test-dpidsha1", + }, + { + name: "DPIDMD5 present", + dvc: &models.DeviceCtx{}, + device: &openrtb2.Device{DPIDMD5: "test-dpidmd5"}, + expected: "test-dpidmd5", + }, + { + name: "MACSHA1 present", + dvc: &models.DeviceCtx{}, + device: &openrtb2.Device{MACSHA1: "test-macsha1"}, + expected: "test-macsha1", + }, + { + name: "MACMD5 present", + dvc: &models.DeviceCtx{}, + device: &openrtb2.Device{MACMD5: "test-macmd5"}, + expected: "test-macmd5", + }, + { + name: "Multiple fields present, DeviceIFA takes precedence", + dvc: &models.DeviceCtx{DeviceIFA: "test-ifa"}, + device: &openrtb2.Device{ + IFA: "test-ifa", + DIDSHA1: "test-didsha1", + DIDMD5: "test-didmd5", + DPIDSHA1: "test-dpidsha1", + DPIDMD5: "test-dpidmd5", + MACSHA1: "test-macsha1", + MACMD5: "test-macmd5", + }, + expected: "test-ifa", + }, + { + name: "Multiple fields present, precedence order respected", + dvc: &models.DeviceCtx{}, + device: &openrtb2.Device{ + DIDMD5: "test-didmd5", + DPIDSHA1: "test-dpidsha1", + DPIDMD5: "test-dpidmd5", + MACSHA1: "test-macsha1", + MACMD5: "test-macmd5", + }, + expected: "test-didmd5", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := getDeviceID(tt.dvc, tt.device) + if got != tt.expected { + t.Errorf("getDeviceID() = %v, want %v", got, tt.expected) + } + }) + } +} diff --git a/modules/pubmatic/openwrap/endpoints/legacy/ctv/constant.go b/modules/pubmatic/openwrap/endpoints/legacy/ctv/constant.go index d3c57c63285..2233911e0c4 100644 --- a/modules/pubmatic/openwrap/endpoints/legacy/ctv/constant.go +++ b/modules/pubmatic/openwrap/endpoints/legacy/ctv/constant.go @@ -287,10 +287,12 @@ const ( ORTBUserGeoUtcOffset = "user.geo.utcoffset" //ORTBUserGeoUtcOffset get api parameter user.geo.utcoffset // User.Ext.Consent level parameters - ORTBUserExtConsent = "user.ext.consent" //ORTBUserExtConsent get api parameter user.ext.consent - ORTBUserExtEIDS = "user.ext.eids" //ORTBUserExtEIDS get api parameter user.ext.eids - ORTBUserData = "user.data" //ORTBUserData get api parameter user.data - ORTBExtEIDS = "eids" //ORTBExtEIDS parameter + ORTBUserExtConsent = "user.ext.consent" //ORTBUserExtConsent get api parameter user.ext.consent + ORTBUserExtEIDS = "user.ext.eids" //ORTBUserExtEIDS get api parameter user.ext.eids + ORTBUserExtSessionDuration = "user.ext.sessionduration" //ORTBUserExtSessionDuration get api parameter user.ext.sessionduration + ORTBUserExtImpDepth = "user.ext.impdepth" //ORTBUserExtImpDepth get api parameter user.ext.impdepth + ORTBUserData = "user.data" //ORTBUserData get api parameter user.data + ORTBExtEIDS = "eids" //ORTBExtEIDS parameter // Regs.Ext.GDPR level parameters ORTBRegsExtGdpr = "regs.ext.gdpr" //ORTBRegsExtGdpr get api parameter regs.ext.gdpr @@ -350,6 +352,8 @@ const ( ORTBUserGeoExt = "user.geo.ext" //ORTBUserGeoExt get api parameter user.geo.ext ORTBUserExtUIDS = "uids" //ORTBUserExtUIDs get api parameter user.ext.eids.uids ORTBUserExtID = "id" //ORTBUserExtID get api parameter user.ext.eids.uids.id + ORTBExtSessionDuration = "sessionduration" //ORTBUserExtSessionDuration get api parameter user.ext.sessionduration + ORTBExtImpDepth = "impdepth" //ORTBUserExtImpDepth get api parameter user.ext.impdepth // ORTB Extension Standard Keys */ //// get api parameter xtension Standard Klevel parameters ORTBExtWrapper = "wrapper" //ORTBExtWrapper get api parameter wrapper diff --git a/modules/pubmatic/openwrap/endpoints/legacy/ctv/parser_implementation.go b/modules/pubmatic/openwrap/endpoints/legacy/ctv/parser_implementation.go index 97184c18f6f..a53295155cb 100644 --- a/modules/pubmatic/openwrap/endpoints/legacy/ctv/parser_implementation.go +++ b/modules/pubmatic/openwrap/endpoints/legacy/ctv/parser_implementation.go @@ -5,8 +5,10 @@ import ( "fmt" "net/http" "net/url" + "strconv" "strings" + "git.pubmatic.com/PubMatic/go-common/logger" "github.com/golang/glog" "github.com/prebid/openrtb/v20/adcom1" "github.com/prebid/openrtb/v20/openrtb2" @@ -4506,6 +4508,70 @@ func (o *OpenRTB) ORTBUserExtEIDS() (err error) { return } +// ORTBUserExtSessionDuration will read and set ortb User.Ext.sessionduration parameter +func (o *OpenRTB) ORTBUserExtSessionDuration() (err error) { + valStr, ok := o.values.GetString(ORTBUserExtSessionDuration) + if !ok || valStr == "" { + return + } + if o.ortb.User == nil { + o.ortb.User = &openrtb2.User{} + } + userExt := map[string]interface{}{} + if o.ortb.User.Ext != nil { + if err = json.Unmarshal(o.ortb.User.Ext, &userExt); err != nil { + return + } + } + + val, err := strconv.ParseUint(valStr, 10, 64) + if err != nil { + logger.Warn("Invalid session duration value '%v': %v", valStr, err) + return nil + } + userExt[ORTBExtSessionDuration] = int64(val) + + data, err := json.Marshal(userExt) + if err != nil { + return + } + + o.ortb.User.Ext = data + return +} + +// ORTBUserExtImpDepth will read and set ortb User.Ext.impdepth parameter +func (o *OpenRTB) ORTBUserExtImpDepth() (err error) { + valStr, ok := o.values.GetString(ORTBUserExtImpDepth) + if !ok || valStr == "" { + return + } + if o.ortb.User == nil { + o.ortb.User = &openrtb2.User{} + } + userExt := map[string]interface{}{} + if o.ortb.User.Ext != nil { + if err = json.Unmarshal(o.ortb.User.Ext, &userExt); err != nil { + return + } + } + + val, err := strconv.ParseUint(valStr, 10, 64) + if err != nil { + logger.Warn("Invalid imp depth value '%v': %v", valStr, err) + return nil + } + userExt[ORTBExtImpDepth] = int64(val) + + data, err := json.Marshal(userExt) + if err != nil { + return + } + + o.ortb.User.Ext = data + return +} + // ORTBUserData will read and set ortb user.data parameter func (o *OpenRTB) ORTBUserData() (err error) { dataValue, ok := o.values.GetString(ORTBUserData) diff --git a/modules/pubmatic/openwrap/endpoints/legacy/ctv/parser_implementation_test.go b/modules/pubmatic/openwrap/endpoints/legacy/ctv/parser_implementation_test.go index 60090727e6e..9791267fa79 100644 --- a/modules/pubmatic/openwrap/endpoints/legacy/ctv/parser_implementation_test.go +++ b/modules/pubmatic/openwrap/endpoints/legacy/ctv/parser_implementation_test.go @@ -316,8 +316,10 @@ func getTestValues() url.Values { ORTBRequestExtPrebidTransparencyContent: {`{"pubmatic": {"include": 1}}`}, // - ORTBUserData: {`[{"name":"publisher.com","ext":{"segtax":4},"segment":[{"id":"1"}]}]`}, - ORTBUserExtEIDS: {`[{"source":"bvod.connect","uids":[{"id":"OztamSession-123456","atype":501},{"id":"7D92078A-8246-4BA4-AE5B-76104861E7DC","atype":2,"ext":{"seq":1,"demgid":"1234"}},{"id":"8D92078A-8246-4BA4-AE5B-76104861E7DC","atype":2,"ext":{"seq":2,"demgid":"2345"}}]}]`}, + ORTBUserData: {`[{"name":"publisher.com","ext":{"segtax":4},"segment":[{"id":"1"}]}]`}, + ORTBUserExtEIDS: {`[{"source":"bvod.connect","uids":[{"id":"OztamSession-123456","atype":501},{"id":"7D92078A-8246-4BA4-AE5B-76104861E7DC","atype":2,"ext":{"seq":1,"demgid":"1234"}},{"id":"8D92078A-8246-4BA4-AE5B-76104861E7DC","atype":2,"ext":{"seq":2,"demgid":"2345"}}]}]`}, + ORTBUserExtSessionDuration: {"40"}, + ORTBUserExtImpDepth: {"10"}, ORTBDeviceExtIfaType: {"ORTBDeviceExtIfaType"}, ORTBDeviceExtSessionID: {"ORTBDeviceExtSessionID"}, @@ -734,6 +736,8 @@ func TestParseORTBRequest(t *testing.T) { ], "ext": { "consent": "BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA", + "sessionduration": 40, + "impdepth": 10, "eids": [ { "source": "bvod.connect", @@ -2444,3 +2448,221 @@ func TestORTBDeviceExtIfaType(t *testing.T) { }) } } + +func TestOpenRTB_ORTBUserExtSessionDuration(t *testing.T) { + type fields struct { + values URLValues + } + tests := []struct { + name string + fields fields + user *openrtb2.User + wantResult *openrtb2.User + wantErr error + }{ + { + name: "Nil_User_and_Ext", + fields: fields{ + values: URLValues{ + Values: url.Values{ + ORTBUserExtSessionDuration: []string{"3600"}, + }, + }, + }, + wantResult: &openrtb2.User{ + Ext: json.RawMessage(`{"sessionduration":3600}`), + }, + wantErr: nil, + }, + { + name: "Valid_sessionduration", + fields: fields{ + values: URLValues{ + Values: url.Values{ + ORTBUserExtSessionDuration: []string{"3600"}, + }, + }, + }, + user: &openrtb2.User{}, + wantResult: &openrtb2.User{ + Ext: json.RawMessage(`{"sessionduration":3600}`), + }, + wantErr: nil, + }, + { + name: "Zero_sessionduration", + fields: fields{ + values: URLValues{ + Values: url.Values{ + ORTBUserExtSessionDuration: {"0"}, + }, + }, + }, + user: &openrtb2.User{}, + wantResult: &openrtb2.User{ + Ext: json.RawMessage(`{"sessionduration":0}`), + }, + wantErr: nil, + }, + { + name: "Negative_sessionduration", + fields: fields{ + values: URLValues{ + Values: url.Values{ + ORTBUserExtSessionDuration: {"-10"}, + }, + }, + }, + user: &openrtb2.User{}, + wantResult: &openrtb2.User{Ext: nil}, + wantErr: nil, + }, + { + name: "Empty_sessionduration", + fields: fields{ + values: URLValues{ + Values: url.Values{ + ORTBUserExtSessionDuration: {""}, + }, + }, + }, + user: &openrtb2.User{}, + wantResult: &openrtb2.User{ + Ext: nil, + }, + wantErr: nil, + }, + { + name: "Missing_sessionduration", + fields: fields{ + values: URLValues{ + Values: url.Values{}, + }, + }, + user: &openrtb2.User{}, + wantResult: &openrtb2.User{ + Ext: nil, + }, + wantErr: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + o := &OpenRTB{ + values: tt.fields.values, + ortb: &openrtb2.BidRequest{ID: "request-ID", User: tt.user}, + } + err := o.ORTBUserExtSessionDuration() + assert.Equal(t, tt.wantErr, err) + assert.Equal(t, tt.wantResult, o.ortb.User) + }) + } +} + +func TestOpenRTB_ORTBUserExtImpDepth(t *testing.T) { + type fields struct { + values URLValues + } + tests := []struct { + name string + fields fields + user *openrtb2.User + wantResult *openrtb2.User + wantErr error + }{ + { + name: "Nil_User_and_Ext", + fields: fields{ + values: URLValues{ + Values: url.Values{ + ORTBUserExtImpDepth: []string{"2"}, + }, + }, + }, + wantResult: &openrtb2.User{ + Ext: json.RawMessage(`{"impdepth":2}`), + }, + wantErr: nil, + }, + { + name: "Valid_impdepth", + fields: fields{ + values: URLValues{ + Values: url.Values{ + ORTBUserExtImpDepth: []string{"2"}, + }, + }, + }, + user: &openrtb2.User{}, + wantResult: &openrtb2.User{ + Ext: json.RawMessage(`{"impdepth":2}`), + }, + wantErr: nil, + }, + { + name: "Zero_impdepth", + fields: fields{ + values: URLValues{ + Values: url.Values{ + ORTBUserExtImpDepth: {"0"}, + }, + }, + }, + user: &openrtb2.User{}, + wantResult: &openrtb2.User{ + Ext: json.RawMessage(`{"impdepth":0}`), + }, + wantErr: nil, + }, + { + name: "Negative_impdepth", + fields: fields{ + values: URLValues{ + Values: url.Values{ + ORTBUserExtImpDepth: {"-10"}, + }, + }, + }, + user: &openrtb2.User{}, + wantResult: &openrtb2.User{Ext: nil}, + wantErr: nil, + }, + { + name: "Empty_impdepth", + fields: fields{ + values: URLValues{ + Values: url.Values{ + ORTBUserExtImpDepth: {""}, + }, + }, + }, + user: &openrtb2.User{}, + wantResult: &openrtb2.User{ + Ext: nil, + }, + wantErr: nil, + }, + { + name: "Missing_impdepth", + fields: fields{ + values: URLValues{}, + }, + user: &openrtb2.User{}, + wantResult: &openrtb2.User{ + Ext: nil, + }, + wantErr: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + o := &OpenRTB{ + values: tt.fields.values, + ortb: &openrtb2.BidRequest{ID: "request-ID", User: tt.user}, + } + err := o.ORTBUserExtImpDepth() + assert.Equal(t, tt.wantErr, err) + assert.Equal(t, tt.wantResult, o.ortb.User) + }) + } +} diff --git a/modules/pubmatic/openwrap/endpoints/legacy/ctv/parser_map.go b/modules/pubmatic/openwrap/endpoints/legacy/ctv/parser_map.go index d536c439460..a774822b529 100644 --- a/modules/pubmatic/openwrap/endpoints/legacy/ctv/parser_map.go +++ b/modules/pubmatic/openwrap/endpoints/legacy/ctv/parser_map.go @@ -255,6 +255,10 @@ var ortbMapper = &ParserMap{ ORTBUserExtConsent: Parser.ORTBUserExtConsent, //User.Ext.EIDS ORTBUserExtEIDS: Parser.ORTBUserExtEIDS, + //User.Ext.SessionDuration + ORTBUserExtSessionDuration: Parser.ORTBUserExtSessionDuration, + //User.Ext.ImpDepth + ORTBUserExtImpDepth: Parser.ORTBUserExtImpDepth, //User.Data ORTBUserData: Parser.ORTBUserData, @@ -603,6 +607,13 @@ type Parser interface { //User.Ext.EIDS ORTBUserExtEIDS() error + + //User.Ext.SessionDuration + ORTBUserExtSessionDuration() error + + //User.Ext.ImpDepth + ORTBUserExtImpDepth() error + //User.Data ORTBUserData() error diff --git a/modules/pubmatic/openwrap/endpoints/legacy/ctv/util.go b/modules/pubmatic/openwrap/endpoints/legacy/ctv/util.go index 0e734870b58..b17ff67171c 100644 --- a/modules/pubmatic/openwrap/endpoints/legacy/ctv/util.go +++ b/modules/pubmatic/openwrap/endpoints/legacy/ctv/util.go @@ -1,16 +1,9 @@ package ctv import ( - "encoding/json" "net/url" - "regexp" - - "github.com/prebid/openrtb/v20/openrtb2" - "github.com/prebid/prebid-server/v2/openrtb_ext" ) -var uidRegexp = regexp.MustCompile(`^(UID2|ID5|BGID|euid|PAIRID|IDL|connectid|firstid|utiq):`) - func GetPubIdFromQueryParams(params url.Values) string { pubId := params.Get(ORTBSitePublisherID) if len(pubId) == 0 { @@ -18,54 +11,3 @@ func GetPubIdFromQueryParams(params url.Values) string { } return pubId } - -func ValidateEIDs(eids []openrtb2.EID) []openrtb2.EID { - validEIDs := make([]openrtb2.EID, 0, len(eids)) - for _, eid := range eids { - validUIDs := make([]openrtb2.UID, 0, len(eid.UIDs)) - for _, uid := range eid.UIDs { - uid.ID = uidRegexp.ReplaceAllString(uid.ID, "") - if uid.ID != "" { - validUIDs = append(validUIDs, uid) - } - } - if len(validUIDs) > 0 { - eid.UIDs = validUIDs - validEIDs = append(validEIDs, eid) - } - } - return validEIDs -} - -func UpdateUserEidsWithValidValues(user *openrtb2.User) { - if user == nil { - return - } - - if user.Ext != nil { - var userExt openrtb_ext.ExtUser - err := json.Unmarshal(user.Ext, &userExt) - if err != nil { - return - } - - eids := ValidateEIDs(userExt.Eids) - userExt.Eids = nil - if len(eids) > 0 { - userExt.Eids = eids - } - - userExtjson, err := json.Marshal(userExt) - if err == nil { - user.Ext = userExtjson - } - } - - if len(user.EIDs) > 0 { - eids := ValidateEIDs(user.EIDs) - user.EIDs = nil - if len(eids) > 0 { - user.EIDs = eids - } - } -} diff --git a/modules/pubmatic/openwrap/endpoints/legacy/ctv/util_test.go b/modules/pubmatic/openwrap/endpoints/legacy/ctv/util_test.go deleted file mode 100644 index 177ab5c6372..00000000000 --- a/modules/pubmatic/openwrap/endpoints/legacy/ctv/util_test.go +++ /dev/null @@ -1,127 +0,0 @@ -package ctv - -import ( - "encoding/json" - "testing" - - "github.com/prebid/openrtb/v20/openrtb2" - "github.com/stretchr/testify/assert" -) - -func TestUpdateUserEidsWithValidValues(t *testing.T) { - type args struct { - user *openrtb2.User - } - tests := []struct { - name string - args args - want *openrtb2.User - }{ - { - name: "test_valid_user_eids", - args: args{ - user: &openrtb2.User{ - EIDs: []openrtb2.EID{ - { - Source: "uidapi.com", - UIDs: []openrtb2.UID{ - { - ID: "UID2:testUID", - }, - }, - }, - }, - }, - }, - want: &openrtb2.User{ - EIDs: []openrtb2.EID{ - { - Source: "uidapi.com", - UIDs: []openrtb2.UID{ - { - ID: "testUID", - }, - }, - }, - }, - }, - }, - { - name: "test_user_eids_and_user_ext_eids", - args: args{ - user: &openrtb2.User{ - Ext: json.RawMessage(`{"eids":[{"source":"uidapi.com","uids":[{"id":"UID2:testUID"},{"id":"testUID2"}]},{"source":"euid.eu","uids":[{"id":"testeuid"}]},{"source":"liveramp.com","uids":[{"id":""}]}]}`), - EIDs: []openrtb2.EID{ - { - Source: "uidapi.com", - UIDs: []openrtb2.UID{ - { - ID: "UID2:testUID", - }, - }, - }, - }, - }, - }, - want: &openrtb2.User{ - Ext: json.RawMessage(`{"eids":[{"source":"uidapi.com","uids":[{"id":"testUID"},{"id":"testUID2"}]},{"source":"euid.eu","uids":[{"id":"testeuid"}]}]}`), - EIDs: []openrtb2.EID{ - { - Source: "uidapi.com", - UIDs: []openrtb2.UID{ - { - ID: "testUID", - }, - }, - }, - }, - }, - }, - { - name: "test_user_ext_eids", - args: args{ - user: &openrtb2.User{ - Ext: json.RawMessage(`{"eids":[{"source":"uidapi.com","uids":[{"id":"UID2:testUID"},{"id":"testUID2"}]},{"source":"euid.eu","uids":[{"id":"testeuid"}]},{"source":"liveramp.com","uids":[{"id":""}]}]}`), - }, - }, - want: &openrtb2.User{ - Ext: json.RawMessage(`{"eids":[{"source":"uidapi.com","uids":[{"id":"testUID"},{"id":"testUID2"}]},{"source":"euid.eu","uids":[{"id":"testeuid"}]}]}`), - }, - }, - { - name: "test_user_ext_eids_invalid", - args: args{ - user: &openrtb2.User{ - Ext: json.RawMessage(`{"eids":[{"source":"uidapi.com","uids":[{"id":"UID2:"},{"id":""}]},{"source":"euid.eu","uids":[{"id":"euid:"}]},{"source":"liveramp.com","uids":[{"id":""}]}]}`), - }, - }, - want: &openrtb2.User{ - Ext: json.RawMessage(`{}`), - }, - }, - { - name: "test_valid_user_eids_invalid", - args: args{ - user: &openrtb2.User{ - EIDs: []openrtb2.EID{ - { - Source: "uidapi.com", - UIDs: []openrtb2.UID{ - { - ID: "UID2:", - }, - }, - }, - }, - }, - }, - want: &openrtb2.User{}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - UpdateUserEidsWithValidValues(tt.args.user) - assert.Equal(t, tt.want, tt.args.user) - }) - } -} diff --git a/modules/pubmatic/openwrap/entrypointhook.go b/modules/pubmatic/openwrap/entrypointhook.go index d5b7eb6c2b0..4886b8a0e43 100644 --- a/modules/pubmatic/openwrap/entrypointhook.go +++ b/modules/pubmatic/openwrap/entrypointhook.go @@ -48,7 +48,10 @@ func (m OpenWrap) handleEntrypointHook( var requestExtWrapper models.RequestExtWrapper defer func() { if result.Reject { - m.metricEngine.RecordBadRequests(endpoint, getPubmaticErrorCode(openrtb3.NoBidReason(result.NbrCode))) + if rCtx.PubIDStr == "" { + rCtx.PubIDStr = "0" + } + m.metricEngine.RecordBadRequests(endpoint, rCtx.PubIDStr, getPubmaticErrorCode(openrtb3.NoBidReason(result.NbrCode))) if glog.V(models.LogLevelDebug) { glog.Infof("[bad_request] pubid:[%d] profid:[%d] endpoint:[%s] nbr:[%d] query_params:[%s] body:[%s]", rCtx.PubID, rCtx.ProfileID, rCtx.Endpoint, result.NbrCode, queryParams.Encode(), string(payload.Body)) @@ -71,6 +74,8 @@ func (m OpenWrap) handleEntrypointHook( return result, nil } + originalRequestBody := payload.Body + if endpoint == models.EndpointAppLovinMax { rCtx.MetricsEngine = m.metricEngine // updating body locally to access updated fields from signal @@ -179,7 +184,7 @@ func (m OpenWrap) handleEntrypointHook( rCtx.WakandaDebug.EnableIfRequired(pubIdStr, rCtx.ProfileIDStr) if rCtx.WakandaDebug.IsEnable() { - rCtx.WakandaDebug.SetHTTPRequestData(payload.Request, payload.Body) + rCtx.WakandaDebug.SetHTTPRequestData(payload.Request, originalRequestBody) } result.Reject = false @@ -207,15 +212,7 @@ func GetRequestWrapper(payload hookstage.EntrypointPayload, result hookstage.Hoo case models.EndpointVideo, models.EndpointORTB, models.EndpointVAST, models.EndpointJson: requestExtWrapper, err = models.GetRequestExtWrapper(payload.Body, "ext", "wrapper") case models.EndpointAppLovinMax: - requestExtWrapper, err = models.GetRequestExtWrapper(payload.Body) - if requestExtWrapper.ProfileId == 0 { - profileIDStr := getProfileID(payload.Body) - if profileIDStr != "" { - if ProfileId, newErr := strconv.Atoi(profileIDStr); newErr == nil { - requestExtWrapper.ProfileId = ProfileId - } - } - } + fallthrough case models.EndpointWebS2S: fallthrough default: diff --git a/modules/pubmatic/openwrap/entrypointhook_test.go b/modules/pubmatic/openwrap/entrypointhook_test.go index 429d0fb3e4f..72c47da7916 100644 --- a/modules/pubmatic/openwrap/entrypointhook_test.go +++ b/modules/pubmatic/openwrap/entrypointhook_test.go @@ -23,9 +23,12 @@ func TestOpenWrap_handleEntrypointHook(t *testing.T) { ctrl := gomock.NewController(t) mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) mockFeature := mock_feature.NewMockFeature(ctrl) - wakanda.Init(wakanda.Wakanda{}) + reset := wakanda.TestInstance("111", "222") - defer ctrl.Finish() + defer func() { + ctrl.Finish() + reset() + }() type fields struct { cfg config.Config @@ -223,7 +226,7 @@ func TestOpenWrap_handleEntrypointHook(t *testing.T) { Body: []byte(`{"ext":{"wrapper":{"profileids":5890,"versionid":1}}}`), }, setup: func(mme *mock_metrics.MockMetricsEngine) { - mme.EXPECT().RecordBadRequests(gomock.Any(), 700) + mme.EXPECT().RecordBadRequests(gomock.Any(), "0", 700) }, }, want: hookstage.HookResult[hookstage.EntrypointPayload]{ @@ -380,7 +383,7 @@ func TestOpenWrap_handleEntrypointHook(t *testing.T) { Body: []byte(`{"imp":[{"tagid":"/43743431/DMDemo","ext":{"prebid":{"bidder":{"pubmatic":{"publisherId":"5890"}},"adunitcode":"div-gpt-ad-1460505748561-0"}},"id":"div-gpt-ad-1460505748561-0","banner":{"topframe":1,"format":[{"w":300,"h":250}]}}],"site":{"domain":"localhost:9999","publisher":{"domain":"localhost:9999","id":"5890"},"page":"http://localhost:9999/integrationExamples/gpt/owServer_example.html"},"device":{"w":1792,"h":446,"dnt":0,"ua":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36","language":"en","sua":{"source":1,"platform":{"brand":"macOS"},"browsers":[{"brand":"Google Chrome","version":["117"]},{"brand":"Not;A=Brand","version":["8"]},{"brand":"Chromium","version":["117"]}],"mobile":0}},"ext":{"prebid":{"auctiontimestamp":1697191822565,"targeting":{"includewinners":true,"includebidderkeys":true},"bidderparams":{"pubmatic":{"publisherId":"5890","wrapper":{}}},"channel":{"name":"pbjs","version":"v8.7.0-pre"},"createtids":false}},"id":"5bdd7da5-1166-40fe-a9cb-3bf3c3164cd3","test":0,"tmax":3000}`), }, setup: func(mme *mock_metrics.MockMetricsEngine) { - mme.EXPECT().RecordBadRequests(gomock.Any(), 700) + mme.EXPECT().RecordBadRequests(gomock.Any(), "0", 700) }, }, want: hookstage.HookResult[hookstage.EntrypointPayload]{ @@ -607,6 +610,109 @@ func TestOpenWrap_handleEntrypointHook(t *testing.T) { wantErr: nil, wantBody: []byte(`{"id":"test-case-1","imp":[{"id":"1","banner":{"format":[{"w":728,"h":90},{"w":300,"h":250}],"w":700,"h":900,"api":[5,6,7]},"video":{"mimes":["video/mp4","video/mpeg"],"w":640,"h":480},"displaymanager":"PubMatic_OpenBid_SDK","displaymanagerver":"1.4.0","tagid":"/43743431/DMDemo","bidfloor":0.01,"bidfloorcur":"USD","clickbrowser":0,"secure":0,"exp":14400}],"app":{"id":"1234567","name":"DrawHappyAngel","bundle":"com.newstory.DrawHappyAngel","storeurl":"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098","cat":["IAB9-30"],"ver":"0.5.4","paid":4,"publisher":{"id":"5890","name":"New Story Inc.","ext":{"installed_sdk":{"id":"MOLOCO_BIDDING","sdk_version":{"major":1,"minor":0,"micro":0},"adapter_version":{"major":1,"minor":0,"micro":0}}}},"ext":{"orientation":1}},"device":{"geo":{"lat":40.7429,"lon":-73.9392,"type":2,"ipservice":3,"country":"USA","region":"ny","metro":"501","city":"Queens","zip":"11101","ext":{"org":"Myanmar Broadband Telecom Co.","isp":"Myanmar Broadband Telecom Co."}},"ua":"Mozilla/5.0 (Linux; Android 13; 22101316C Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.230 Mobile Safari/537.36","ip":"38.158.207.171","devicetype":4,"make":"xiaomi","model":"AndroidSDKbuiltforx86","os":"android","osv":"13.0.0","hwv":"ruby","h":2400,"w":1080,"ppi":440,"pxratio":2.75,"js":1,"language":"en_US","carrier":"MYTEL","mccmnc":"310-260","connectiontype":5,"ifa":"497a10d6-c4dd-4e04-a986-c32b7180d462","ext":{"atts":3}},"user":{"data":[{"id":"1234"}],"ext":{"gdpr":0}},"at":1,"tmax":1000,"bcat":["IAB26-4","IAB26-2","IAB25-6","IAB25-5","IAB25-4","IAB25-3","IAB25-1","IAB25-7","IAB8-18","IAB26-3","IAB26-1","IAB8-5","IAB25-2","IAB11-4"],"source":{"ext":{"schain":{"ver":"1.0","complete":1,"nodes":[{"asi":"applovin.com","sid":"53bf468f18c5a0e2b7d4e3f748c677c1","rid":"494dbe15a3ce08c54f4e456363f35a022247f997","hp":1}]},"omidpn":"PubMatic","omidpv":"1.2.11-Pubmatic"}},"regs":{"ext":{"gdpr":0}},"ext":{"prebid":{"bidderparams":{"pubmatic":{"sendburl":true,"wrapper":{"profileid":12929,"versionid":1,"clientconfig":1}}}}}}`), }, + { + name: "valid request for applovin max /openrtb2/auction?source=owsdk&agent=max request with wakanda enable", + fields: fields{ + cfg: config.Config{ + Tracker: config.Tracker{ + Endpoint: "t.pubmatic.com", + VideoErrorTrackerEndpoint: "t.pubmatic.com/error", + }, + }, + cache: nil, + }, + args: args{ + in0: context.Background(), + miCtx: hookstage.ModuleInvocationContext{}, + payload: hookstage.EntrypointPayload{ + Request: func() *http.Request { + r, err := http.NewRequest("POST", "http://localhost/openrtb2/auction?source=owsdk&agent=max&debug=1", nil) + if err != nil { + panic(err) + } + r.Header.Add("User-Agent", "go-test") + r.Header.Add("Source_ip", "127.0.0.1") + r.Header.Add("Cookie", `KADUSERCOOKIE=7D75D25F-FAC9-443D-B2D1-B17FEE11E027; DPSync3=1684886400%3A248%7C1685491200%3A245_226_201; KRTBCOOKIE_80=16514-CAESEMih0bN7ISRdZT8xX8LXzEw&KRTB&22987-CAESEMih0bN7ISRdZT8xX8LXzEw&KRTB&23025-CAESEMih0bN7ISRdZT8xX8LXzEw&KRTB&23386-CAESEMih0bN7ISRdZT8xX8LXzEw; KRTBCOOKIE_377=6810-59dc50c9-d658-44ce-b442-5a1f344d97c0&KRTB&22918-59dc50c9-d658-44ce-b442-5a1f344d97c0&KRTB&23031-59dc50c9-d658-44ce-b442-5a1f344d97c0; uids=eyJ0ZW1wVUlEcyI6eyIzM2Fjcm9zcyI6eyJ1aWQiOiIxMTkxNzkxMDk5Nzc2NjEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTo0My4zODg4Nzk5NVoifSwiYWRmIjp7InVpZCI6IjgwNDQ2MDgzMzM3Nzg4MzkwNzgiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMS4wMzMwNTQ3MjdaIn0sImFka2VybmVsIjp7InVpZCI6IkE5MTYzNTAwNzE0OTkyOTMyOTkwIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuMzczMzg1NjYyWiJ9LCJhZGtlcm5lbEFkbiI6eyJ1aWQiOiJBOTE2MzUwMDcxNDk5MjkzMjk5MCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEzLjQzNDkyNTg5NloifSwiYWRtaXhlciI6eyJ1aWQiOiIzNjZhMTdiMTJmMjI0ZDMwOGYzZTNiOGRhOGMzYzhhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjU5MjkxNDgwMVoifSwiYWRueHMiOnsidWlkIjoiNDE5Mjg5ODUzMDE0NTExOTMiLCJleHBpcmVzIjoiMjAyMy0wMS0xOFQwOTo1MzowOC44MjU0NDI2NzZaIn0sImFqYSI6eyJ1aWQiOiJzMnN1aWQ2RGVmMFl0bjJveGQ1aG9zS1AxVmV3IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTMuMjM5MTc2MDU0WiJ9LCJlcGxhbm5pbmciOnsidWlkIjoiQUoxRjBTOE5qdTdTQ0xWOSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjkyOTk2MDQ3M1oifSwiZ2Ftb3NoaSI6eyJ1aWQiOiJndXNyXzM1NmFmOWIxZDhjNjQyYjQ4MmNiYWQyYjdhMjg4MTYxIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuNTI0MTU3MjI1WiJ9LCJncmlkIjp7InVpZCI6IjRmYzM2MjUwLWQ4NTItNDU5Yy04NzcyLTczNTZkZTE3YWI5NyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE0LjY5NjMxNjIyN1oifSwiZ3JvdXBtIjp7InVpZCI6IjdENzVEMjVGLUZBQzktNDQzRC1CMkQxLUIxN0ZFRTExRTAyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjM5LjIyNjIxMjUzMloifSwiaXgiOnsidWlkIjoiWW9ORlNENlc5QkphOEh6eEdtcXlCUUFBXHUwMDI2Mjk3IiwiZXhwaXJlcyI6IjIwMjMtMDUtMzFUMDc6NTM6MzguNTU1ODI3MzU0WiJ9LCJqaXhpZSI6eyJ1aWQiOiI3MzY3MTI1MC1lODgyLTExZWMtYjUzOC0xM2FjYjdhZjBkZTQiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi4xOTEwOTk3MzJaIn0sImxvZ2ljYWQiOnsidWlkIjoiQVZ4OVROQS11c25pa3M4QURzTHpWa3JvaDg4QUFBR0JUREh0UUEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS40NTUxNDk2MTZaIn0sIm1lZGlhbmV0Ijp7InVpZCI6IjI5Nzg0MjM0OTI4OTU0MTAwMDBWMTAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMy42NzIyMTUxMjhaIn0sIm1naWQiOnsidWlkIjoibTU5Z1hyN0xlX1htIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTcuMDk3MDAxNDcxWiJ9LCJuYW5vaW50ZXJhY3RpdmUiOnsidWlkIjoiNmFlYzhjMTAzNzlkY2I3ODQxMmJjODBiNmRkOWM5NzMxNzNhYjdkNzEyZTQzMWE1YTVlYTcwMzRlNTZhNThhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE2LjcxNDgwNzUwNVoifSwib25ldGFnIjp7InVpZCI6IjdPelZoVzFOeC1LOGFVak1HMG52NXVNYm5YNEFHUXZQbnVHcHFrZ3k0ckEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS4xNDE3NDEyNjJaIn0sIm9wZW54Ijp7InVpZCI6IjVkZWNlNjIyLTBhMjMtMGRhYi0zYTI0LTVhNzcwMTBlNDU4MiIsImV4cGlyZXMiOiIyMDIzLTA1LTMxVDA3OjUyOjQ3LjE0MDQxNzM2M1oifSwicHVibWF0aWMiOnsidWlkIjoiN0Q3NUQyNUYtRkFDOS00NDNELUIyRDEtQjE3RkVFMTFFMDI3IiwiZXhwaXJlcyI6IjIwMjItMTAtMzFUMDk6MTQ6MjUuNzM3MjU2ODk5WiJ9LCJyaWNoYXVkaWVuY2UiOnsidWlkIjoiY2I2YzYzMjAtMzNlMi00Nzc0LWIxNjAtMXp6MTY1NDg0MDc0OSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjUyNTA3NDE4WiJ9LCJzbWFydHlhZHMiOnsidWlkIjoiMTJhZjE1ZTQ0ZjAwZDA3NjMwZTc0YzQ5MTU0Y2JmYmE0Zjg0N2U4ZDRhMTU0YzhjM2Q1MWY1OGNmNzJhNDYyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjgyNTAzMTg4NFoifSwic21pbGV3YW50ZWQiOnsidWlkIjoiZGQ5YzNmZTE4N2VmOWIwOWNhYTViNzExNDA0YzI4MzAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNC4yNTU2MDkzNjNaIn0sInN5bmFjb3JtZWRpYSI6eyJ1aWQiOiJHRFBSIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuOTc5NTgzNDM4WiJ9LCJ0cmlwbGVsaWZ0Ijp7InVpZCI6IjcwMjE5NzUwNTQ4MDg4NjUxOTQ2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA4Ljk4OTY3MzU3NFoifSwidmFsdWVpbXByZXNzaW9uIjp7InVpZCI6IjlkMDgxNTVmLWQ5ZmUtNGI1OC04OThlLWUyYzU2MjgyYWIzZSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjA2NzgzOTE2NFoifSwidmlzeCI6eyJ1aWQiOiIyN2UwYWMzYy1iNDZlLTQxYjMtOTkyYy1mOGQyNzE0OTQ5NWUiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi45ODk1MjM1NzNaIn0sInlpZWxkbGFiIjp7InVpZCI6IjY5NzE0ZDlmLWZiMDAtNGE1Zi04MTljLTRiZTE5MTM2YTMyNSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjExLjMwMzAyNjYxNVoifSwieWllbGRtbyI6eyJ1aWQiOiJnOTZjMmY3MTlmMTU1MWIzMWY2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjExMDUyODYwOVoifSwieWllbGRvbmUiOnsidWlkIjoiMmE0MmZiZDMtMmM3MC00ZWI5LWIxYmQtMDQ2OTY2NTBkOTQ4IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuMzE4MzMzOTM5WiJ9LCJ6ZXJvY2xpY2tmcmF1ZCI6eyJ1aWQiOiJiOTk5NThmZS0yYTg3LTJkYTQtOWNjNC05NjFmZDExM2JlY2UiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNS43MTk1OTQ1NjZaIn19LCJiZGF5IjoiMjAyMi0wNS0xN1QwNjo0ODozOC4wMTc5ODgyMDZaIn0=; KRTBCOOKIE_153=1923-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse&KRTB&19420-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse&KRTB&22979-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse&KRTB&23462-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse; KRTBCOOKIE_57=22776-41928985301451193&KRTB&23339-41928985301451193; KRTBCOOKIE_27=16735-uid:3cab6283-4546-4500-a7b6-40ef605fe745&KRTB&16736-uid:3cab6283-4546-4500-a7b6-40ef605fe745&KRTB&23019-uid:3cab6283-4546-4500-a7b6-40ef605fe745&KRTB&23114-uid:3cab6283-4546-4500-a7b6-40ef605fe745; KRTBCOOKIE_18=22947-1978557989514665832; KRTBCOOKIE_466=16530-4fc36250-d852-459c-8772-7356de17ab97; KRTBCOOKIE_391=22924-8044608333778839078&KRTB&23263-8044608333778839078&KRTB&23481-8044608333778839078; KRTBCOOKIE_1310=23431-b81c3g7dr67i&KRTB&23446-b81c3g7dr67i&KRTB&23465-b81c3g7dr67i; KRTBCOOKIE_1290=23368-vkf3yv9lbbl; KRTBCOOKIE_22=14911-4554572065121110164&KRTB&23150-4554572065121110164; KRTBCOOKIE_860=16335-YGAqDU1zUTdjyAFxCoe3kctlNPo&KRTB&23334-YGAqDU1zUTdjyAFxCoe3kctlNPo&KRTB&23417-YGAqDU1zUTdjyAFxCoe3kctlNPo&KRTB&23426-YGAqDU1zUTdjyAFxCoe3kctlNPo; KRTBCOOKIE_904=16787-KwJwE7NkCZClNJRysN2iYg; KRTBCOOKIE_1159=23138-5545f53f3d6e4ec199d8ed627ff026f3&KRTB&23328-5545f53f3d6e4ec199d8ed627ff026f3&KRTB&23427-5545f53f3d6e4ec199d8ed627ff026f3&KRTB&23445-5545f53f3d6e4ec199d8ed627ff026f3; KRTBCOOKIE_32=11175-AQEI_1QecY2ESAIjEW6KAQEBAQE&KRTB&22713-AQEI_1QecY2ESAIjEW6KAQEBAQE&KRTB&22715-AQEI_1QecY2ESAIjEW6KAQEBAQE; SyncRTB3=1685577600%3A35%7C1685491200%3A107_21_71_56_204_247_165_231_233_179_22_209_54_254_238_96_99_220_7_214_13_3_8_234_176_46_5%7C1684886400%3A2_223_15%7C1689465600%3A69%7C1685145600%3A63; KRTBCOOKIE_107=1471-uid:EK38R0PM1NQR0H5&KRTB&23421-uid:EK38R0PM1NQR0H5; KRTBCOOKIE_594=17105-RX-447a6332-530e-456a-97f4-3f0fd1ed48c9-004&KRTB&17107-RX-447a6332-530e-456a-97f4-3f0fd1ed48c9-004; SPugT=1684310122; chkChromeAb67Sec=133; KRTBCOOKIE_699=22727-AAFy2k7FBosAAEasbJoXnw; PugT=1684310473; origin=go-test`) + return r + }(), + Body: []byte(`{"id":"test-case-1","at":1,"bcat":["IAB26-4","IAB26-2","IAB25-6","IAB25-5","IAB25-4","IAB25-3","IAB25-1","IAB25-7","IAB8-18","IAB26-3","IAB26-1","IAB8-5","IAB25-2","IAB11-4"],"tmax":1000,"app":{"publisher":{"name":"New Story Inc.","id":"111","ext":{"installed_sdk":{"id":"MOLOCO_BIDDING","sdk_version":{"major":1,"minor":0,"micro":0},"adapter_version":{"major":1,"minor":0,"micro":0}}}},"paid":0,"name":"DrawHappyAngel","ver":"0.5.4","bundle":"com.newstory.DrawHappyAngel","cat":["IAB9-30"],"id":"1234567","ext":{"orientation":1}},"device":{"ifa":"497a10d6-c4dd-4e04-a986-c32b7180d462","ip":"38.158.207.171","carrier":"MYTEL","language":"en_US","hwv":"ruby","ppi":440,"pxratio":2.75,"devicetype":4,"connectiontype":2,"js":1,"h":2400,"w":1080,"geo":{"type":2,"ipservice":3,"lat":40.7429,"lon":-73.9392,"long":-73.9392,"city":"Queens","country":"USA","region":"ny","dma":"501","metro":"501","zip":"11101","ext":{"org":"Myanmar Broadband Telecom Co.","isp":"Myanmar Broadband Telecom Co."}},"ext":{},"osv":"13.0.0","ua":"Mozilla/5.0 (Linux; Android 13; 22101316C Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.230 Mobile Safari/537.36","make":"xiaomi","model":"22101316c","os":"android"},"imp":[{"id":"1","displaymanager":"applovin_mediation","displaymanagerver":"11.8.2","instl":0,"secure":0,"tagid":"/43743431/DMDemo","bidfloor":0.01,"bidfloorcur":"USD","exp":14400,"banner":{"format":[{"w":728,"h":90},{"w":300,"h":250}],"w":700,"h":900},"video":{"mimes":["video/mp4","video/mpeg"],"w":640,"h":480},"rwdd":0}],"user":{"data":[{"id":"1","name":"Publisher Passed","segment":[{"signal":"{\"id\":\"95d6643c-3da6-40a2-b9ca-12279393ffbf\",\"at\":1,\"tmax\":500,\"cur\":[\"USD\"],\"imp\":[{\"id\":\"imp176227948\",\"clickbrowser\":0,\"displaymanager\":\"PubMatic_OpenBid_SDK\",\"displaymanagerver\":\"1.4.0\",\"tagid\":\"/43743431/DMDemo\",\"secure\":0,\"banner\":{\"pos\":7,\"format\":[{\"w\":300,\"h\":250}],\"api\":[5,6,7]},\"instl\":1}],\"app\":{\"paid\":4,\"name\":\"OpenWrapperSample\",\"bundle\":\"com.pubmatic.openbid.app\",\"storeurl\":\"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098\",\"ver\":\"1.0\",\"publisher\":{\"id\":\"111\"}},\"device\":{\"geo\":{\"type\":1,\"lat\":37.421998333333335,\"lon\":-122.08400000000002},\"pxratio\":2.625,\"mccmnc\":\"310-260\",\"lmt\":0,\"ifa\":\"07c387f2-e030-428f-8336-42f682150759\",\"connectiontype\":5,\"carrier\":\"Android\",\"js\":1,\"ua\":\"Mozilla/5.0(Linux;Android9;AndroidSDKbuiltforx86Build/PSR1.180720.075;wv)AppleWebKit/537.36(KHTML,likeGecko)Version/4.0Chrome/69.0.3497.100MobileSafari/537.36\",\"make\":\"Google\",\"model\":\"AndroidSDKbuiltforx86\",\"os\":\"Android\",\"osv\":\"9\",\"h\":1794,\"w\":1080,\"language\":\"en-US\",\"devicetype\":4,\"ext\":{\"atts\":3}},\"source\":{\"ext\":{\"omidpn\":\"PubMatic\",\"omidpv\":\"1.2.11-Pubmatic\"}},\"user\":{\"data\":[{\"id\":\"1234\"}]},\"ext\":{\"wrapper\":{\"ssauction\":1,\"sumry_disable\":0,\"profileid\":58135,\"versionid\":1,\"clientconfig\":1}}}"}]}],"ext":{"gdpr":0}},"regs":{"coppa":0,"ext":{"gdpr":0}},"source":{"ext":{"schain":{"ver":"1.0","complete":1,"nodes":[{"asi":"applovin.com","sid":"53bf468f18c5a0e2b7d4e3f748c677c1","rid":"494dbe15a3ce08c54f4e456363f35a022247f997","hp":1}]}}},"ext":{"prebid":{"bidderparams":{"pubmatic":{"sendburl":true,"wrapper":{"profileid":222,"versionid":1}}}}}}`), + }, + setup: func(mme *mock_metrics.MockMetricsEngine) { + mockFeature.EXPECT().GetApplovinMultiFloors(111, "222").Return(models.ApplovinAdUnitFloors{ + "adunit_name": {5.1, 2.1, 4, 4.3}, + }) + mockFeature.EXPECT().IsApplovinMultiFloorsEnabled(111, "222").Return(true) + }, + }, + want: hookstage.HookResult[hookstage.EntrypointPayload]{ + ModuleContext: hookstage.ModuleContext{ + "rctx": models.RequestCtx{ + PubIDStr: "111", + PubID: 111, + ProfileID: 222, + DisplayID: 1, + DisplayVersionID: 1, + SSAuction: -1, + ClientConfigFlag: 1, + Debug: true, + UA: "go-test", + IP: "127.0.0.1", + IsCTVRequest: false, + TrackerEndpoint: "t.pubmatic.com", + VideoErrorTrackerEndpoint: "t.pubmatic.com/error", + UidCookie: &http.Cookie{ + Name: "uids", + Value: `eyJ0ZW1wVUlEcyI6eyIzM2Fjcm9zcyI6eyJ1aWQiOiIxMTkxNzkxMDk5Nzc2NjEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTo0My4zODg4Nzk5NVoifSwiYWRmIjp7InVpZCI6IjgwNDQ2MDgzMzM3Nzg4MzkwNzgiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMS4wMzMwNTQ3MjdaIn0sImFka2VybmVsIjp7InVpZCI6IkE5MTYzNTAwNzE0OTkyOTMyOTkwIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuMzczMzg1NjYyWiJ9LCJhZGtlcm5lbEFkbiI6eyJ1aWQiOiJBOTE2MzUwMDcxNDk5MjkzMjk5MCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEzLjQzNDkyNTg5NloifSwiYWRtaXhlciI6eyJ1aWQiOiIzNjZhMTdiMTJmMjI0ZDMwOGYzZTNiOGRhOGMzYzhhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjU5MjkxNDgwMVoifSwiYWRueHMiOnsidWlkIjoiNDE5Mjg5ODUzMDE0NTExOTMiLCJleHBpcmVzIjoiMjAyMy0wMS0xOFQwOTo1MzowOC44MjU0NDI2NzZaIn0sImFqYSI6eyJ1aWQiOiJzMnN1aWQ2RGVmMFl0bjJveGQ1aG9zS1AxVmV3IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTMuMjM5MTc2MDU0WiJ9LCJlcGxhbm5pbmciOnsidWlkIjoiQUoxRjBTOE5qdTdTQ0xWOSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjkyOTk2MDQ3M1oifSwiZ2Ftb3NoaSI6eyJ1aWQiOiJndXNyXzM1NmFmOWIxZDhjNjQyYjQ4MmNiYWQyYjdhMjg4MTYxIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuNTI0MTU3MjI1WiJ9LCJncmlkIjp7InVpZCI6IjRmYzM2MjUwLWQ4NTItNDU5Yy04NzcyLTczNTZkZTE3YWI5NyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE0LjY5NjMxNjIyN1oifSwiZ3JvdXBtIjp7InVpZCI6IjdENzVEMjVGLUZBQzktNDQzRC1CMkQxLUIxN0ZFRTExRTAyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjM5LjIyNjIxMjUzMloifSwiaXgiOnsidWlkIjoiWW9ORlNENlc5QkphOEh6eEdtcXlCUUFBXHUwMDI2Mjk3IiwiZXhwaXJlcyI6IjIwMjMtMDUtMzFUMDc6NTM6MzguNTU1ODI3MzU0WiJ9LCJqaXhpZSI6eyJ1aWQiOiI3MzY3MTI1MC1lODgyLTExZWMtYjUzOC0xM2FjYjdhZjBkZTQiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi4xOTEwOTk3MzJaIn0sImxvZ2ljYWQiOnsidWlkIjoiQVZ4OVROQS11c25pa3M4QURzTHpWa3JvaDg4QUFBR0JUREh0UUEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS40NTUxNDk2MTZaIn0sIm1lZGlhbmV0Ijp7InVpZCI6IjI5Nzg0MjM0OTI4OTU0MTAwMDBWMTAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMy42NzIyMTUxMjhaIn0sIm1naWQiOnsidWlkIjoibTU5Z1hyN0xlX1htIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTcuMDk3MDAxNDcxWiJ9LCJuYW5vaW50ZXJhY3RpdmUiOnsidWlkIjoiNmFlYzhjMTAzNzlkY2I3ODQxMmJjODBiNmRkOWM5NzMxNzNhYjdkNzEyZTQzMWE1YTVlYTcwMzRlNTZhNThhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE2LjcxNDgwNzUwNVoifSwib25ldGFnIjp7InVpZCI6IjdPelZoVzFOeC1LOGFVak1HMG52NXVNYm5YNEFHUXZQbnVHcHFrZ3k0ckEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS4xNDE3NDEyNjJaIn0sIm9wZW54Ijp7InVpZCI6IjVkZWNlNjIyLTBhMjMtMGRhYi0zYTI0LTVhNzcwMTBlNDU4MiIsImV4cGlyZXMiOiIyMDIzLTA1LTMxVDA3OjUyOjQ3LjE0MDQxNzM2M1oifSwicHVibWF0aWMiOnsidWlkIjoiN0Q3NUQyNUYtRkFDOS00NDNELUIyRDEtQjE3RkVFMTFFMDI3IiwiZXhwaXJlcyI6IjIwMjItMTAtMzFUMDk6MTQ6MjUuNzM3MjU2ODk5WiJ9LCJyaWNoYXVkaWVuY2UiOnsidWlkIjoiY2I2YzYzMjAtMzNlMi00Nzc0LWIxNjAtMXp6MTY1NDg0MDc0OSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjUyNTA3NDE4WiJ9LCJzbWFydHlhZHMiOnsidWlkIjoiMTJhZjE1ZTQ0ZjAwZDA3NjMwZTc0YzQ5MTU0Y2JmYmE0Zjg0N2U4ZDRhMTU0YzhjM2Q1MWY1OGNmNzJhNDYyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjgyNTAzMTg4NFoifSwic21pbGV3YW50ZWQiOnsidWlkIjoiZGQ5YzNmZTE4N2VmOWIwOWNhYTViNzExNDA0YzI4MzAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNC4yNTU2MDkzNjNaIn0sInN5bmFjb3JtZWRpYSI6eyJ1aWQiOiJHRFBSIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuOTc5NTgzNDM4WiJ9LCJ0cmlwbGVsaWZ0Ijp7InVpZCI6IjcwMjE5NzUwNTQ4MDg4NjUxOTQ2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA4Ljk4OTY3MzU3NFoifSwidmFsdWVpbXByZXNzaW9uIjp7InVpZCI6IjlkMDgxNTVmLWQ5ZmUtNGI1OC04OThlLWUyYzU2MjgyYWIzZSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjA2NzgzOTE2NFoifSwidmlzeCI6eyJ1aWQiOiIyN2UwYWMzYy1iNDZlLTQxYjMtOTkyYy1mOGQyNzE0OTQ5NWUiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi45ODk1MjM1NzNaIn0sInlpZWxkbGFiIjp7InVpZCI6IjY5NzE0ZDlmLWZiMDAtNGE1Zi04MTljLTRiZTE5MTM2YTMyNSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjExLjMwMzAyNjYxNVoifSwieWllbGRtbyI6eyJ1aWQiOiJnOTZjMmY3MTlmMTU1MWIzMWY2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjExMDUyODYwOVoifSwieWllbGRvbmUiOnsidWlkIjoiMmE0MmZiZDMtMmM3MC00ZWI5LWIxYmQtMDQ2OTY2NTBkOTQ4IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuMzE4MzMzOTM5WiJ9LCJ6ZXJvY2xpY2tmcmF1ZCI6eyJ1aWQiOiJiOTk5NThmZS0yYTg3LTJkYTQtOWNjNC05NjFmZDExM2JlY2UiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNS43MTk1OTQ1NjZaIn19LCJiZGF5IjoiMjAyMi0wNS0xN1QwNjo0ODozOC4wMTc5ODgyMDZaIn0=`, + }, + KADUSERCookie: &http.Cookie{ + Name: "KADUSERCOOKIE", + Value: `7D75D25F-FAC9-443D-B2D1-B17FEE11E027`, + }, + OriginCookie: "go-test", + Aliases: make(map[string]string), + ImpBidCtx: make(map[string]models.ImpCtx), + PrebidBidderCode: make(map[string]string), + BidderResponseTimeMillis: make(map[string]int), + ProfileIDStr: "222", + Endpoint: models.EndpointAppLovinMax, + MetricsEngine: mockEngine, + SeatNonBids: make(map[string][]openrtb_ext.NonBid), + Method: "POST", + WakandaDebug: &wakanda.Debug{ + Enabled: true, + FolderPaths: []string{"DC1__PUB:111__PROF:222"}, + DebugLevel: 2, + DebugData: wakanda.DebugData{ + HTTPRequest: func() *http.Request { + r, err := http.NewRequest("POST", "http://localhost/openrtb2/auction?source=owsdk&agent=max&debug=1", nil) + if err != nil { + panic(err) + } + r.Header.Add("User-Agent", "go-test") + r.Header.Add("Source_ip", "127.0.0.1") + r.Header.Add("Cookie", `KADUSERCOOKIE=7D75D25F-FAC9-443D-B2D1-B17FEE11E027; DPSync3=1684886400%3A248%7C1685491200%3A245_226_201; KRTBCOOKIE_80=16514-CAESEMih0bN7ISRdZT8xX8LXzEw&KRTB&22987-CAESEMih0bN7ISRdZT8xX8LXzEw&KRTB&23025-CAESEMih0bN7ISRdZT8xX8LXzEw&KRTB&23386-CAESEMih0bN7ISRdZT8xX8LXzEw; KRTBCOOKIE_377=6810-59dc50c9-d658-44ce-b442-5a1f344d97c0&KRTB&22918-59dc50c9-d658-44ce-b442-5a1f344d97c0&KRTB&23031-59dc50c9-d658-44ce-b442-5a1f344d97c0; uids=eyJ0ZW1wVUlEcyI6eyIzM2Fjcm9zcyI6eyJ1aWQiOiIxMTkxNzkxMDk5Nzc2NjEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTo0My4zODg4Nzk5NVoifSwiYWRmIjp7InVpZCI6IjgwNDQ2MDgzMzM3Nzg4MzkwNzgiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMS4wMzMwNTQ3MjdaIn0sImFka2VybmVsIjp7InVpZCI6IkE5MTYzNTAwNzE0OTkyOTMyOTkwIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuMzczMzg1NjYyWiJ9LCJhZGtlcm5lbEFkbiI6eyJ1aWQiOiJBOTE2MzUwMDcxNDk5MjkzMjk5MCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEzLjQzNDkyNTg5NloifSwiYWRtaXhlciI6eyJ1aWQiOiIzNjZhMTdiMTJmMjI0ZDMwOGYzZTNiOGRhOGMzYzhhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjU5MjkxNDgwMVoifSwiYWRueHMiOnsidWlkIjoiNDE5Mjg5ODUzMDE0NTExOTMiLCJleHBpcmVzIjoiMjAyMy0wMS0xOFQwOTo1MzowOC44MjU0NDI2NzZaIn0sImFqYSI6eyJ1aWQiOiJzMnN1aWQ2RGVmMFl0bjJveGQ1aG9zS1AxVmV3IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTMuMjM5MTc2MDU0WiJ9LCJlcGxhbm5pbmciOnsidWlkIjoiQUoxRjBTOE5qdTdTQ0xWOSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjkyOTk2MDQ3M1oifSwiZ2Ftb3NoaSI6eyJ1aWQiOiJndXNyXzM1NmFmOWIxZDhjNjQyYjQ4MmNiYWQyYjdhMjg4MTYxIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuNTI0MTU3MjI1WiJ9LCJncmlkIjp7InVpZCI6IjRmYzM2MjUwLWQ4NTItNDU5Yy04NzcyLTczNTZkZTE3YWI5NyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE0LjY5NjMxNjIyN1oifSwiZ3JvdXBtIjp7InVpZCI6IjdENzVEMjVGLUZBQzktNDQzRC1CMkQxLUIxN0ZFRTExRTAyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjM5LjIyNjIxMjUzMloifSwiaXgiOnsidWlkIjoiWW9ORlNENlc5QkphOEh6eEdtcXlCUUFBXHUwMDI2Mjk3IiwiZXhwaXJlcyI6IjIwMjMtMDUtMzFUMDc6NTM6MzguNTU1ODI3MzU0WiJ9LCJqaXhpZSI6eyJ1aWQiOiI3MzY3MTI1MC1lODgyLTExZWMtYjUzOC0xM2FjYjdhZjBkZTQiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi4xOTEwOTk3MzJaIn0sImxvZ2ljYWQiOnsidWlkIjoiQVZ4OVROQS11c25pa3M4QURzTHpWa3JvaDg4QUFBR0JUREh0UUEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS40NTUxNDk2MTZaIn0sIm1lZGlhbmV0Ijp7InVpZCI6IjI5Nzg0MjM0OTI4OTU0MTAwMDBWMTAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMy42NzIyMTUxMjhaIn0sIm1naWQiOnsidWlkIjoibTU5Z1hyN0xlX1htIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTcuMDk3MDAxNDcxWiJ9LCJuYW5vaW50ZXJhY3RpdmUiOnsidWlkIjoiNmFlYzhjMTAzNzlkY2I3ODQxMmJjODBiNmRkOWM5NzMxNzNhYjdkNzEyZTQzMWE1YTVlYTcwMzRlNTZhNThhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE2LjcxNDgwNzUwNVoifSwib25ldGFnIjp7InVpZCI6IjdPelZoVzFOeC1LOGFVak1HMG52NXVNYm5YNEFHUXZQbnVHcHFrZ3k0ckEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS4xNDE3NDEyNjJaIn0sIm9wZW54Ijp7InVpZCI6IjVkZWNlNjIyLTBhMjMtMGRhYi0zYTI0LTVhNzcwMTBlNDU4MiIsImV4cGlyZXMiOiIyMDIzLTA1LTMxVDA3OjUyOjQ3LjE0MDQxNzM2M1oifSwicHVibWF0aWMiOnsidWlkIjoiN0Q3NUQyNUYtRkFDOS00NDNELUIyRDEtQjE3RkVFMTFFMDI3IiwiZXhwaXJlcyI6IjIwMjItMTAtMzFUMDk6MTQ6MjUuNzM3MjU2ODk5WiJ9LCJyaWNoYXVkaWVuY2UiOnsidWlkIjoiY2I2YzYzMjAtMzNlMi00Nzc0LWIxNjAtMXp6MTY1NDg0MDc0OSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjUyNTA3NDE4WiJ9LCJzbWFydHlhZHMiOnsidWlkIjoiMTJhZjE1ZTQ0ZjAwZDA3NjMwZTc0YzQ5MTU0Y2JmYmE0Zjg0N2U4ZDRhMTU0YzhjM2Q1MWY1OGNmNzJhNDYyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjgyNTAzMTg4NFoifSwic21pbGV3YW50ZWQiOnsidWlkIjoiZGQ5YzNmZTE4N2VmOWIwOWNhYTViNzExNDA0YzI4MzAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNC4yNTU2MDkzNjNaIn0sInN5bmFjb3JtZWRpYSI6eyJ1aWQiOiJHRFBSIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuOTc5NTgzNDM4WiJ9LCJ0cmlwbGVsaWZ0Ijp7InVpZCI6IjcwMjE5NzUwNTQ4MDg4NjUxOTQ2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA4Ljk4OTY3MzU3NFoifSwidmFsdWVpbXByZXNzaW9uIjp7InVpZCI6IjlkMDgxNTVmLWQ5ZmUtNGI1OC04OThlLWUyYzU2MjgyYWIzZSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjA2NzgzOTE2NFoifSwidmlzeCI6eyJ1aWQiOiIyN2UwYWMzYy1iNDZlLTQxYjMtOTkyYy1mOGQyNzE0OTQ5NWUiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi45ODk1MjM1NzNaIn0sInlpZWxkbGFiIjp7InVpZCI6IjY5NzE0ZDlmLWZiMDAtNGE1Zi04MTljLTRiZTE5MTM2YTMyNSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjExLjMwMzAyNjYxNVoifSwieWllbGRtbyI6eyJ1aWQiOiJnOTZjMmY3MTlmMTU1MWIzMWY2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjExMDUyODYwOVoifSwieWllbGRvbmUiOnsidWlkIjoiMmE0MmZiZDMtMmM3MC00ZWI5LWIxYmQtMDQ2OTY2NTBkOTQ4IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuMzE4MzMzOTM5WiJ9LCJ6ZXJvY2xpY2tmcmF1ZCI6eyJ1aWQiOiJiOTk5NThmZS0yYTg3LTJkYTQtOWNjNC05NjFmZDExM2JlY2UiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNS43MTk1OTQ1NjZaIn19LCJiZGF5IjoiMjAyMi0wNS0xN1QwNjo0ODozOC4wMTc5ODgyMDZaIn0=; KRTBCOOKIE_153=1923-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse&KRTB&19420-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse&KRTB&22979-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse&KRTB&23462-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse; KRTBCOOKIE_57=22776-41928985301451193&KRTB&23339-41928985301451193; KRTBCOOKIE_27=16735-uid:3cab6283-4546-4500-a7b6-40ef605fe745&KRTB&16736-uid:3cab6283-4546-4500-a7b6-40ef605fe745&KRTB&23019-uid:3cab6283-4546-4500-a7b6-40ef605fe745&KRTB&23114-uid:3cab6283-4546-4500-a7b6-40ef605fe745; KRTBCOOKIE_18=22947-1978557989514665832; KRTBCOOKIE_466=16530-4fc36250-d852-459c-8772-7356de17ab97; KRTBCOOKIE_391=22924-8044608333778839078&KRTB&23263-8044608333778839078&KRTB&23481-8044608333778839078; KRTBCOOKIE_1310=23431-b81c3g7dr67i&KRTB&23446-b81c3g7dr67i&KRTB&23465-b81c3g7dr67i; KRTBCOOKIE_1290=23368-vkf3yv9lbbl; KRTBCOOKIE_22=14911-4554572065121110164&KRTB&23150-4554572065121110164; KRTBCOOKIE_860=16335-YGAqDU1zUTdjyAFxCoe3kctlNPo&KRTB&23334-YGAqDU1zUTdjyAFxCoe3kctlNPo&KRTB&23417-YGAqDU1zUTdjyAFxCoe3kctlNPo&KRTB&23426-YGAqDU1zUTdjyAFxCoe3kctlNPo; KRTBCOOKIE_904=16787-KwJwE7NkCZClNJRysN2iYg; KRTBCOOKIE_1159=23138-5545f53f3d6e4ec199d8ed627ff026f3&KRTB&23328-5545f53f3d6e4ec199d8ed627ff026f3&KRTB&23427-5545f53f3d6e4ec199d8ed627ff026f3&KRTB&23445-5545f53f3d6e4ec199d8ed627ff026f3; KRTBCOOKIE_32=11175-AQEI_1QecY2ESAIjEW6KAQEBAQE&KRTB&22713-AQEI_1QecY2ESAIjEW6KAQEBAQE&KRTB&22715-AQEI_1QecY2ESAIjEW6KAQEBAQE; SyncRTB3=1685577600%3A35%7C1685491200%3A107_21_71_56_204_247_165_231_233_179_22_209_54_254_238_96_99_220_7_214_13_3_8_234_176_46_5%7C1684886400%3A2_223_15%7C1689465600%3A69%7C1685145600%3A63; KRTBCOOKIE_107=1471-uid:EK38R0PM1NQR0H5&KRTB&23421-uid:EK38R0PM1NQR0H5; KRTBCOOKIE_594=17105-RX-447a6332-530e-456a-97f4-3f0fd1ed48c9-004&KRTB&17107-RX-447a6332-530e-456a-97f4-3f0fd1ed48c9-004; SPugT=1684310122; chkChromeAb67Sec=133; KRTBCOOKIE_699=22727-AAFy2k7FBosAAEasbJoXnw; PugT=1684310473; origin=go-test`) + return r + }(), + HTTPRequestBody: []byte(`{"id":"test-case-1","at":1,"bcat":["IAB26-4","IAB26-2","IAB25-6","IAB25-5","IAB25-4","IAB25-3","IAB25-1","IAB25-7","IAB8-18","IAB26-3","IAB26-1","IAB8-5","IAB25-2","IAB11-4"],"tmax":1000,"app":{"publisher":{"name":"New Story Inc.","id":"111","ext":{"installed_sdk":{"id":"MOLOCO_BIDDING","sdk_version":{"major":1,"minor":0,"micro":0},"adapter_version":{"major":1,"minor":0,"micro":0}}}},"paid":0,"name":"DrawHappyAngel","ver":"0.5.4","bundle":"com.newstory.DrawHappyAngel","cat":["IAB9-30"],"id":"1234567","ext":{"orientation":1}},"device":{"ifa":"497a10d6-c4dd-4e04-a986-c32b7180d462","ip":"38.158.207.171","carrier":"MYTEL","language":"en_US","hwv":"ruby","ppi":440,"pxratio":2.75,"devicetype":4,"connectiontype":2,"js":1,"h":2400,"w":1080,"geo":{"type":2,"ipservice":3,"lat":40.7429,"lon":-73.9392,"long":-73.9392,"city":"Queens","country":"USA","region":"ny","dma":"501","metro":"501","zip":"11101","ext":{"org":"Myanmar Broadband Telecom Co.","isp":"Myanmar Broadband Telecom Co."}},"ext":{},"osv":"13.0.0","ua":"Mozilla/5.0 (Linux; Android 13; 22101316C Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.230 Mobile Safari/537.36","make":"xiaomi","model":"22101316c","os":"android"},"imp":[{"id":"1","displaymanager":"applovin_mediation","displaymanagerver":"11.8.2","instl":0,"secure":0,"tagid":"/43743431/DMDemo","bidfloor":0.01,"bidfloorcur":"USD","exp":14400,"banner":{"format":[{"w":728,"h":90},{"w":300,"h":250}],"w":700,"h":900},"video":{"mimes":["video/mp4","video/mpeg"],"w":640,"h":480},"rwdd":0}],"user":{"data":[{"id":"1","name":"Publisher Passed","segment":[{"signal":"{\"id\":\"95d6643c-3da6-40a2-b9ca-12279393ffbf\",\"at\":1,\"tmax\":500,\"cur\":[\"USD\"],\"imp\":[{\"id\":\"imp176227948\",\"clickbrowser\":0,\"displaymanager\":\"PubMatic_OpenBid_SDK\",\"displaymanagerver\":\"1.4.0\",\"tagid\":\"/43743431/DMDemo\",\"secure\":0,\"banner\":{\"pos\":7,\"format\":[{\"w\":300,\"h\":250}],\"api\":[5,6,7]},\"instl\":1}],\"app\":{\"paid\":4,\"name\":\"OpenWrapperSample\",\"bundle\":\"com.pubmatic.openbid.app\",\"storeurl\":\"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098\",\"ver\":\"1.0\",\"publisher\":{\"id\":\"111\"}},\"device\":{\"geo\":{\"type\":1,\"lat\":37.421998333333335,\"lon\":-122.08400000000002},\"pxratio\":2.625,\"mccmnc\":\"310-260\",\"lmt\":0,\"ifa\":\"07c387f2-e030-428f-8336-42f682150759\",\"connectiontype\":5,\"carrier\":\"Android\",\"js\":1,\"ua\":\"Mozilla/5.0(Linux;Android9;AndroidSDKbuiltforx86Build/PSR1.180720.075;wv)AppleWebKit/537.36(KHTML,likeGecko)Version/4.0Chrome/69.0.3497.100MobileSafari/537.36\",\"make\":\"Google\",\"model\":\"AndroidSDKbuiltforx86\",\"os\":\"Android\",\"osv\":\"9\",\"h\":1794,\"w\":1080,\"language\":\"en-US\",\"devicetype\":4,\"ext\":{\"atts\":3}},\"source\":{\"ext\":{\"omidpn\":\"PubMatic\",\"omidpv\":\"1.2.11-Pubmatic\"}},\"user\":{\"data\":[{\"id\":\"1234\"}]},\"ext\":{\"wrapper\":{\"ssauction\":1,\"sumry_disable\":0,\"profileid\":58135,\"versionid\":1,\"clientconfig\":1}}}"}]}],"ext":{"gdpr":0}},"regs":{"coppa":0,"ext":{"gdpr":0}},"source":{"ext":{"schain":{"ver":"1.0","complete":1,"nodes":[{"asi":"applovin.com","sid":"53bf468f18c5a0e2b7d4e3f748c677c1","rid":"494dbe15a3ce08c54f4e456363f35a022247f997","hp":1}]}}},"ext":{"prebid":{"bidderparams":{"pubmatic":{"sendburl":true,"wrapper":{"profileid":222,"versionid":1}}}}}}`), + }, + }, + AppLovinMax: models.AppLovinMax{ + MultiFloorsConfig: models.MultiFloorsConfig{ + Enabled: true, + Config: models.ApplovinAdUnitFloors{ + "adunit_name": {5.1, 2.1, 4, 4.3}, + }, + }, + }, + SendBurl: true, + ImpCountingMethodEnabledBidders: make(map[string]struct{}), + }, + }, + }, + doMutate: true, + wantErr: nil, + wantBody: []byte(`{"id":"test-case-1","imp":[{"id":"1","banner":{"format":[{"w":728,"h":90},{"w":300,"h":250}],"w":700,"h":900,"api":[5,6,7]},"video":{"mimes":["video/mp4","video/mpeg"],"w":640,"h":480},"displaymanager":"PubMatic_OpenBid_SDK","displaymanagerver":"1.4.0","tagid":"/43743431/DMDemo","bidfloor":0.01,"bidfloorcur":"USD","clickbrowser":0,"secure":0,"exp":14400}],"app":{"id":"1234567","name":"DrawHappyAngel","bundle":"com.newstory.DrawHappyAngel","storeurl":"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098","cat":["IAB9-30"],"ver":"0.5.4","paid":4,"publisher":{"id":"111","name":"New Story Inc.","ext":{"installed_sdk":{"id":"MOLOCO_BIDDING","sdk_version":{"major":1,"minor":0,"micro":0},"adapter_version":{"major":1,"minor":0,"micro":0}}}},"ext":{"orientation":1}},"device":{"geo":{"lat":40.7429,"lon":-73.9392,"type":2,"ipservice":3,"country":"USA","region":"ny","metro":"501","city":"Queens","zip":"11101","ext":{"org":"Myanmar Broadband Telecom Co.","isp":"Myanmar Broadband Telecom Co."}},"ua":"Mozilla/5.0 (Linux; Android 13; 22101316C Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.230 Mobile Safari/537.36","ip":"38.158.207.171","devicetype":4,"make":"xiaomi","model":"AndroidSDKbuiltforx86","os":"android","osv":"13.0.0","hwv":"ruby","h":2400,"w":1080,"ppi":440,"pxratio":2.75,"js":1,"language":"en_US","carrier":"MYTEL","mccmnc":"310-260","connectiontype":5,"ifa":"497a10d6-c4dd-4e04-a986-c32b7180d462","ext":{"atts":3}},"user":{"data":[{"id":"1234"}],"ext":{"gdpr":0}},"at":1,"tmax":1000,"bcat":["IAB26-4","IAB26-2","IAB25-6","IAB25-5","IAB25-4","IAB25-3","IAB25-1","IAB25-7","IAB8-18","IAB26-3","IAB26-1","IAB8-5","IAB25-2","IAB11-4"],"source":{"ext":{"schain":{"ver":"1.0","complete":1,"nodes":[{"asi":"applovin.com","sid":"53bf468f18c5a0e2b7d4e3f748c677c1","rid":"494dbe15a3ce08c54f4e456363f35a022247f997","hp":1}]},"omidpn":"PubMatic","omidpv":"1.2.11-Pubmatic"}},"regs":{"ext":{"gdpr":0}},"ext":{"prebid":{"bidderparams":{"pubmatic":{"sendburl":true,"wrapper":{"profileid":222,"versionid":1,"clientconfig":1}}}}}}`), + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -701,7 +807,7 @@ func TestGetRequestWrapper(t *testing.T) { name: "EndpointAppLovinMax", args: args{ payload: hookstage.EntrypointPayload{ - Body: []byte(`{"app":{"bundle":"com.pubmatic.openbid.app","name":"Sample","publisher":{"id":"156276"},"id":"13137","storeurl":"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098","ver":"1.0"},"at":1,"device":{"carrier":"MYTEL","connectiontype":2,"devicetype":4,"ext":{"atts":2},"geo":{"city":"Queens","country":"USA","dma":"501","ipservice":3,"lat":40.7429,"lon":-73.9392,"long":-73.9392,"metro":"501","region":"ny","type":2,"zip":"11101"},"h":2400,"hwv":"ruby","ifa":"497a10d6-c4dd-4e04-a986-c32b7180d462","ip":"38.158.207.171","js":1,"language":"en_US","make":"xiaomi","model":"22101316c","os":"android","osv":"13.0.0","ppi":440,"pxratio":2.75,"ua":"Mozilla/5.0 (Linux; Android 13; 22101316C Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.230 Mobile Safari/537.36","w":1080},"ext":{"prebid":{"bidderparams":{"pubmatic":{"wrapper":{}}}}},"id":"95d6643c-3da6-40a2-b9ca-12279393ffbf","imp":[{"banner":{"format":[{"w":728,"h":90},{"w":320,"h":50}],"api":[5,2],"w":700,"h":900},"clickbrowser":0,"displaymanager":"OpenBid_SDK","displaymanagerver":"1.4.0","ext":{"reward":1},"id":"imp176227948","secure":0,"tagid":"OpenWrapBidderBannerAdUnit"}],"regs":{"ext":{"gdpr":0}},"source":{"ext":{"omidpn":"PubMatic","omidpv":"1.2.11-Pubmatic"}},"user":{"data":[{"id":"1","name":"Publisher Passed","segment":[{"signal":"{\"id\":\"95d6643c-3da6-40a2-b9ca-12279393ffbf\",\"at\":1,\"tmax\":500,\"cur\":[\"USD\"],\"imp\":[{\"id\":\"imp176227948\",\"clickbrowser\":0,\"displaymanager\":\"PubMatic_OpenBid_SDK\",\"displaymanagerver\":\"1.4.0\",\"tagid\":\"/43743431/DMDemo\",\"secure\":0,\"banner\":{\"pos\":7,\"format\":[{\"w\":300,\"h\":250}],\"api\":[5,6,7]},\"instl\":1}],\"app\":{\"name\":\"OpenWrapperSample\",\"bundle\":\"com.pubmatic.openbid.app\",\"storeurl\":\"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098?appnexus_banner_fixedbid=1&fixedbid=1\",\"ver\":\"1.0\",\"publisher\":{\"id\":\"5890\"}},\"device\":{\"ext\":{\"atts\":0},\"geo\":{\"type\":1,\"lat\":37.421998333333335,\"lon\":-122.08400000000002},\"pxratio\":2.625,\"mccmnc\":\"310-260\",\"lmt\":0,\"ifa\":\"07c387f2-e030-428f-8336-42f682150759\",\"connectiontype\":6,\"carrier\":\"Android\",\"js\":1,\"ua\":\"Mozilla/5.0(Linux;Android9;AndroidSDKbuiltforx86Build/PSR1.180720.075;wv)AppleWebKit/537.36(KHTML,likeGecko)Version/4.0Chrome/69.0.3497.100MobileSafari/537.36\",\"make\":\"Google\",\"model\":\"AndroidSDKbuiltforx86\",\"os\":\"Android\",\"osv\":\"9\",\"h\":1794,\"w\":1080,\"language\":\"en-US\",\"devicetype\":4},\"source\":{\"ext\":{\"omidpn\":\"PubMatic\",\"omidpv\":\"1.2.11-Pubmatic\"}},\"user\":{},\"ext\":{\"wrapper\":{\"ssauction\":0,\"sumry_disable\":0,\"profileid\":58135,\"versionid\":1,\"clientconfig\":1}}}"}]}],"ext":{"consent":"CP2KIMAP2KIgAEPgABBYJGNX_H__bX9j-Xr3"}}}`), + Body: []byte(`{"app":{"bundle":"com.pubmatic.openbid.app","name":"Sample","publisher":{"id":"156276"},"id":"13137","storeurl":"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098","ver":"1.0"},"at":1,"device":{"carrier":"MYTEL","connectiontype":2,"devicetype":4,"ext":{"atts":2},"geo":{"city":"Queens","country":"USA","dma":"501","ipservice":3,"lat":40.7429,"lon":-73.9392,"long":-73.9392,"metro":"501","region":"ny","type":2,"zip":"11101"},"h":2400,"hwv":"ruby","ifa":"497a10d6-c4dd-4e04-a986-c32b7180d462","ip":"38.158.207.171","js":1,"language":"en_US","make":"xiaomi","model":"22101316c","os":"android","osv":"13.0.0","ppi":440,"pxratio":2.75,"ua":"Mozilla/5.0 (Linux; Android 13; 22101316C Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.230 Mobile Safari/537.36","w":1080},"ext":{"prebid":{"bidderparams":{"pubmatic":{"wrapper":{"profileid":13137}}}}},"id":"95d6643c-3da6-40a2-b9ca-12279393ffbf","imp":[{"banner":{"format":[{"w":728,"h":90},{"w":320,"h":50}],"api":[5,2],"w":700,"h":900},"clickbrowser":0,"displaymanager":"OpenBid_SDK","displaymanagerver":"1.4.0","ext":{"reward":1},"id":"imp176227948","secure":0,"tagid":"OpenWrapBidderBannerAdUnit"}],"regs":{"ext":{"gdpr":0}},"source":{"ext":{"omidpn":"PubMatic","omidpv":"1.2.11-Pubmatic"}},"user":{"data":[{"id":"1","name":"Publisher Passed","segment":[{"signal":"{\"id\":\"95d6643c-3da6-40a2-b9ca-12279393ffbf\",\"at\":1,\"tmax\":500,\"cur\":[\"USD\"],\"imp\":[{\"id\":\"imp176227948\",\"clickbrowser\":0,\"displaymanager\":\"PubMatic_OpenBid_SDK\",\"displaymanagerver\":\"1.4.0\",\"tagid\":\"/43743431/DMDemo\",\"secure\":0,\"banner\":{\"pos\":7,\"format\":[{\"w\":300,\"h\":250}],\"api\":[5,6,7]},\"instl\":1}],\"app\":{\"name\":\"OpenWrapperSample\",\"bundle\":\"com.pubmatic.openbid.app\",\"storeurl\":\"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098?appnexus_banner_fixedbid=1&fixedbid=1\",\"ver\":\"1.0\",\"publisher\":{\"id\":\"5890\"}},\"device\":{\"ext\":{\"atts\":0},\"geo\":{\"type\":1,\"lat\":37.421998333333335,\"lon\":-122.08400000000002},\"pxratio\":2.625,\"mccmnc\":\"310-260\",\"lmt\":0,\"ifa\":\"07c387f2-e030-428f-8336-42f682150759\",\"connectiontype\":6,\"carrier\":\"Android\",\"js\":1,\"ua\":\"Mozilla/5.0(Linux;Android9;AndroidSDKbuiltforx86Build/PSR1.180720.075;wv)AppleWebKit/537.36(KHTML,likeGecko)Version/4.0Chrome/69.0.3497.100MobileSafari/537.36\",\"make\":\"Google\",\"model\":\"AndroidSDKbuiltforx86\",\"os\":\"Android\",\"osv\":\"9\",\"h\":1794,\"w\":1080,\"language\":\"en-US\",\"devicetype\":4},\"source\":{\"ext\":{\"omidpn\":\"PubMatic\",\"omidpv\":\"1.2.11-Pubmatic\"}},\"user\":{},\"ext\":{\"wrapper\":{\"ssauction\":0,\"sumry_disable\":0,\"profileid\":58135,\"versionid\":1,\"clientconfig\":1}}}"}]}],"ext":{"consent":"CP2KIMAP2KIgAEPgABBYJGNX_H__bX9j-Xr3"}}}`), }, result: hookstage.HookResult[hookstage.EntrypointPayload]{}, endpoint: models.EndpointAppLovinMax, diff --git a/modules/pubmatic/openwrap/hook_raw_bidder_response.go b/modules/pubmatic/openwrap/hook_raw_bidder_response.go index 8bdc4f06b00..a97f80a5af4 100644 --- a/modules/pubmatic/openwrap/hook_raw_bidder_response.go +++ b/modules/pubmatic/openwrap/hook_raw_bidder_response.go @@ -2,14 +2,20 @@ package openwrap import ( "fmt" - "sync" "github.com/prebid/prebid-server/v2/adapters" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/nbr" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/prebid/prebid-server/v2/hooks/hookstage" ) +type BidUnwrapInfo struct { + bid *adapters.TypedBid + unwrapStatus string +} + func (m OpenWrap) handleRawBidderResponseHook( miCtx hookstage.ModuleInvocationContext, payload hookstage.RawBidderResponsePayload, @@ -20,41 +26,63 @@ func (m OpenWrap) handleRawBidderResponseHook( return result, nil } - if vastRequestContext.VastUnwrapEnabled { - // Do Unwrap and Update Adm - wg := new(sync.WaitGroup) - for _, bid := range payload.Bids { - if string(bid.BidType) == models.MediaTypeVideo { - wg.Add(1) - go func(bid *adapters.TypedBid) { - defer wg.Done() - m.unwrap.Unwrap(miCtx.AccountID, payload.Bidder, bid, vastRequestContext.UA, vastRequestContext.IP, vastRequestContext.VastUnwrapStatsEnabled) - }(bid) - } - } - wg.Wait() - changeSet := hookstage.ChangeSet[hookstage.RawBidderResponsePayload]{} - changeSet.RawBidderResponse().Bids().Update(payload.Bids) - result.ChangeSet = changeSet - } else { - vastRequestContext.VastUnwrapStatsEnabled = GetRandomNumberIn1To100() <= m.cfg.Features.VASTUnwrapStatsPercent - if vastRequestContext.VastUnwrapStatsEnabled { - // Do Unwrap and Collect stats only - for _, bid := range payload.Bids { - if string(bid.BidType) == models.MediaTypeVideo { - go func(bid *adapters.TypedBid) { - m.unwrap.Unwrap(miCtx.AccountID, payload.Bidder, bid, vastRequestContext.UA, vastRequestContext.IP, vastRequestContext.VastUnwrapStatsEnabled) - }(bid) - } - } + if !vastRequestContext.VastUnwrapEnabled { + return result, nil + } + + seatNonBid := openrtb_ext.NonBidCollection{} + unwrappedBids := make([]*adapters.TypedBid, 0, len(payload.BidderResponse.Bids)) + unwrappedBidsChan := make(chan BidUnwrapInfo, len(payload.BidderResponse.Bids)) + defer close(unwrappedBidsChan) + + unwrappedBidsCnt, unwrappedSuccessBidCnt := 0, 0 + totalBidCnt := len(payload.BidderResponse.Bids) + // send bids for unwrap + for _, bid := range payload.BidderResponse.Bids { + if !isEligibleForUnwrap(bid) { + unwrappedBids = append(unwrappedBids, bid) + continue } + unwrappedBidsCnt++ + go func(bid adapters.TypedBid) { + unwrapStatus := m.unwrap.Unwrap(&bid, miCtx.AccountID, payload.Bidder, vastRequestContext.UA, vastRequestContext.IP) + unwrappedBidsChan <- BidUnwrapInfo{&bid, unwrapStatus} + }(*bid) } - if vastRequestContext.VastUnwrapEnabled || vastRequestContext.VastUnwrapStatsEnabled { - result.DebugMessages = append(result.DebugMessages, - fmt.Sprintf("For pubid:[%d] VastUnwrapEnabled: [%v] VastUnwrapStatsEnabled:[%v] ", - vastRequestContext.PubID, vastRequestContext.VastUnwrapEnabled, vastRequestContext.VastUnwrapStatsEnabled)) + // collect bids after unwrap + for i := 0; i < unwrappedBidsCnt; i++ { + unwrappedBid := <-unwrappedBidsChan + if !rejectBid(unwrappedBid.unwrapStatus) { + unwrappedSuccessBidCnt++ + unwrappedBids = append(unwrappedBids, unwrappedBid.bid) + continue + } + seatNonBid.AddBid(openrtb_ext.NewNonBid(openrtb_ext.NonBidParams{ + Bid: unwrappedBid.bid.Bid, + NonBidReason: int(nbr.LossBidLostInVastUnwrap), + DealPriority: unwrappedBid.bid.DealPriority, + BidMeta: unwrappedBid.bid.BidMeta, + BidType: unwrappedBid.bid.BidType, + BidVideo: unwrappedBid.bid.BidVideo, + OriginalBidCur: payload.BidderResponse.Currency, + }), payload.Bidder, + ) } + changeSet := hookstage.ChangeSet[hookstage.RawBidderResponsePayload]{} + changeSet.RawBidderResponse().Bids().Update(unwrappedBids) + result.ChangeSet = changeSet + result.SeatNonBid = seatNonBid + result.DebugMessages = append(result.DebugMessages, + fmt.Sprintf("For pubid:[%d] VastUnwrapEnabled: [%v] Total Input Bids: [%d] Total Bids sent for unwrapping: [%d] Total Unwrap Success: [%d]", vastRequestContext.PubID, vastRequestContext.VastUnwrapEnabled, totalBidCnt, unwrappedBidsCnt, unwrappedSuccessBidCnt)) return result, nil } + +func isEligibleForUnwrap(bid *adapters.TypedBid) bool { + return bid != nil && bid.BidType == openrtb_ext.BidTypeVideo && bid.Bid != nil && bid.Bid.AdM != "" +} + +func rejectBid(bidUnwrapStatus string) bool { + return bidUnwrapStatus == models.UnwrapEmptyVASTStatus || bidUnwrapStatus == models.UnwrapInvalidVASTStatus +} diff --git a/modules/pubmatic/openwrap/hook_raw_bidder_response_test.go b/modules/pubmatic/openwrap/hook_raw_bidder_response_test.go index 07130c3aed3..421dd4f6285 100644 --- a/modules/pubmatic/openwrap/hook_raw_bidder_response_test.go +++ b/modules/pubmatic/openwrap/hook_raw_bidder_response_test.go @@ -3,7 +3,6 @@ package openwrap import ( "fmt" "net/http" - "testing" unWrapCfg "git.pubmatic.com/vastunwrap/config" @@ -14,7 +13,9 @@ import ( "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/config" mock_metrics "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics/mock" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/nbr" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/unwrap" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -23,7 +24,6 @@ var invalidVastXMLAdM = "PubMaticAcudeo CompatibleVAST 2.0 Instream Test 1VAST 2.0 Instream Test 1https://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&er=[ERRORCODE]https://track.dsptracker.com?p=1234&er=[ERRORCODE]https://aktrack.pubmatic.com/AdServer/AdDisplayTrackerServlet?operId=1&pubId=64195&siteId=47105&adId=1405154&adType=13&adServerId=243&kefact=1.000000&kaxefact=1.000000&kadNetFrequecy=0&kadwidth=0&kadheight=0&kadsizeid=97&kltstamp=1536933242&indirectAdId=0&adServerOptimizerId=2&ranreq=0.05969169352174375&kpbmtpfact=11.000000&dcId=1&tldId=0&passback=0&svr=ktk57&ekefact=er2bW2sDAwCra06ACbsIQySn5nqBtYsTl8fy5lupAexh37D_&ekaxefact=er2bW4EDAwB_LQpJJ23Fq0DcNC-NSAFXdpSQC8XBk_S33_Fa&ekpbmtpfact=er2bW5MDAwDJHdBnLBt5IrRuh7x0oqp_tjIALv_VvSQDAl6R&crID=m:1_x:3_y:3_p:11_va:3&lpu=ae.com&ucrid=678722001014421372&campaignId=16774&creativeId=0&pctr=0.000000&wDSPByrId=511&wDspId=27&wbId=0&wrId=0&wAdvID=3170&isRTB=1&rtbId=EBCA079F-8D7C-45B8-B733-92951F670AA1&imprId=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&oid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&pageURL=http%253A%252F%252Fowsdk-stagingams.pubmatic.com%253A8443%252Fvast-validator%252F%2523&sec=1&pmc=1https://DspImpressionTracker.com/https://mytracking.com/linear/closehttps://mytracking.com/linear/skiphttps://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&e=1https://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&e=2https://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&e=3https://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&e=4https://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&e=5https://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&e=600:00:04https://www.automationtester.inhttps://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&e=99https://stagingams.pubmatic.com:8443/openwrap/media/pubmatic.mp4https://stagingams.pubmatic.com:8443/openwrap/media/pubmatic.mp4https://stagingams.pubmatic.com:8443/openwrap/media/mp4-sample-3.mp4" func TestHandleRawBidderResponseHook(t *testing.T) { - ctrl := gomock.NewController(t) defer ctrl.Finish() mockMetricsEngine := mock_metrics.NewMockMetricsEngine(ctrl) @@ -33,27 +33,25 @@ func TestHandleRawBidderResponseHook(t *testing.T) { payload hookstage.RawBidderResponsePayload moduleInvocationCtx hookstage.ModuleInvocationContext isAdmUpdated bool - randomNumber int } tests := []struct { - name string - args args - wantResult hookstage.HookResult[hookstage.RawBidderResponsePayload] - setup func() - wantErr bool - mockHandler http.HandlerFunc + name string + args args + wantResult hookstage.HookResult[hookstage.RawBidderResponsePayload] + setup func() + wantSeatNonBid openrtb_ext.NonBidCollection + mockHandler http.HandlerFunc + wantBids []*adapters.TypedBid }{ - { - name: "Empty Request Context", + name: "Empty_Request_Context", args: args{ module: OpenWrap{}, }, wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{DebugMessages: []string{"error: request-ctx not found in handleRawBidderResponseHook()"}}, - wantErr: false, }, { - name: "Set Vast Unwrapper to false in request context with type video", + name: "VASTUnwrap_Disabled_Video_Bids", args: args{ module: OpenWrap{ cfg: config.Config{VastUnwrapCfg: unWrapCfg.VastUnWrapCfg{ @@ -64,32 +62,31 @@ func TestHandleRawBidderResponseHook(t *testing.T) { metricEngine: mockMetricsEngine, }, payload: hookstage.RawBidderResponsePayload{ - Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ - ID: "Bid-123", - ImpID: fmt.Sprintf("div-adunit-%d", 123), - Price: 2.1, - AdM: "
This is an Ad
", - CrID: "Cr-234", - W: 100, - H: 50, - }, - BidType: "video", - }}}, + BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: "
This is an Ad
", + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }}}}, moduleInvocationCtx: hookstage.ModuleInvocationContext{ModuleContext: hookstage.ModuleContext{models.RequestContext: models.RequestCtx{VastUnwrapEnabled: false}}}, - randomNumber: 1, }, wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, - wantErr: false, }, { - name: "Set Vast Unwrapper to false in request context with type video, stats enabled true", + name: "VASTUnwrap_Enabled_Single_Video_Bid_Invalid_Vast_xml", args: args{ module: OpenWrap{ cfg: config.Config{ Features: config.FeatureToggle{ - VASTUnwrapStatsPercent: 2, + VASTUnwrapPercent: 50, }, VastUnwrapCfg: unWrapCfg.VastUnWrapCfg{ MaxWrapperSupport: 5, @@ -99,30 +96,93 @@ func TestHandleRawBidderResponseHook(t *testing.T) { metricEngine: mockMetricsEngine, }, payload: hookstage.RawBidderResponsePayload{ - Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ - ID: "Bid-123", - ImpID: fmt.Sprintf("div-adunit-%d", 123), - Price: 2.1, - AdM: vastXMLAdM, - CrID: "Cr-234", - W: 100, - H: 50, + BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: invalidVastXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", }, - BidType: "video", - }}, + }, + }, Bidder: "pubmatic", }, - moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{models.RequestContext: models.RequestCtx{VastUnwrapEnabled: false}}}, - randomNumber: 1, + moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{models.RequestContext: models.RequestCtx{VastUnwrapEnabled: true}}}, + }, + mockHandler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Add("unwrap-status", "1") + w.WriteHeader(http.StatusNoContent) + }), + wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, + setup: func() { + mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", "1") + mockMetricsEngine.EXPECT().RecordUnwrapRequestTime("5890", "pubmatic", gomock.Any()) + }, + wantBids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: invalidVastXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }, }, + }, + { + name: "VASTUnwrap_Enabled_Single_Video_Bid", mockHandler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Header().Add("unwrap-status", "0") w.Header().Add("unwrap-count", "1") w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(vastXMLAdM)) + _, _ = w.Write([]byte(inlineXMLAdM)) }), + args: args{ + module: OpenWrap{ + cfg: config.Config{ + Features: config.FeatureToggle{ + VASTUnwrapPercent: 50, + }, + VastUnwrapCfg: unWrapCfg.VastUnWrapCfg{ + MaxWrapperSupport: 5, + StatConfig: unWrapCfg.StatConfig{Endpoint: "http://10.172.141.13:8080", PublishInterval: 1}, + APPConfig: unWrapCfg.AppConfig{UnwrapDefaultTimeout: 1500}, + }}, + metricEngine: mockMetricsEngine, + }, + payload: hookstage.RawBidderResponsePayload{ + BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: vastXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }, + }, + }, + Bidder: "pubmatic", + }, + moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{models.RequestContext: models.RequestCtx{VastUnwrapEnabled: true}}}, + isAdmUpdated: true, + }, wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, setup: func() { mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", "0") @@ -130,16 +190,28 @@ func TestHandleRawBidderResponseHook(t *testing.T) { mockMetricsEngine.EXPECT().RecordUnwrapRequestTime("5890", "pubmatic", gomock.Any()) mockMetricsEngine.EXPECT().RecordUnwrapRespTime("5890", "1", gomock.Any()) }, - wantErr: false, + wantBids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: inlineXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }, + }, }, { - name: "Set Vast Unwrapper to true in request context with invalid vast xml", + name: "VASTUnwrap_Enabled_Multiple_Video_Bids", args: args{ module: OpenWrap{ cfg: config.Config{ Features: config.FeatureToggle{ - VASTUnwrapStatsPercent: 2, - VASTUnwrapPercent: 50, + VASTUnwrapPercent: 100, }, VastUnwrapCfg: unWrapCfg.VastUnWrapCfg{ MaxWrapperSupport: 5, @@ -149,50 +221,173 @@ func TestHandleRawBidderResponseHook(t *testing.T) { metricEngine: mockMetricsEngine, }, payload: hookstage.RawBidderResponsePayload{ - Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ - ID: "Bid-123", - ImpID: fmt.Sprintf("div-adunit-%d", 123), - Price: 2.1, - AdM: invalidVastXMLAdM, - CrID: "Cr-234", - W: 100, - H: 50, + BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: vastXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", }, - BidType: "video", - }, + { + Bid: &openrtb2.Bid{ + ID: "Bid-456", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: vastXMLAdM, + CrID: "Cr-789", + W: 100, + H: 50, + }, + BidType: "video", + }}, }, Bidder: "pubmatic", }, moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{models.RequestContext: models.RequestCtx{VastUnwrapEnabled: true}}}, + isAdmUpdated: true, }, mockHandler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Add("unwrap-status", "1") - w.WriteHeader(http.StatusNoContent) + w.Header().Add("unwrap-status", "0") + w.Header().Add("unwrap-count", "1") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(inlineXMLAdM)) }), wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, setup: func() { - mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", "1") - mockMetricsEngine.EXPECT().RecordUnwrapRequestTime("5890", "pubmatic", gomock.Any()) + mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", "0").Times(2) + mockMetricsEngine.EXPECT().RecordUnwrapWrapperCount("5890", "pubmatic", "1").Times(2) + mockMetricsEngine.EXPECT().RecordUnwrapRequestTime("5890", "pubmatic", gomock.Any()).Times(2) + mockMetricsEngine.EXPECT().RecordUnwrapRespTime("5890", "1", gomock.Any()).Times(2) + }, + wantBids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-456", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: inlineXMLAdM, + CrID: "Cr-789", + W: 100, + H: 50, + }, + BidType: "video", + }, + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: inlineXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }, }, - wantErr: true, }, - { - name: "Set Vast Unwrapper to true in request context with type video", + name: "VASTUnwrap_Enabled_Video_and_Banner_Bids", + args: args{ + module: OpenWrap{ + cfg: config.Config{ + Features: config.FeatureToggle{ + VASTUnwrapPercent: 50, + }, + VastUnwrapCfg: unWrapCfg.VastUnWrapCfg{ + MaxWrapperSupport: 5, + StatConfig: unWrapCfg.StatConfig{Endpoint: "http://10.172.141.13:8080", PublishInterval: 1}, + APPConfig: unWrapCfg.AppConfig{UnwrapDefaultTimeout: 1500}, + }}, + metricEngine: mockMetricsEngine, + }, + payload: hookstage.RawBidderResponsePayload{ + BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: vastXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }, + { + Bid: &openrtb2.Bid{ + ID: "Bid-456", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: "This is banner creative", + CrID: "Cr-789", + W: 100, + H: 50, + }, + BidType: "banner", + }}, + }, + Bidder: "pubmatic", + }, + moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{models.RequestContext: models.RequestCtx{VastUnwrapEnabled: true}}}, + isAdmUpdated: true, + }, mockHandler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Header().Add("unwrap-status", "0") - w.Header().Add("unwrap-count", "1") + w.Header().Add("unwrap-count", "0") w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(inlineXMLAdM)) }), + wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, + setup: func() { + mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", "0") + mockMetricsEngine.EXPECT().RecordUnwrapWrapperCount("5890", "pubmatic", "0") + mockMetricsEngine.EXPECT().RecordUnwrapRequestTime("5890", "pubmatic", gomock.Any()) + mockMetricsEngine.EXPECT().RecordUnwrapRespTime("5890", "0", gomock.Any()) + }, + wantBids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-456", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: "This is banner creative", + CrID: "Cr-789", + W: 100, + H: 50, + }, + BidType: "banner", + }, + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: inlineXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }, + }, + }, + { + name: "VASTUnwrap_Enabled_Video_and_Native_Bids", args: args{ module: OpenWrap{ cfg: config.Config{ Features: config.FeatureToggle{ - VASTUnwrapStatsPercent: 2, - VASTUnwrapPercent: 50, + VASTUnwrapPercent: 50, }, VastUnwrapCfg: unWrapCfg.VastUnWrapCfg{ MaxWrapperSupport: 5, @@ -202,43 +397,85 @@ func TestHandleRawBidderResponseHook(t *testing.T) { metricEngine: mockMetricsEngine, }, payload: hookstage.RawBidderResponsePayload{ - Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ - ID: "Bid-123", - ImpID: fmt.Sprintf("div-adunit-%d", 123), - Price: 2.1, - AdM: vastXMLAdM, - CrID: "Cr-234", - W: 100, - H: 50, + BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: vastXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", }, - BidType: "video", - }, + { + Bid: &openrtb2.Bid{ + ID: "Bid-456", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: "This is native creative", + CrID: "Cr-789", + W: 100, + H: 50, + }, + BidType: "native", + }}, }, Bidder: "pubmatic", }, moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{models.RequestContext: models.RequestCtx{VastUnwrapEnabled: true}}}, - - isAdmUpdated: true, + isAdmUpdated: true, }, + mockHandler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Add("unwrap-status", "0") + w.Header().Add("unwrap-count", "0") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(inlineXMLAdM)) + }), wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, setup: func() { - mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", "0").AnyTimes() - mockMetricsEngine.EXPECT().RecordUnwrapWrapperCount("5890", "pubmatic", "1").AnyTimes() - mockMetricsEngine.EXPECT().RecordUnwrapRequestTime("5890", "pubmatic", gomock.Any()).AnyTimes() - mockMetricsEngine.EXPECT().RecordUnwrapRespTime("5890", "1", gomock.Any()).AnyTimes() + mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", "0") + mockMetricsEngine.EXPECT().RecordUnwrapWrapperCount("5890", "pubmatic", "0") + mockMetricsEngine.EXPECT().RecordUnwrapRequestTime("5890", "pubmatic", gomock.Any()) + mockMetricsEngine.EXPECT().RecordUnwrapRespTime("5890", "0", gomock.Any()) + }, + wantBids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-456", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: "This is native creative", + CrID: "Cr-789", + W: 100, + H: 50, + }, + BidType: "native", + }, + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: inlineXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }, }, - wantErr: false, }, { - name: "Set Vast Unwrapper to true in request context for multiple bids with type video", + name: "VASTUnwrap_Enabled_Single_Video_bid_and_source_owsdk", args: args{ module: OpenWrap{ cfg: config.Config{ Features: config.FeatureToggle{ - VASTUnwrapStatsPercent: 2, - VASTUnwrapPercent: 50, + VASTUnwrapPercent: 50, }, VastUnwrapCfg: unWrapCfg.VastUnWrapCfg{ MaxWrapperSupport: 5, @@ -248,36 +485,26 @@ func TestHandleRawBidderResponseHook(t *testing.T) { metricEngine: mockMetricsEngine, }, payload: hookstage.RawBidderResponsePayload{ - Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ - ID: "Bid-123", - ImpID: fmt.Sprintf("div-adunit-%d", 123), - Price: 2.1, - AdM: vastXMLAdM, - CrID: "Cr-234", - W: 100, - H: 50, + BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: vastXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", }, - BidType: "video", }, - { - Bid: &openrtb2.Bid{ - ID: "Bid-456", - ImpID: fmt.Sprintf("div-adunit-%d", 123), - Price: 2.1, - AdM: vastXMLAdM, - CrID: "Cr-789", - W: 100, - H: 50, - }, - BidType: "video", - }}, + }, Bidder: "pubmatic", }, moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{models.RequestContext: models.RequestCtx{VastUnwrapEnabled: true}}}, isAdmUpdated: true, - randomNumber: 10, }, mockHandler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Header().Add("unwrap-status", "0") @@ -287,22 +514,33 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }), wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, setup: func() { - mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", "0").AnyTimes() - mockMetricsEngine.EXPECT().RecordUnwrapWrapperCount("5890", "pubmatic", "1").AnyTimes() - mockMetricsEngine.EXPECT().RecordUnwrapRequestTime("5890", "pubmatic", gomock.Any()).AnyTimes() - mockMetricsEngine.EXPECT().RecordUnwrapRespTime("5890", "1", gomock.Any()).AnyTimes() + mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", "0") + mockMetricsEngine.EXPECT().RecordUnwrapWrapperCount("5890", "pubmatic", "1") + mockMetricsEngine.EXPECT().RecordUnwrapRequestTime("5890", "pubmatic", gomock.Any()) + mockMetricsEngine.EXPECT().RecordUnwrapRespTime("5890", "1", gomock.Any()) + }, + wantBids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: inlineXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }, }, - wantErr: false, }, - { - name: "Set Vast Unwrapper to true in request context for multiple bids with different type", + name: "VASTUnwrap_Enabled_Native_and_Banner_Bids", args: args{ module: OpenWrap{ cfg: config.Config{ Features: config.FeatureToggle{ - VASTUnwrapStatsPercent: 2, - VASTUnwrapPercent: 50, + VASTUnwrapPercent: 50, }, VastUnwrapCfg: unWrapCfg.VastUnWrapCfg{ MaxWrapperSupport: 5, @@ -312,60 +550,423 @@ func TestHandleRawBidderResponseHook(t *testing.T) { metricEngine: mockMetricsEngine, }, payload: hookstage.RawBidderResponsePayload{ - Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ - ID: "Bid-123", - ImpID: fmt.Sprintf("div-adunit-%d", 123), - Price: 2.1, - AdM: vastXMLAdM, - CrID: "Cr-234", - W: 100, - H: 50, + BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: "This is banner creative", + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "banner", + }, + { + Bid: &openrtb2.Bid{ + ID: "Bid-456", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: "This is native creative", + CrID: "Cr-789", + W: 100, + H: 50, + }, + BidType: "native", + }}, + }, + Bidder: "pubmatic", + }, + moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{models.RequestContext: models.RequestCtx{VastUnwrapEnabled: true}}}, + }, + wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, + wantBids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: "This is banner creative", + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "banner", + }, + { + Bid: &openrtb2.Bid{ + ID: "Bid-456", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: "This is native creative", + CrID: "Cr-789", + W: 100, + H: 50, + }, + BidType: "native", + }, + }, + }, + { + name: "bid_with_InvalidVAST_should_be_discarded_and_should_be_present_in_seatNonBid", + args: args{ + module: OpenWrap{ + cfg: config.Config{ + Features: config.FeatureToggle{ + VASTUnwrapPercent: 50, + }, + VastUnwrapCfg: unWrapCfg.VastUnWrapCfg{ + MaxWrapperSupport: 5, + StatConfig: unWrapCfg.StatConfig{Endpoint: "http://10.172.141.13:8080", PublishInterval: 1}, + APPConfig: unWrapCfg.AppConfig{UnwrapDefaultTimeout: 1500}, + }}, + metricEngine: mockMetricsEngine, + }, + payload: hookstage.RawBidderResponsePayload{ + BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: invalidVastXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", }, - BidType: "video", }, - { - Bid: &openrtb2.Bid{ - ID: "Bid-456", - ImpID: fmt.Sprintf("div-adunit-%d", 123), + }, + Bidder: "pubmatic", + }, + moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{models.RequestContext: models.RequestCtx{VastUnwrapEnabled: true}}}, + }, + mockHandler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Add("unwrap-status", models.UnwrapInvalidVASTStatus) + w.WriteHeader(http.StatusNoContent) + }), + wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, + setup: func() { + mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", models.UnwrapInvalidVASTStatus) + mockMetricsEngine.EXPECT().RecordUnwrapRequestTime("5890", "pubmatic", gomock.Any()) + }, + wantBids: []*adapters.TypedBid{}, + wantSeatNonBid: func() openrtb_ext.NonBidCollection { + seatNonBid := openrtb_ext.NonBidCollection{} + seatNonBid.AddBid(openrtb_ext.NonBid{ + ImpId: fmt.Sprintf("div-adunit-%d", 123), + StatusCode: int(nbr.LossBidLostInVastUnwrap), + Ext: openrtb_ext.ExtNonBid{ + Prebid: openrtb_ext.ExtNonBidPrebid{ + Bid: openrtb_ext.ExtNonBidPrebidBid{ Price: 2.1, - AdM: "This is banner creative", - CrID: "Cr-789", + ID: "Bid-123", W: 100, H: 50, + Type: openrtb_ext.BidTypeVideo, }, - BidType: "banner", + }, + }, + }, "pubmatic") + return seatNonBid + }(), + }, + { + name: "bid_with_EmptyVAST_should_be_discarded_and_should_be_present_in_seatNonBid", + args: args{ + module: OpenWrap{ + cfg: config.Config{ + Features: config.FeatureToggle{ + VASTUnwrapPercent: 50, + }, + VastUnwrapCfg: unWrapCfg.VastUnWrapCfg{ + MaxWrapperSupport: 5, + StatConfig: unWrapCfg.StatConfig{Endpoint: "http://10.172.141.13:8080", PublishInterval: 1}, + APPConfig: unWrapCfg.AppConfig{UnwrapDefaultTimeout: 1500}, }}, + metricEngine: mockMetricsEngine, + }, + payload: hookstage.RawBidderResponsePayload{ + BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: invalidVastXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }, + }, + }, Bidder: "pubmatic", }, moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{models.RequestContext: models.RequestCtx{VastUnwrapEnabled: true}}}, - - isAdmUpdated: true, }, mockHandler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Add("unwrap-status", "0") - w.Header().Add("unwrap-count", "0") - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(inlineXMLAdM)) + w.Header().Add("unwrap-status", models.UnwrapEmptyVASTStatus) + w.WriteHeader(http.StatusNoContent) }), wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, setup: func() { - mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", "0").AnyTimes() - mockMetricsEngine.EXPECT().RecordUnwrapWrapperCount("5890", "pubmatic", "0").AnyTimes() - mockMetricsEngine.EXPECT().RecordUnwrapRequestTime("5890", "pubmatic", gomock.Any()).AnyTimes() - mockMetricsEngine.EXPECT().RecordUnwrapRespTime("5890", "0", gomock.Any()).AnyTimes() + mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", models.UnwrapEmptyVASTStatus) + mockMetricsEngine.EXPECT().RecordUnwrapRequestTime("5890", "pubmatic", gomock.Any()) + }, + wantBids: []*adapters.TypedBid{}, + wantSeatNonBid: func() openrtb_ext.NonBidCollection { + seatNonBid := openrtb_ext.NonBidCollection{} + seatNonBid.AddBid(openrtb_ext.NonBid{ + ImpId: fmt.Sprintf("div-adunit-%d", 123), + StatusCode: int(nbr.LossBidLostInVastUnwrap), + Ext: openrtb_ext.ExtNonBid{ + Prebid: openrtb_ext.ExtNonBidPrebid{ + Bid: openrtb_ext.ExtNonBidPrebidBid{ + Price: 2.1, + ID: "Bid-123", + W: 100, + H: 50, + Type: openrtb_ext.BidTypeVideo, + }, + }, + }, + }, "pubmatic") + return seatNonBid + }(), + }, + { + name: "VASTUnwrap_Disabled_Video_Bids_Valid_XML", + args: args{ + module: OpenWrap{ + cfg: config.Config{ + Features: config.FeatureToggle{}, + VastUnwrapCfg: unWrapCfg.VastUnWrapCfg{ + MaxWrapperSupport: 5, + StatConfig: unWrapCfg.StatConfig{Endpoint: "http://10.172.141.13:8080", PublishInterval: 1}, + APPConfig: unWrapCfg.AppConfig{UnwrapDefaultTimeout: 1500}, + }}, + metricEngine: mockMetricsEngine, + }, + payload: hookstage.RawBidderResponsePayload{ + BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: vastXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }}}, + Bidder: "pubmatic", + }, + moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{models.RequestContext: models.RequestCtx{VastUnwrapEnabled: false}}}, + }, + wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, + wantBids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: vastXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }, + }, + }, + { + name: "VASTUnwrap_Disabled_Video_and_Banner_Bids", + args: args{ + module: OpenWrap{ + cfg: config.Config{ + Features: config.FeatureToggle{}, + VastUnwrapCfg: unWrapCfg.VastUnWrapCfg{ + MaxWrapperSupport: 5, + StatConfig: unWrapCfg.StatConfig{Endpoint: "http://10.172.141.13:8080", PublishInterval: 1}, + APPConfig: unWrapCfg.AppConfig{UnwrapDefaultTimeout: 1500}, + }}, + metricEngine: mockMetricsEngine, + }, + payload: hookstage.RawBidderResponsePayload{ + BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: vastXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }, + { + Bid: &openrtb2.Bid{ + ID: "Bid-456", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: "This is banner creative", + CrID: "Cr-789", + W: 100, + H: 50, + }, + BidType: "banner", + }}, + }, + Bidder: "pubmatic", + }, + moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{models.RequestContext: models.RequestCtx{VastUnwrapEnabled: false}}}, + }, + wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, + wantBids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: vastXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }, + { + Bid: &openrtb2.Bid{ + ID: "Bid-456", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: "This is banner creative", + CrID: "Cr-789", + W: 100, + H: 50, + }, + BidType: "banner", + }, }, - wantErr: false, }, { - name: "Set Vast Unwrapper to true in request context with type video and source owsdk", + name: "VASTUnwrap_Disabled_Banner_Bids", + args: args{ + module: OpenWrap{ + cfg: config.Config{ + Features: config.FeatureToggle{}, + VastUnwrapCfg: unWrapCfg.VastUnWrapCfg{ + MaxWrapperSupport: 5, + StatConfig: unWrapCfg.StatConfig{Endpoint: "http://10.172.141.13:8080", PublishInterval: 1}, + APPConfig: unWrapCfg.AppConfig{UnwrapDefaultTimeout: 1500}, + }}, + metricEngine: mockMetricsEngine, + }, + payload: hookstage.RawBidderResponsePayload{ + BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-456", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: "This is banner creative", + CrID: "Cr-789", + W: 100, + H: 50, + }, + BidType: "banner", + }}, + }, + Bidder: "pubmatic", + }, + moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{models.RequestContext: models.RequestCtx{VastUnwrapEnabled: false}}}, + }, + wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, + wantBids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-456", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: "This is banner creative", + CrID: "Cr-789", + W: 100, + H: 50, + }, + BidType: "banner", + }, + }, + }, + { + name: "VASTUnwrap_Enabled_Banner_Bids", + args: args{ + module: OpenWrap{ + cfg: config.Config{ + Features: config.FeatureToggle{}, + VastUnwrapCfg: unWrapCfg.VastUnWrapCfg{ + MaxWrapperSupport: 5, + StatConfig: unWrapCfg.StatConfig{Endpoint: "http://10.172.141.13:8080", PublishInterval: 1}, + APPConfig: unWrapCfg.AppConfig{UnwrapDefaultTimeout: 1500}, + }}, + metricEngine: mockMetricsEngine, + }, + payload: hookstage.RawBidderResponsePayload{ + BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-456", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: "This is banner creative", + CrID: "Cr-789", + W: 100, + H: 50, + }, + BidType: "banner", + }}, + }, + Bidder: "pubmatic", + }, + moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{models.RequestContext: models.RequestCtx{VastUnwrapEnabled: true}}}, + }, + wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, + wantBids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-456", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: "This is banner creative", + CrID: "Cr-789", + W: 100, + H: 50, + }, + BidType: "banner", + }, + }, + }, + { + name: "VASTUnwrap_Enabled_Invalid_Video_and_Banner_Bids", args: args{ module: OpenWrap{ cfg: config.Config{ Features: config.FeatureToggle{ - VASTUnwrapStatsPercent: 2, - VASTUnwrapPercent: 50, + VASTUnwrapPercent: 50, }, VastUnwrapCfg: unWrapCfg.VastUnWrapCfg{ MaxWrapperSupport: 5, @@ -375,40 +976,73 @@ func TestHandleRawBidderResponseHook(t *testing.T) { metricEngine: mockMetricsEngine, }, payload: hookstage.RawBidderResponsePayload{ - Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ - ID: "Bid-123", - ImpID: fmt.Sprintf("div-adunit-%d", 123), - Price: 2.1, - AdM: vastXMLAdM, - CrID: "Cr-234", - W: 100, - H: 50, + BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: invalidVastXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }, + { + Bid: &openrtb2.Bid{ + ID: "Bid-456", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: "This is banner creative", + CrID: "Cr-789", + W: 100, + H: 50, + }, + BidType: "banner", }, - BidType: "video", }, }, Bidder: "pubmatic", }, moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{models.RequestContext: models.RequestCtx{VastUnwrapEnabled: true}}}, - - isAdmUpdated: true, }, mockHandler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Add("unwrap-status", "0") - w.Header().Add("unwrap-count", "1") - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(inlineXMLAdM)) + w.Header().Add("unwrap-status", "1") + w.WriteHeader(http.StatusNoContent) }), wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, setup: func() { - mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", "0").AnyTimes() - mockMetricsEngine.EXPECT().RecordUnwrapWrapperCount("5890", "pubmatic", "1").AnyTimes() - mockMetricsEngine.EXPECT().RecordUnwrapRequestTime("5890", "pubmatic", gomock.Any()).AnyTimes() - mockMetricsEngine.EXPECT().RecordUnwrapRespTime("5890", "1", gomock.Any()).AnyTimes() + mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", "1") + mockMetricsEngine.EXPECT().RecordUnwrapRequestTime("5890", "pubmatic", gomock.Any()) + }, + wantBids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-456", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: "This is banner creative", + CrID: "Cr-789", + W: 100, + H: 50, + }, + BidType: "banner", + }, + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: invalidVastXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }, }, - wantErr: false, }, } for _, tt := range tests { @@ -417,19 +1051,65 @@ func TestHandleRawBidderResponseHook(t *testing.T) { tt.setup() } - GetRandomNumberIn1To100 = func() int { - return tt.args.randomNumber - } - m := tt.args.module m.unwrap = unwrap.NewUnwrap("http://localhost:8001/unwrap", 200, tt.mockHandler, m.metricEngine) - _, err := m.handleRawBidderResponseHook(tt.args.moduleInvocationCtx, tt.args.payload) - if !assert.NoError(t, err, tt.wantErr) { - return - } + hookResult, _ := m.handleRawBidderResponseHook(tt.args.moduleInvocationCtx, tt.args.payload) if tt.args.moduleInvocationCtx.ModuleContext != nil && tt.args.isAdmUpdated { - assert.Equal(t, inlineXMLAdM, tt.args.payload.Bids[0].Bid.AdM, "AdM is not updated correctly after executing RawBidderResponse hook.") + assert.Equal(t, inlineXMLAdM, tt.args.payload.BidderResponse.Bids[0].Bid.AdM, "AdM is not updated correctly after executing RawBidderResponse hook.") + } + for _, mut := range hookResult.ChangeSet.Mutations() { + newPayload, err := mut.Apply(tt.args.payload) + assert.NoError(t, err) + tt.args.payload = newPayload + } + if tt.wantBids != nil { + assert.ElementsMatch(t, tt.wantBids, tt.args.payload.BidderResponse.Bids, "Mismatched response bids") } + + assert.Equal(t, tt.wantSeatNonBid, hookResult.SeatNonBid, "mismatched seatNonBids") + }) + } +} + +func TestIsEligibleForUnwrap(t *testing.T) { + type args struct { + bid *adapters.TypedBid + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "Bid is nil", + args: args{bid: nil}, + want: false, + }, + { + name: "Bid.Bid is nil", + args: args{bid: &adapters.TypedBid{Bid: nil}}, + want: false, + }, + { + name: "AdM is empty", + args: args{bid: &adapters.TypedBid{Bid: &openrtb2.Bid{AdM: ""}}}, + want: false, + }, + { + name: "BidType is not video", + args: args{bid: &adapters.TypedBid{Bid: &openrtb2.Bid{AdM: "some_adm"}, BidType: openrtb_ext.BidTypeBanner}}, + want: false, + }, + { + name: "Bid is eligible for unwrap", + args: args{bid: &adapters.TypedBid{Bid: &openrtb2.Bid{AdM: "some_adm"}, BidType: openrtb_ext.BidTypeVideo}}, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := isEligibleForUnwrap(tt.args.bid) + assert.Equal(t, tt.want, got) }) } } diff --git a/modules/pubmatic/openwrap/metrics/config/multimetrics.go b/modules/pubmatic/openwrap/metrics/config/multimetrics.go index 405a15cb20e..2b2d282dbc4 100644 --- a/modules/pubmatic/openwrap/metrics/config/multimetrics.go +++ b/modules/pubmatic/openwrap/metrics/config/multimetrics.go @@ -103,13 +103,6 @@ func (me *MultiMetricsEngine) RecordPublisherProfileRequests(publisher, profile } } -// RecordPublisherInvalidProfileImpressions across all engines -func (me *MultiMetricsEngine) RecordPublisherInvalidProfileImpressions(publisher, profileID string, impCount int) { - for _, thisME := range *me { - thisME.RecordPublisherInvalidProfileImpressions(publisher, profileID, impCount) - } -} - // RecordNobidErrPrebidServerRequests across all engines func (me *MultiMetricsEngine) RecordNobidErrPrebidServerRequests(publisher string, nbr int) { for _, thisME := range *me { @@ -167,9 +160,9 @@ func (me *MultiMetricsEngine) RecordPublisherResponseTimeStats(publisher string, } // RecordPublisherWrapperLoggerFailure across all engines -func (me *MultiMetricsEngine) RecordPublisherWrapperLoggerFailure(publisher, profileID, versionID string) { +func (me *MultiMetricsEngine) RecordPublisherWrapperLoggerFailure(publisher string) { for _, thisME := range *me { - thisME.RecordPublisherWrapperLoggerFailure(publisher, profileID, versionID) + thisME.RecordPublisherWrapperLoggerFailure(publisher) } } @@ -188,9 +181,9 @@ func (me *MultiMetricsEngine) RecordPublisherInvalidProfileRequests(endpoint, pu } // RecordBadRequests across all engines -func (me *MultiMetricsEngine) RecordBadRequests(endpoint string, errorCode int) { +func (me *MultiMetricsEngine) RecordBadRequests(endpoint, publisher string, errorCode int) { for _, thisME := range *me { - thisME.RecordBadRequests(endpoint, errorCode) + thisME.RecordBadRequests(endpoint, publisher, errorCode) } } @@ -530,6 +523,20 @@ func (me *MultiMetricsEngine) RecordSignalDataStatus(pubid, profileid, signalTyp } } +// RecordFailedParsingItuneID record failed parsing itune id +func (me *MultiMetricsEngine) RecordFailedParsingItuneID(pubId, profId string) { + for _, thisME := range *me { + thisME.RecordFailedParsingItuneID(pubId, profId) + } +} + +// RecordEndpointResponseSize record endpoint specific response size +func (me *MultiMetricsEngine) RecordEndpointResponseSize(endpoint string, bodySize float64) { + for _, thisME := range *me { + thisME.RecordEndpointResponseSize(endpoint, bodySize) + } +} + // RecordBidRecoveryStatus across all engines func (me *MultiMetricsEngine) RecordBidRecoveryStatus(publisher, profile string, success bool) { for _, thisME := range *me { diff --git a/modules/pubmatic/openwrap/metrics/config/multimetrics_test.go b/modules/pubmatic/openwrap/metrics/config/multimetrics_test.go index 554ae4dc438..fb5ef32caec 100644 --- a/modules/pubmatic/openwrap/metrics/config/multimetrics_test.go +++ b/modules/pubmatic/openwrap/metrics/config/multimetrics_test.go @@ -146,7 +146,6 @@ func TestRecordFunctionForMultiMetricsEngine(t *testing.T) { platform := "video" responseTime := 1 endpoint := "in-app" - versionID := "1" errorCode := 10 processingTime := 10 method := "GET" @@ -166,7 +165,6 @@ func TestRecordFunctionForMultiMetricsEngine(t *testing.T) { mockEngine.EXPECT().RecordPartnerConfigErrors(publisher, profile, partner, models.PartnerErrSlotNotMapped) mockEngine.EXPECT().RecordPublisherProfileRequests(publisher, profile) - mockEngine.EXPECT().RecordPublisherInvalidProfileImpressions(publisher, profile, impCount) mockEngine.EXPECT().RecordNobidErrPrebidServerRequests(publisher, int(nbr.AllPartnerThrottled)) mockEngine.EXPECT().RecordNobidErrPrebidServerResponse(publisher) mockEngine.EXPECT().RecordInvalidCreativeStats(publisher, partner) @@ -175,10 +173,10 @@ func TestRecordFunctionForMultiMetricsEngine(t *testing.T) { mockEngine.EXPECT().RecordPublisherResponseEncodingErrorStats(publisher) mockEngine.EXPECT().RecordPartnerResponseTimeStats(publisher, partner, responseTime) mockEngine.EXPECT().RecordPublisherResponseTimeStats(publisher, responseTime) - mockEngine.EXPECT().RecordPublisherWrapperLoggerFailure(publisher, profile, versionID) + mockEngine.EXPECT().RecordPublisherWrapperLoggerFailure(publisher) mockEngine.EXPECT().RecordCacheErrorRequests(endpoint, publisher, profile) mockEngine.EXPECT().RecordPublisherInvalidProfileRequests(endpoint, publisher, profile) - mockEngine.EXPECT().RecordBadRequests(endpoint, errorCode) + mockEngine.EXPECT().RecordBadRequests(endpoint, publisher, errorCode) mockEngine.EXPECT().RecordPrebidTimeoutRequests(publisher, profile) mockEngine.EXPECT().RecordSSTimeoutRequests(publisher, profile) mockEngine.EXPECT().RecordUidsCookieNotPresentErrorStats(publisher, profile) @@ -236,7 +234,6 @@ func TestRecordFunctionForMultiMetricsEngine(t *testing.T) { multiMetricEngine.RecordPartnerResponseErrors(publisher, partner, models.PartnerErrTimeout) multiMetricEngine.RecordPartnerConfigErrors(publisher, profile, partner, models.PartnerErrSlotNotMapped) multiMetricEngine.RecordPublisherProfileRequests(publisher, profile) - multiMetricEngine.RecordPublisherInvalidProfileImpressions(publisher, profile, impCount) multiMetricEngine.RecordNobidErrPrebidServerRequests(publisher, int(nbr.AllPartnerThrottled)) multiMetricEngine.RecordNobidErrPrebidServerResponse(publisher) multiMetricEngine.RecordInvalidCreativeStats(publisher, partner) @@ -245,10 +242,10 @@ func TestRecordFunctionForMultiMetricsEngine(t *testing.T) { multiMetricEngine.RecordPublisherResponseEncodingErrorStats(publisher) multiMetricEngine.RecordPartnerResponseTimeStats(publisher, partner, responseTime) multiMetricEngine.RecordPublisherResponseTimeStats(publisher, responseTime) - multiMetricEngine.RecordPublisherWrapperLoggerFailure(publisher, profile, versionID) + multiMetricEngine.RecordPublisherWrapperLoggerFailure(publisher) multiMetricEngine.RecordCacheErrorRequests(endpoint, publisher, profile) multiMetricEngine.RecordPublisherInvalidProfileRequests(endpoint, publisher, profile) - multiMetricEngine.RecordBadRequests(endpoint, errorCode) + multiMetricEngine.RecordBadRequests(endpoint, publisher, errorCode) multiMetricEngine.RecordPrebidTimeoutRequests(publisher, profile) multiMetricEngine.RecordSSTimeoutRequests(publisher, profile) multiMetricEngine.RecordUidsCookieNotPresentErrorStats(publisher, profile) diff --git a/modules/pubmatic/openwrap/metrics/metrics.go b/modules/pubmatic/openwrap/metrics/metrics.go index 6af13a88689..4f97931245e 100644 --- a/modules/pubmatic/openwrap/metrics/metrics.go +++ b/modules/pubmatic/openwrap/metrics/metrics.go @@ -9,16 +9,15 @@ type MetricsEngine interface { RecordPartnerResponseErrors(publisherID, partner, err string) RecordPartnerConfigErrors(publisherID, profileID, partner string, errcode int) RecordPublisherProfileRequests(publisher, profileID string) - RecordPublisherInvalidProfileImpressions(publisher, profileID string, impCount int) RecordNobidErrPrebidServerRequests(publisher string, nbr int) RecordNobidErrPrebidServerResponse(publisher string) RecordPlatformPublisherPartnerReqStats(platform, publisher, partner string) RecordPlatformPublisherPartnerResponseStats(platform, publisher, partner string) RecordPartnerResponseTimeStats(publisher, partner string, responseTime int) RecordPublisherResponseTimeStats(publisher string, responseTimeMs int) - RecordPublisherWrapperLoggerFailure(publisher, profileID, versionID string) + RecordPublisherWrapperLoggerFailure(publisher string) RecordPublisherInvalidProfileRequests(endpoint, publisher, profileID string) - RecordBadRequests(endpoint string, errorCode int) + RecordBadRequests(endpoint, publisher string, errorCode int) RecordUidsCookieNotPresentErrorStats(publisher, profileID string) RecordVideoInstlImpsStats(publisher, profileID string) RecordImpDisabledViaConfigStats(impType, publisher, profileID string) @@ -94,4 +93,8 @@ type MetricsEngine interface { //VMAP-adrule RecordAdruleEnabled(pubId, profId string) RecordAdruleValidationFailure(pubId, profId string) + + //AppLovinMax metrics + RecordFailedParsingItuneID(pubId, profId string) + RecordEndpointResponseSize(endpoint string, bodySize float64) } diff --git a/modules/pubmatic/openwrap/metrics/mock/mock.go b/modules/pubmatic/openwrap/metrics/mock/mock.go index d70dc6a196c..fcd72ae30bf 100644 --- a/modules/pubmatic/openwrap/metrics/mock/mock.go +++ b/modules/pubmatic/openwrap/metrics/mock/mock.go @@ -5,811 +5,822 @@ package mock_metrics import ( - reflect "reflect" - time "time" - gomock "github.com/golang/mock/gomock" metrics "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics" + reflect "reflect" + time "time" ) -// MockMetricsEngine is a mock of MetricsEngine interface. +// MockMetricsEngine is a mock of MetricsEngine interface type MockMetricsEngine struct { ctrl *gomock.Controller recorder *MockMetricsEngineMockRecorder } -// MockMetricsEngineMockRecorder is the mock recorder for MockMetricsEngine. +// MockMetricsEngineMockRecorder is the mock recorder for MockMetricsEngine type MockMetricsEngineMockRecorder struct { mock *MockMetricsEngine } -// NewMockMetricsEngine creates a new mock instance. +// NewMockMetricsEngine creates a new mock instance func NewMockMetricsEngine(ctrl *gomock.Controller) *MockMetricsEngine { mock := &MockMetricsEngine{ctrl: ctrl} mock.recorder = &MockMetricsEngineMockRecorder{mock} return mock } -// EXPECT returns an object that allows the caller to indicate expected use. +// EXPECT returns an object that allows the caller to indicate expected use func (m *MockMetricsEngine) EXPECT() *MockMetricsEngineMockRecorder { return m.recorder } -// RecordAdPodGeneratedImpressionsCount mocks base method. +// RecordAdPodGeneratedImpressionsCount mocks base method func (m *MockMetricsEngine) RecordAdPodGeneratedImpressionsCount(arg0 int, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordAdPodGeneratedImpressionsCount", arg0, arg1) } -// RecordAdPodGeneratedImpressionsCount indicates an expected call of RecordAdPodGeneratedImpressionsCount. +// RecordAdPodGeneratedImpressionsCount indicates an expected call of RecordAdPodGeneratedImpressionsCount func (mr *MockMetricsEngineMockRecorder) RecordAdPodGeneratedImpressionsCount(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordAdPodGeneratedImpressionsCount", reflect.TypeOf((*MockMetricsEngine)(nil).RecordAdPodGeneratedImpressionsCount), arg0, arg1) } -// RecordAdPodImpressionYield mocks base method. +// RecordAdPodImpressionYield mocks base method func (m *MockMetricsEngine) RecordAdPodImpressionYield(arg0, arg1 int, arg2 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordAdPodImpressionYield", arg0, arg1, arg2) } -// RecordAdPodImpressionYield indicates an expected call of RecordAdPodImpressionYield. +// RecordAdPodImpressionYield indicates an expected call of RecordAdPodImpressionYield func (mr *MockMetricsEngineMockRecorder) RecordAdPodImpressionYield(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordAdPodImpressionYield", reflect.TypeOf((*MockMetricsEngine)(nil).RecordAdPodImpressionYield), arg0, arg1, arg2) } -// RecordAdruleEnabled mocks base method. +// RecordAdruleEnabled mocks base method func (m *MockMetricsEngine) RecordAdruleEnabled(arg0, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordAdruleEnabled", arg0, arg1) } -// RecordAdruleEnabled indicates an expected call of RecordAdruleEnabled. +// RecordAdruleEnabled indicates an expected call of RecordAdruleEnabled func (mr *MockMetricsEngineMockRecorder) RecordAdruleEnabled(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordAdruleEnabled", reflect.TypeOf((*MockMetricsEngine)(nil).RecordAdruleEnabled), arg0, arg1) } -// RecordAdruleValidationFailure mocks base method. +// RecordAdruleValidationFailure mocks base method func (m *MockMetricsEngine) RecordAdruleValidationFailure(arg0, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordAdruleValidationFailure", arg0, arg1) } -// RecordAdruleValidationFailure indicates an expected call of RecordAdruleValidationFailure. +// RecordAdruleValidationFailure indicates an expected call of RecordAdruleValidationFailure func (mr *MockMetricsEngineMockRecorder) RecordAdruleValidationFailure(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordAdruleValidationFailure", reflect.TypeOf((*MockMetricsEngine)(nil).RecordAdruleValidationFailure), arg0, arg1) } -// RecordAmpVideoRequests mocks base method. +// RecordAmpVideoRequests mocks base method func (m *MockMetricsEngine) RecordAmpVideoRequests(arg0, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordAmpVideoRequests", arg0, arg1) } -// RecordAmpVideoRequests indicates an expected call of RecordAmpVideoRequests. +// RecordAmpVideoRequests indicates an expected call of RecordAmpVideoRequests func (mr *MockMetricsEngineMockRecorder) RecordAmpVideoRequests(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordAmpVideoRequests", reflect.TypeOf((*MockMetricsEngine)(nil).RecordAmpVideoRequests), arg0, arg1) } -// RecordAmpVideoResponses mocks base method. +// RecordAmpVideoResponses mocks base method func (m *MockMetricsEngine) RecordAmpVideoResponses(arg0, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordAmpVideoResponses", arg0, arg1) } -// RecordAmpVideoResponses indicates an expected call of RecordAmpVideoResponses. +// RecordAmpVideoResponses indicates an expected call of RecordAmpVideoResponses func (mr *MockMetricsEngineMockRecorder) RecordAmpVideoResponses(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordAmpVideoResponses", reflect.TypeOf((*MockMetricsEngine)(nil).RecordAmpVideoResponses), arg0, arg1) } -// RecordAnalyticsTrackingThrottled mocks base method. +// RecordAnalyticsTrackingThrottled mocks base method func (m *MockMetricsEngine) RecordAnalyticsTrackingThrottled(arg0, arg1, arg2 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordAnalyticsTrackingThrottled", arg0, arg1, arg2) } -// RecordAnalyticsTrackingThrottled indicates an expected call of RecordAnalyticsTrackingThrottled. +// RecordAnalyticsTrackingThrottled indicates an expected call of RecordAnalyticsTrackingThrottled func (mr *MockMetricsEngineMockRecorder) RecordAnalyticsTrackingThrottled(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordAnalyticsTrackingThrottled", reflect.TypeOf((*MockMetricsEngine)(nil).RecordAnalyticsTrackingThrottled), arg0, arg1, arg2) } -// RecordBadRequests mocks base method. -func (m *MockMetricsEngine) RecordBadRequests(arg0 string, arg1 int) { +// RecordBadRequests mocks base method +func (m *MockMetricsEngine) RecordBadRequests(arg0, arg1 string, arg2 int) { m.ctrl.T.Helper() - m.ctrl.Call(m, "RecordBadRequests", arg0, arg1) + m.ctrl.Call(m, "RecordBadRequests", arg0, arg1, arg2) } -// RecordBadRequests indicates an expected call of RecordBadRequests. -func (mr *MockMetricsEngineMockRecorder) RecordBadRequests(arg0, arg1 interface{}) *gomock.Call { +// RecordBadRequests indicates an expected call of RecordBadRequests +func (mr *MockMetricsEngineMockRecorder) RecordBadRequests(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordBadRequests", reflect.TypeOf((*MockMetricsEngine)(nil).RecordBadRequests), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordBadRequests", reflect.TypeOf((*MockMetricsEngine)(nil).RecordBadRequests), arg0, arg1, arg2) } -// RecordBidResponseByDealCountInHB mocks base method. +// RecordBidResponseByDealCountInHB mocks base method func (m *MockMetricsEngine) RecordBidResponseByDealCountInHB(arg0, arg1, arg2, arg3 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordBidResponseByDealCountInHB", arg0, arg1, arg2, arg3) } -// RecordBidResponseByDealCountInHB indicates an expected call of RecordBidResponseByDealCountInHB. +// RecordBidResponseByDealCountInHB indicates an expected call of RecordBidResponseByDealCountInHB func (mr *MockMetricsEngineMockRecorder) RecordBidResponseByDealCountInHB(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordBidResponseByDealCountInHB", reflect.TypeOf((*MockMetricsEngine)(nil).RecordBidResponseByDealCountInHB), arg0, arg1, arg2, arg3) } -// RecordBidResponseByDealCountInPBS mocks base method. +// RecordBidResponseByDealCountInPBS mocks base method func (m *MockMetricsEngine) RecordBidResponseByDealCountInPBS(arg0, arg1, arg2, arg3 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordBidResponseByDealCountInPBS", arg0, arg1, arg2, arg3) } -// RecordBidResponseByDealCountInPBS indicates an expected call of RecordBidResponseByDealCountInPBS. +// RecordBidResponseByDealCountInPBS indicates an expected call of RecordBidResponseByDealCountInPBS func (mr *MockMetricsEngineMockRecorder) RecordBidResponseByDealCountInPBS(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordBidResponseByDealCountInPBS", reflect.TypeOf((*MockMetricsEngine)(nil).RecordBidResponseByDealCountInPBS), arg0, arg1, arg2, arg3) } -// RecordBids mocks base method. +// RecordBids mocks base method func (m *MockMetricsEngine) RecordBids(arg0, arg1, arg2, arg3 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordBids", arg0, arg1, arg2, arg3) } -// RecordBids indicates an expected call of RecordBids. +// RecordBids indicates an expected call of RecordBids func (mr *MockMetricsEngineMockRecorder) RecordBids(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordBids", reflect.TypeOf((*MockMetricsEngine)(nil).RecordBids), arg0, arg1, arg2, arg3) } -// RecordCTVHTTPMethodRequests mocks base method. +// RecordCTVHTTPMethodRequests mocks base method func (m *MockMetricsEngine) RecordCTVHTTPMethodRequests(arg0, arg1, arg2 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordCTVHTTPMethodRequests", arg0, arg1, arg2) } -// RecordCTVHTTPMethodRequests indicates an expected call of RecordCTVHTTPMethodRequests. +// RecordCTVHTTPMethodRequests indicates an expected call of RecordCTVHTTPMethodRequests func (mr *MockMetricsEngineMockRecorder) RecordCTVHTTPMethodRequests(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordCTVHTTPMethodRequests", reflect.TypeOf((*MockMetricsEngine)(nil).RecordCTVHTTPMethodRequests), arg0, arg1, arg2) } -// RecordCTVInvalidReasonCount mocks base method. +// RecordCTVInvalidReasonCount mocks base method func (m *MockMetricsEngine) RecordCTVInvalidReasonCount(arg0 int, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordCTVInvalidReasonCount", arg0, arg1) } -// RecordCTVInvalidReasonCount indicates an expected call of RecordCTVInvalidReasonCount. +// RecordCTVInvalidReasonCount indicates an expected call of RecordCTVInvalidReasonCount func (mr *MockMetricsEngineMockRecorder) RecordCTVInvalidReasonCount(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordCTVInvalidReasonCount", reflect.TypeOf((*MockMetricsEngine)(nil).RecordCTVInvalidReasonCount), arg0, arg1) } -// RecordCTVReqCountWithAdPod mocks base method. +// RecordCTVReqCountWithAdPod mocks base method func (m *MockMetricsEngine) RecordCTVReqCountWithAdPod(arg0, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordCTVReqCountWithAdPod", arg0, arg1) } -// RecordCTVReqCountWithAdPod indicates an expected call of RecordCTVReqCountWithAdPod. +// RecordCTVReqCountWithAdPod indicates an expected call of RecordCTVReqCountWithAdPod func (mr *MockMetricsEngineMockRecorder) RecordCTVReqCountWithAdPod(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordCTVReqCountWithAdPod", reflect.TypeOf((*MockMetricsEngine)(nil).RecordCTVReqCountWithAdPod), arg0, arg1) } -// RecordCTVReqImpsWithDbConfigCount mocks base method. +// RecordCTVReqImpsWithDbConfigCount mocks base method func (m *MockMetricsEngine) RecordCTVReqImpsWithDbConfigCount(arg0 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordCTVReqImpsWithDbConfigCount", arg0) } -// RecordCTVReqImpsWithDbConfigCount indicates an expected call of RecordCTVReqImpsWithDbConfigCount. +// RecordCTVReqImpsWithDbConfigCount indicates an expected call of RecordCTVReqImpsWithDbConfigCount func (mr *MockMetricsEngineMockRecorder) RecordCTVReqImpsWithDbConfigCount(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordCTVReqImpsWithDbConfigCount", reflect.TypeOf((*MockMetricsEngine)(nil).RecordCTVReqImpsWithDbConfigCount), arg0) } -// RecordCTVReqImpsWithReqConfigCount mocks base method. +// RecordCTVReqImpsWithReqConfigCount mocks base method func (m *MockMetricsEngine) RecordCTVReqImpsWithReqConfigCount(arg0 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordCTVReqImpsWithReqConfigCount", arg0) } -// RecordCTVReqImpsWithReqConfigCount indicates an expected call of RecordCTVReqImpsWithReqConfigCount. +// RecordCTVReqImpsWithReqConfigCount indicates an expected call of RecordCTVReqImpsWithReqConfigCount func (mr *MockMetricsEngineMockRecorder) RecordCTVReqImpsWithReqConfigCount(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordCTVReqImpsWithReqConfigCount", reflect.TypeOf((*MockMetricsEngine)(nil).RecordCTVReqImpsWithReqConfigCount), arg0) } -// RecordCTVRequests mocks base method. +// RecordCTVRequests mocks base method func (m *MockMetricsEngine) RecordCTVRequests(arg0, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordCTVRequests", arg0, arg1) } -// RecordCTVRequests indicates an expected call of RecordCTVRequests. +// RecordCTVRequests indicates an expected call of RecordCTVRequests func (mr *MockMetricsEngineMockRecorder) RecordCTVRequests(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordCTVRequests", reflect.TypeOf((*MockMetricsEngine)(nil).RecordCTVRequests), arg0, arg1) } -// RecordCacheErrorRequests mocks base method. +// RecordCacheErrorRequests mocks base method func (m *MockMetricsEngine) RecordCacheErrorRequests(arg0, arg1, arg2 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordCacheErrorRequests", arg0, arg1, arg2) } -// RecordCacheErrorRequests indicates an expected call of RecordCacheErrorRequests. +// RecordCacheErrorRequests indicates an expected call of RecordCacheErrorRequests func (mr *MockMetricsEngineMockRecorder) RecordCacheErrorRequests(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordCacheErrorRequests", reflect.TypeOf((*MockMetricsEngine)(nil).RecordCacheErrorRequests), arg0, arg1, arg2) } -// RecordCtvUaAccuracy mocks base method. +// RecordCtvUaAccuracy mocks base method func (m *MockMetricsEngine) RecordCtvUaAccuracy(arg0, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordCtvUaAccuracy", arg0, arg1) } -// RecordCtvUaAccuracy indicates an expected call of RecordCtvUaAccuracy. +// RecordCtvUaAccuracy indicates an expected call of RecordCtvUaAccuracy func (mr *MockMetricsEngineMockRecorder) RecordCtvUaAccuracy(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordCtvUaAccuracy", reflect.TypeOf((*MockMetricsEngine)(nil).RecordCtvUaAccuracy), arg0, arg1) } -// RecordDBQueryFailure mocks base method. +// RecordDBQueryFailure mocks base method func (m *MockMetricsEngine) RecordDBQueryFailure(arg0, arg1, arg2 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordDBQueryFailure", arg0, arg1, arg2) } -// RecordDBQueryFailure indicates an expected call of RecordDBQueryFailure. +// RecordDBQueryFailure indicates an expected call of RecordDBQueryFailure func (mr *MockMetricsEngineMockRecorder) RecordDBQueryFailure(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordDBQueryFailure", reflect.TypeOf((*MockMetricsEngine)(nil).RecordDBQueryFailure), arg0, arg1, arg2) } -// RecordGetProfileDataTime mocks base method. +// RecordEndpointResponseSize mocks base method +func (m *MockMetricsEngine) RecordEndpointResponseSize(arg0 string, arg1 float64) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordEndpointResponseSize", arg0, arg1) +} + +// RecordEndpointResponseSize indicates an expected call of RecordEndpointResponseSize +func (mr *MockMetricsEngineMockRecorder) RecordEndpointResponseSize(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordEndpointResponseSize", reflect.TypeOf((*MockMetricsEngine)(nil).RecordEndpointResponseSize), arg0, arg1) +} + +// RecordFailedParsingItuneID mocks base method +func (m *MockMetricsEngine) RecordFailedParsingItuneID(arg0, arg1 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordFailedParsingItuneID", arg0, arg1) +} + +// RecordFailedParsingItuneID indicates an expected call of RecordFailedParsingItuneID +func (mr *MockMetricsEngineMockRecorder) RecordFailedParsingItuneID(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordFailedParsingItuneID", reflect.TypeOf((*MockMetricsEngine)(nil).RecordFailedParsingItuneID), arg0, arg1) +} + +// RecordGetProfileDataTime mocks base method func (m *MockMetricsEngine) RecordGetProfileDataTime(arg0 time.Duration) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordGetProfileDataTime", arg0) } -// RecordGetProfileDataTime indicates an expected call of RecordGetProfileDataTime. +// RecordGetProfileDataTime indicates an expected call of RecordGetProfileDataTime func (mr *MockMetricsEngineMockRecorder) RecordGetProfileDataTime(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordGetProfileDataTime", reflect.TypeOf((*MockMetricsEngine)(nil).RecordGetProfileDataTime), arg0) } -// RecordImpDisabledViaConfigStats mocks base method. +// RecordImpDisabledViaConfigStats mocks base method func (m *MockMetricsEngine) RecordImpDisabledViaConfigStats(arg0, arg1, arg2 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordImpDisabledViaConfigStats", arg0, arg1, arg2) } -// RecordImpDisabledViaConfigStats indicates an expected call of RecordImpDisabledViaConfigStats. +// RecordImpDisabledViaConfigStats indicates an expected call of RecordImpDisabledViaConfigStats func (mr *MockMetricsEngineMockRecorder) RecordImpDisabledViaConfigStats(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordImpDisabledViaConfigStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordImpDisabledViaConfigStats), arg0, arg1, arg2) } -// RecordInjectTrackerErrorCount mocks base method. +// RecordInjectTrackerErrorCount mocks base method func (m *MockMetricsEngine) RecordInjectTrackerErrorCount(arg0, arg1, arg2 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordInjectTrackerErrorCount", arg0, arg1, arg2) } -// RecordInjectTrackerErrorCount indicates an expected call of RecordInjectTrackerErrorCount. +// RecordInjectTrackerErrorCount indicates an expected call of RecordInjectTrackerErrorCount func (mr *MockMetricsEngineMockRecorder) RecordInjectTrackerErrorCount(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordInjectTrackerErrorCount", reflect.TypeOf((*MockMetricsEngine)(nil).RecordInjectTrackerErrorCount), arg0, arg1, arg2) } -// RecordInvalidCreativeStats mocks base method. +// RecordInvalidCreativeStats mocks base method func (m *MockMetricsEngine) RecordInvalidCreativeStats(arg0, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordInvalidCreativeStats", arg0, arg1) } -// RecordInvalidCreativeStats indicates an expected call of RecordInvalidCreativeStats. +// RecordInvalidCreativeStats indicates an expected call of RecordInvalidCreativeStats func (mr *MockMetricsEngineMockRecorder) RecordInvalidCreativeStats(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordInvalidCreativeStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordInvalidCreativeStats), arg0, arg1) } -// RecordLurlBatchSent mocks base method. +// RecordLurlBatchSent mocks base method func (m *MockMetricsEngine) RecordLurlBatchSent(arg0 metrics.LurlBatchStatusLabels) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordLurlBatchSent", arg0) } -// RecordLurlBatchSent indicates an expected call of RecordLurlBatchSent. +// RecordLurlBatchSent indicates an expected call of RecordLurlBatchSent func (mr *MockMetricsEngineMockRecorder) RecordLurlBatchSent(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordLurlBatchSent", reflect.TypeOf((*MockMetricsEngine)(nil).RecordLurlBatchSent), arg0) } -// RecordLurlSent mocks base method. +// RecordLurlSent mocks base method func (m *MockMetricsEngine) RecordLurlSent(arg0 metrics.LurlStatusLabels) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordLurlSent", arg0) } -// RecordLurlSent indicates an expected call of RecordLurlSent. +// RecordLurlSent indicates an expected call of RecordLurlSent func (mr *MockMetricsEngineMockRecorder) RecordLurlSent(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordLurlSent", reflect.TypeOf((*MockMetricsEngine)(nil).RecordLurlSent), arg0) } -// RecordNobidErrPrebidServerRequests mocks base method. +// RecordNobidErrPrebidServerRequests mocks base method func (m *MockMetricsEngine) RecordNobidErrPrebidServerRequests(arg0 string, arg1 int) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordNobidErrPrebidServerRequests", arg0, arg1) } -// RecordNobidErrPrebidServerRequests indicates an expected call of RecordNobidErrPrebidServerRequests. +// RecordNobidErrPrebidServerRequests indicates an expected call of RecordNobidErrPrebidServerRequests func (mr *MockMetricsEngineMockRecorder) RecordNobidErrPrebidServerRequests(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordNobidErrPrebidServerRequests", reflect.TypeOf((*MockMetricsEngine)(nil).RecordNobidErrPrebidServerRequests), arg0, arg1) } -// RecordNobidErrPrebidServerResponse mocks base method. +// RecordNobidErrPrebidServerResponse mocks base method func (m *MockMetricsEngine) RecordNobidErrPrebidServerResponse(arg0 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordNobidErrPrebidServerResponse", arg0) } -// RecordNobidErrPrebidServerResponse indicates an expected call of RecordNobidErrPrebidServerResponse. +// RecordNobidErrPrebidServerResponse indicates an expected call of RecordNobidErrPrebidServerResponse func (mr *MockMetricsEngineMockRecorder) RecordNobidErrPrebidServerResponse(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordNobidErrPrebidServerResponse", reflect.TypeOf((*MockMetricsEngine)(nil).RecordNobidErrPrebidServerResponse), arg0) } -// RecordOWServerPanic mocks base method. +// RecordOWServerPanic mocks base method func (m *MockMetricsEngine) RecordOWServerPanic(arg0, arg1, arg2, arg3 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordOWServerPanic", arg0, arg1, arg2, arg3) } -// RecordOWServerPanic indicates an expected call of RecordOWServerPanic. +// RecordOWServerPanic indicates an expected call of RecordOWServerPanic func (mr *MockMetricsEngineMockRecorder) RecordOWServerPanic(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordOWServerPanic", reflect.TypeOf((*MockMetricsEngine)(nil).RecordOWServerPanic), arg0, arg1, arg2, arg3) } -// RecordOpenWrapServerPanicStats mocks base method. +// RecordOpenWrapServerPanicStats mocks base method func (m *MockMetricsEngine) RecordOpenWrapServerPanicStats(arg0, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordOpenWrapServerPanicStats", arg0, arg1) } -// RecordOpenWrapServerPanicStats indicates an expected call of RecordOpenWrapServerPanicStats. +// RecordOpenWrapServerPanicStats indicates an expected call of RecordOpenWrapServerPanicStats func (mr *MockMetricsEngineMockRecorder) RecordOpenWrapServerPanicStats(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordOpenWrapServerPanicStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordOpenWrapServerPanicStats), arg0, arg1) } -// RecordPBSAuctionRequestsStats mocks base method. +// RecordPBSAuctionRequestsStats mocks base method func (m *MockMetricsEngine) RecordPBSAuctionRequestsStats() { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordPBSAuctionRequestsStats") } -// RecordPBSAuctionRequestsStats indicates an expected call of RecordPBSAuctionRequestsStats. +// RecordPBSAuctionRequestsStats indicates an expected call of RecordPBSAuctionRequestsStats func (mr *MockMetricsEngineMockRecorder) RecordPBSAuctionRequestsStats() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPBSAuctionRequestsStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPBSAuctionRequestsStats)) } -// RecordPartnerConfigErrors mocks base method. +// RecordPartnerConfigErrors mocks base method func (m *MockMetricsEngine) RecordPartnerConfigErrors(arg0, arg1, arg2 string, arg3 int) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordPartnerConfigErrors", arg0, arg1, arg2, arg3) } -// RecordPartnerConfigErrors indicates an expected call of RecordPartnerConfigErrors. +// RecordPartnerConfigErrors indicates an expected call of RecordPartnerConfigErrors func (mr *MockMetricsEngineMockRecorder) RecordPartnerConfigErrors(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPartnerConfigErrors", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPartnerConfigErrors), arg0, arg1, arg2, arg3) } -// RecordPartnerResponseErrors mocks base method. +// RecordPartnerResponseErrors mocks base method func (m *MockMetricsEngine) RecordPartnerResponseErrors(arg0, arg1, arg2 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordPartnerResponseErrors", arg0, arg1, arg2) } -// RecordPartnerResponseErrors indicates an expected call of RecordPartnerResponseErrors. +// RecordPartnerResponseErrors indicates an expected call of RecordPartnerResponseErrors func (mr *MockMetricsEngineMockRecorder) RecordPartnerResponseErrors(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPartnerResponseErrors", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPartnerResponseErrors), arg0, arg1, arg2) } -// RecordPartnerResponseTimeStats mocks base method. +// RecordPartnerResponseTimeStats mocks base method func (m *MockMetricsEngine) RecordPartnerResponseTimeStats(arg0, arg1 string, arg2 int) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordPartnerResponseTimeStats", arg0, arg1, arg2) } -// RecordPartnerResponseTimeStats indicates an expected call of RecordPartnerResponseTimeStats. +// RecordPartnerResponseTimeStats indicates an expected call of RecordPartnerResponseTimeStats func (mr *MockMetricsEngineMockRecorder) RecordPartnerResponseTimeStats(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPartnerResponseTimeStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPartnerResponseTimeStats), arg0, arg1, arg2) } -// RecordPartnerTimeoutInPBS mocks base method. +// RecordPartnerTimeoutInPBS mocks base method func (m *MockMetricsEngine) RecordPartnerTimeoutInPBS(arg0, arg1, arg2 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordPartnerTimeoutInPBS", arg0, arg1, arg2) } -// RecordPartnerTimeoutInPBS indicates an expected call of RecordPartnerTimeoutInPBS. +// RecordPartnerTimeoutInPBS indicates an expected call of RecordPartnerTimeoutInPBS func (mr *MockMetricsEngineMockRecorder) RecordPartnerTimeoutInPBS(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPartnerTimeoutInPBS", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPartnerTimeoutInPBS), arg0, arg1, arg2) } -// RecordPartnerTimeoutRequests mocks base method. +// RecordPartnerTimeoutRequests mocks base method func (m *MockMetricsEngine) RecordPartnerTimeoutRequests(arg0, arg1, arg2 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordPartnerTimeoutRequests", arg0, arg1, arg2) } -// RecordPartnerTimeoutRequests indicates an expected call of RecordPartnerTimeoutRequests. +// RecordPartnerTimeoutRequests indicates an expected call of RecordPartnerTimeoutRequests func (mr *MockMetricsEngineMockRecorder) RecordPartnerTimeoutRequests(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPartnerTimeoutRequests", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPartnerTimeoutRequests), arg0, arg1, arg2) } -// RecordPlatformPublisherPartnerReqStats mocks base method. +// RecordPlatformPublisherPartnerReqStats mocks base method func (m *MockMetricsEngine) RecordPlatformPublisherPartnerReqStats(arg0, arg1, arg2 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordPlatformPublisherPartnerReqStats", arg0, arg1, arg2) } -// RecordPlatformPublisherPartnerReqStats indicates an expected call of RecordPlatformPublisherPartnerReqStats. +// RecordPlatformPublisherPartnerReqStats indicates an expected call of RecordPlatformPublisherPartnerReqStats func (mr *MockMetricsEngineMockRecorder) RecordPlatformPublisherPartnerReqStats(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPlatformPublisherPartnerReqStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPlatformPublisherPartnerReqStats), arg0, arg1, arg2) } -// RecordPlatformPublisherPartnerResponseStats mocks base method. +// RecordPlatformPublisherPartnerResponseStats mocks base method func (m *MockMetricsEngine) RecordPlatformPublisherPartnerResponseStats(arg0, arg1, arg2 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordPlatformPublisherPartnerResponseStats", arg0, arg1, arg2) } -// RecordPlatformPublisherPartnerResponseStats indicates an expected call of RecordPlatformPublisherPartnerResponseStats. +// RecordPlatformPublisherPartnerResponseStats indicates an expected call of RecordPlatformPublisherPartnerResponseStats func (mr *MockMetricsEngineMockRecorder) RecordPlatformPublisherPartnerResponseStats(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPlatformPublisherPartnerResponseStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPlatformPublisherPartnerResponseStats), arg0, arg1, arg2) } -// RecordPreProcessingTimeStats mocks base method. +// RecordPreProcessingTimeStats mocks base method func (m *MockMetricsEngine) RecordPreProcessingTimeStats(arg0 string, arg1 int) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordPreProcessingTimeStats", arg0, arg1) } -// RecordPreProcessingTimeStats indicates an expected call of RecordPreProcessingTimeStats. +// RecordPreProcessingTimeStats indicates an expected call of RecordPreProcessingTimeStats func (mr *MockMetricsEngineMockRecorder) RecordPreProcessingTimeStats(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPreProcessingTimeStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPreProcessingTimeStats), arg0, arg1) } -// RecordPrebidAuctionBidResponse mocks base method. +// RecordPrebidAuctionBidResponse mocks base method func (m *MockMetricsEngine) RecordPrebidAuctionBidResponse(arg0, arg1, arg2, arg3 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordPrebidAuctionBidResponse", arg0, arg1, arg2, arg3) } -// RecordPrebidAuctionBidResponse indicates an expected call of RecordPrebidAuctionBidResponse. +// RecordPrebidAuctionBidResponse indicates an expected call of RecordPrebidAuctionBidResponse func (mr *MockMetricsEngineMockRecorder) RecordPrebidAuctionBidResponse(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPrebidAuctionBidResponse", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPrebidAuctionBidResponse), arg0, arg1, arg2, arg3) } -// RecordPrebidCacheRequestTime mocks base method. +// RecordPrebidCacheRequestTime mocks base method func (m *MockMetricsEngine) RecordPrebidCacheRequestTime(arg0 bool, arg1 time.Duration) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordPrebidCacheRequestTime", arg0, arg1) } -// RecordPrebidCacheRequestTime indicates an expected call of RecordPrebidCacheRequestTime. +// RecordPrebidCacheRequestTime indicates an expected call of RecordPrebidCacheRequestTime func (mr *MockMetricsEngineMockRecorder) RecordPrebidCacheRequestTime(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPrebidCacheRequestTime", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPrebidCacheRequestTime), arg0, arg1) } -// RecordPrebidTimeoutRequests mocks base method. +// RecordPrebidTimeoutRequests mocks base method func (m *MockMetricsEngine) RecordPrebidTimeoutRequests(arg0, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordPrebidTimeoutRequests", arg0, arg1) } -// RecordPrebidTimeoutRequests indicates an expected call of RecordPrebidTimeoutRequests. +// RecordPrebidTimeoutRequests indicates an expected call of RecordPrebidTimeoutRequests func (mr *MockMetricsEngineMockRecorder) RecordPrebidTimeoutRequests(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPrebidTimeoutRequests", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPrebidTimeoutRequests), arg0, arg1) } -// RecordPublisherInvalidProfileImpressions mocks base method. -func (m *MockMetricsEngine) RecordPublisherInvalidProfileImpressions(arg0, arg1 string, arg2 int) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "RecordPublisherInvalidProfileImpressions", arg0, arg1, arg2) -} - -// RecordPublisherInvalidProfileImpressions indicates an expected call of RecordPublisherInvalidProfileImpressions. -func (mr *MockMetricsEngineMockRecorder) RecordPublisherInvalidProfileImpressions(arg0, arg1, arg2 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPublisherInvalidProfileImpressions", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPublisherInvalidProfileImpressions), arg0, arg1, arg2) -} - -// RecordPublisherInvalidProfileRequests mocks base method. +// RecordPublisherInvalidProfileRequests mocks base method func (m *MockMetricsEngine) RecordPublisherInvalidProfileRequests(arg0, arg1, arg2 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordPublisherInvalidProfileRequests", arg0, arg1, arg2) } -// RecordPublisherInvalidProfileRequests indicates an expected call of RecordPublisherInvalidProfileRequests. +// RecordPublisherInvalidProfileRequests indicates an expected call of RecordPublisherInvalidProfileRequests func (mr *MockMetricsEngineMockRecorder) RecordPublisherInvalidProfileRequests(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPublisherInvalidProfileRequests", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPublisherInvalidProfileRequests), arg0, arg1, arg2) } -// RecordPublisherPartnerNoCookieStats mocks base method. +// RecordPublisherPartnerNoCookieStats mocks base method func (m *MockMetricsEngine) RecordPublisherPartnerNoCookieStats(arg0, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordPublisherPartnerNoCookieStats", arg0, arg1) } -// RecordPublisherPartnerNoCookieStats indicates an expected call of RecordPublisherPartnerNoCookieStats. +// RecordPublisherPartnerNoCookieStats indicates an expected call of RecordPublisherPartnerNoCookieStats func (mr *MockMetricsEngineMockRecorder) RecordPublisherPartnerNoCookieStats(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPublisherPartnerNoCookieStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPublisherPartnerNoCookieStats), arg0, arg1) } -// RecordPublisherProfileRequests mocks base method. +// RecordPublisherProfileRequests mocks base method func (m *MockMetricsEngine) RecordPublisherProfileRequests(arg0, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordPublisherProfileRequests", arg0, arg1) } -// RecordPublisherProfileRequests indicates an expected call of RecordPublisherProfileRequests. +// RecordPublisherProfileRequests indicates an expected call of RecordPublisherProfileRequests func (mr *MockMetricsEngineMockRecorder) RecordPublisherProfileRequests(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPublisherProfileRequests", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPublisherProfileRequests), arg0, arg1) } -// RecordPublisherRequests mocks base method. +// RecordPublisherRequests mocks base method func (m *MockMetricsEngine) RecordPublisherRequests(arg0, arg1, arg2 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordPublisherRequests", arg0, arg1, arg2) } -// RecordPublisherRequests indicates an expected call of RecordPublisherRequests. +// RecordPublisherRequests indicates an expected call of RecordPublisherRequests func (mr *MockMetricsEngineMockRecorder) RecordPublisherRequests(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPublisherRequests", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPublisherRequests), arg0, arg1, arg2) } -// RecordPublisherResponseEncodingErrorStats mocks base method. +// RecordPublisherResponseEncodingErrorStats mocks base method func (m *MockMetricsEngine) RecordPublisherResponseEncodingErrorStats(arg0 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordPublisherResponseEncodingErrorStats", arg0) } -// RecordPublisherResponseEncodingErrorStats indicates an expected call of RecordPublisherResponseEncodingErrorStats. +// RecordPublisherResponseEncodingErrorStats indicates an expected call of RecordPublisherResponseEncodingErrorStats func (mr *MockMetricsEngineMockRecorder) RecordPublisherResponseEncodingErrorStats(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPublisherResponseEncodingErrorStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPublisherResponseEncodingErrorStats), arg0) } -// RecordPublisherResponseTimeStats mocks base method. +// RecordPublisherResponseTimeStats mocks base method func (m *MockMetricsEngine) RecordPublisherResponseTimeStats(arg0 string, arg1 int) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordPublisherResponseTimeStats", arg0, arg1) } -// RecordPublisherResponseTimeStats indicates an expected call of RecordPublisherResponseTimeStats. +// RecordPublisherResponseTimeStats indicates an expected call of RecordPublisherResponseTimeStats func (mr *MockMetricsEngineMockRecorder) RecordPublisherResponseTimeStats(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPublisherResponseTimeStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPublisherResponseTimeStats), arg0, arg1) } -// RecordPublisherWrapperLoggerFailure mocks base method. -func (m *MockMetricsEngine) RecordPublisherWrapperLoggerFailure(arg0, arg1, arg2 string) { +// RecordPublisherWrapperLoggerFailure mocks base method +func (m *MockMetricsEngine) RecordPublisherWrapperLoggerFailure(arg0 string) { m.ctrl.T.Helper() - m.ctrl.Call(m, "RecordPublisherWrapperLoggerFailure", arg0, arg1, arg2) + m.ctrl.Call(m, "RecordPublisherWrapperLoggerFailure", arg0) } -// RecordPublisherWrapperLoggerFailure indicates an expected call of RecordPublisherWrapperLoggerFailure. -func (mr *MockMetricsEngineMockRecorder) RecordPublisherWrapperLoggerFailure(arg0, arg1, arg2 interface{}) *gomock.Call { +// RecordPublisherWrapperLoggerFailure indicates an expected call of RecordPublisherWrapperLoggerFailure +func (mr *MockMetricsEngineMockRecorder) RecordPublisherWrapperLoggerFailure(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPublisherWrapperLoggerFailure", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPublisherWrapperLoggerFailure), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPublisherWrapperLoggerFailure", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPublisherWrapperLoggerFailure), arg0) } -// RecordReqImpsWithContentCount mocks base method. +// RecordReqImpsWithContentCount mocks base method func (m *MockMetricsEngine) RecordReqImpsWithContentCount(arg0, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordReqImpsWithContentCount", arg0, arg1) } -// RecordReqImpsWithContentCount indicates an expected call of RecordReqImpsWithContentCount. +// RecordReqImpsWithContentCount indicates an expected call of RecordReqImpsWithContentCount func (mr *MockMetricsEngineMockRecorder) RecordReqImpsWithContentCount(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordReqImpsWithContentCount", reflect.TypeOf((*MockMetricsEngine)(nil).RecordReqImpsWithContentCount), arg0, arg1) } -// RecordRequest mocks base method. +// RecordRequest mocks base method func (m *MockMetricsEngine) RecordRequest(arg0 metrics.Labels) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordRequest", arg0) } -// RecordRequest indicates an expected call of RecordRequest. +// RecordRequest indicates an expected call of RecordRequest func (mr *MockMetricsEngineMockRecorder) RecordRequest(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordRequest", reflect.TypeOf((*MockMetricsEngine)(nil).RecordRequest), arg0) } -// RecordRequestAdPodGeneratedImpressionsCount mocks base method. +// RecordRequestAdPodGeneratedImpressionsCount mocks base method func (m *MockMetricsEngine) RecordRequestAdPodGeneratedImpressionsCount(arg0 int, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordRequestAdPodGeneratedImpressionsCount", arg0, arg1) } -// RecordRequestAdPodGeneratedImpressionsCount indicates an expected call of RecordRequestAdPodGeneratedImpressionsCount. +// RecordRequestAdPodGeneratedImpressionsCount indicates an expected call of RecordRequestAdPodGeneratedImpressionsCount func (mr *MockMetricsEngineMockRecorder) RecordRequestAdPodGeneratedImpressionsCount(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordRequestAdPodGeneratedImpressionsCount", reflect.TypeOf((*MockMetricsEngine)(nil).RecordRequestAdPodGeneratedImpressionsCount), arg0, arg1) } -// RecordRequestTime mocks base method. +// RecordRequestTime mocks base method func (m *MockMetricsEngine) RecordRequestTime(arg0 string, arg1 time.Duration) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordRequestTime", arg0, arg1) } -// RecordRequestTime indicates an expected call of RecordRequestTime. +// RecordRequestTime indicates an expected call of RecordRequestTime func (mr *MockMetricsEngineMockRecorder) RecordRequestTime(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordRequestTime", reflect.TypeOf((*MockMetricsEngine)(nil).RecordRequestTime), arg0, arg1) } -// RecordSSTimeoutRequests mocks base method. +// RecordSSTimeoutRequests mocks base method func (m *MockMetricsEngine) RecordSSTimeoutRequests(arg0, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordSSTimeoutRequests", arg0, arg1) } -// RecordSSTimeoutRequests indicates an expected call of RecordSSTimeoutRequests. +// RecordSSTimeoutRequests indicates an expected call of RecordSSTimeoutRequests func (mr *MockMetricsEngineMockRecorder) RecordSSTimeoutRequests(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordSSTimeoutRequests", reflect.TypeOf((*MockMetricsEngine)(nil).RecordSSTimeoutRequests), arg0, arg1) } -// RecordSendLoggerDataTime mocks base method. +// RecordSendLoggerDataTime mocks base method func (m *MockMetricsEngine) RecordSendLoggerDataTime(arg0 time.Duration) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordSendLoggerDataTime", arg0) } -// RecordSendLoggerDataTime indicates an expected call of RecordSendLoggerDataTime. +// RecordSendLoggerDataTime indicates an expected call of RecordSendLoggerDataTime func (mr *MockMetricsEngineMockRecorder) RecordSendLoggerDataTime(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordSendLoggerDataTime", reflect.TypeOf((*MockMetricsEngine)(nil).RecordSendLoggerDataTime), arg0) } -// RecordSignalDataStatus mocks base method. +// RecordSignalDataStatus mocks base method func (m *MockMetricsEngine) RecordSignalDataStatus(arg0, arg1, arg2 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordSignalDataStatus", arg0, arg1, arg2) } -// RecordSignalDataStatus indicates an expected call of RecordSignalDataStatus. +// RecordSignalDataStatus indicates an expected call of RecordSignalDataStatus func (mr *MockMetricsEngineMockRecorder) RecordSignalDataStatus(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordSignalDataStatus", reflect.TypeOf((*MockMetricsEngine)(nil).RecordSignalDataStatus), arg0, arg1, arg2) } -// RecordStatsKeyCTVPrebidFailedImpression mocks base method. +// RecordStatsKeyCTVPrebidFailedImpression mocks base method func (m *MockMetricsEngine) RecordStatsKeyCTVPrebidFailedImpression(arg0 int, arg1, arg2 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordStatsKeyCTVPrebidFailedImpression", arg0, arg1, arg2) } -// RecordStatsKeyCTVPrebidFailedImpression indicates an expected call of RecordStatsKeyCTVPrebidFailedImpression. +// RecordStatsKeyCTVPrebidFailedImpression indicates an expected call of RecordStatsKeyCTVPrebidFailedImpression func (mr *MockMetricsEngineMockRecorder) RecordStatsKeyCTVPrebidFailedImpression(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordStatsKeyCTVPrebidFailedImpression", reflect.TypeOf((*MockMetricsEngine)(nil).RecordStatsKeyCTVPrebidFailedImpression), arg0, arg1, arg2) } -// RecordUidsCookieNotPresentErrorStats mocks base method. +// RecordUidsCookieNotPresentErrorStats mocks base method func (m *MockMetricsEngine) RecordUidsCookieNotPresentErrorStats(arg0, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordUidsCookieNotPresentErrorStats", arg0, arg1) } -// RecordUidsCookieNotPresentErrorStats indicates an expected call of RecordUidsCookieNotPresentErrorStats. +// RecordUidsCookieNotPresentErrorStats indicates an expected call of RecordUidsCookieNotPresentErrorStats func (mr *MockMetricsEngineMockRecorder) RecordUidsCookieNotPresentErrorStats(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordUidsCookieNotPresentErrorStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordUidsCookieNotPresentErrorStats), arg0, arg1) } -// RecordUnwrapRequestStatus mocks base method. +// RecordUnwrapRequestStatus mocks base method func (m *MockMetricsEngine) RecordUnwrapRequestStatus(arg0, arg1, arg2 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordUnwrapRequestStatus", arg0, arg1, arg2) } -// RecordUnwrapRequestStatus indicates an expected call of RecordUnwrapRequestStatus. +// RecordUnwrapRequestStatus indicates an expected call of RecordUnwrapRequestStatus func (mr *MockMetricsEngineMockRecorder) RecordUnwrapRequestStatus(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordUnwrapRequestStatus", reflect.TypeOf((*MockMetricsEngine)(nil).RecordUnwrapRequestStatus), arg0, arg1, arg2) } -// RecordUnwrapRequestTime mocks base method. +// RecordUnwrapRequestTime mocks base method func (m *MockMetricsEngine) RecordUnwrapRequestTime(arg0, arg1 string, arg2 time.Duration) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordUnwrapRequestTime", arg0, arg1, arg2) } -// RecordUnwrapRequestTime indicates an expected call of RecordUnwrapRequestTime. +// RecordUnwrapRequestTime indicates an expected call of RecordUnwrapRequestTime func (mr *MockMetricsEngineMockRecorder) RecordUnwrapRequestTime(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordUnwrapRequestTime", reflect.TypeOf((*MockMetricsEngine)(nil).RecordUnwrapRequestTime), arg0, arg1, arg2) } -// RecordUnwrapRespTime mocks base method. +// RecordUnwrapRespTime mocks base method func (m *MockMetricsEngine) RecordUnwrapRespTime(arg0, arg1 string, arg2 time.Duration) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordUnwrapRespTime", arg0, arg1, arg2) } -// RecordUnwrapRespTime indicates an expected call of RecordUnwrapRespTime. +// RecordUnwrapRespTime indicates an expected call of RecordUnwrapRespTime func (mr *MockMetricsEngineMockRecorder) RecordUnwrapRespTime(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordUnwrapRespTime", reflect.TypeOf((*MockMetricsEngine)(nil).RecordUnwrapRespTime), arg0, arg1, arg2) } -// RecordUnwrapWrapperCount mocks base method. +// RecordUnwrapWrapperCount mocks base method func (m *MockMetricsEngine) RecordUnwrapWrapperCount(arg0, arg1, arg2 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordUnwrapWrapperCount", arg0, arg1, arg2) } -// RecordUnwrapWrapperCount indicates an expected call of RecordUnwrapWrapperCount. +// RecordUnwrapWrapperCount indicates an expected call of RecordUnwrapWrapperCount func (mr *MockMetricsEngineMockRecorder) RecordUnwrapWrapperCount(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordUnwrapWrapperCount", reflect.TypeOf((*MockMetricsEngine)(nil).RecordUnwrapWrapperCount), arg0, arg1, arg2) } -// RecordVideoImpDisabledViaConnTypeStats mocks base method. +// RecordVideoImpDisabledViaConnTypeStats mocks base method func (m *MockMetricsEngine) RecordVideoImpDisabledViaConnTypeStats(arg0, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordVideoImpDisabledViaConnTypeStats", arg0, arg1) } -// RecordVideoImpDisabledViaConnTypeStats indicates an expected call of RecordVideoImpDisabledViaConnTypeStats. +// RecordVideoImpDisabledViaConnTypeStats indicates an expected call of RecordVideoImpDisabledViaConnTypeStats func (mr *MockMetricsEngineMockRecorder) RecordVideoImpDisabledViaConnTypeStats(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordVideoImpDisabledViaConnTypeStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordVideoImpDisabledViaConnTypeStats), arg0, arg1) } -// RecordVideoInstlImpsStats mocks base method. +// RecordVideoInstlImpsStats mocks base method func (m *MockMetricsEngine) RecordVideoInstlImpsStats(arg0, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordVideoInstlImpsStats", arg0, arg1) } -// RecordVideoInstlImpsStats indicates an expected call of RecordVideoInstlImpsStats. +// RecordVideoInstlImpsStats indicates an expected call of RecordVideoInstlImpsStats func (mr *MockMetricsEngineMockRecorder) RecordVideoInstlImpsStats(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordVideoInstlImpsStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordVideoInstlImpsStats), arg0, arg1) @@ -845,7 +856,7 @@ func (m *MockMetricsEngine) Shutdown() { m.ctrl.Call(m, "Shutdown") } -// Shutdown indicates an expected call of Shutdown. +// Shutdown indicates an expected call of Shutdown func (mr *MockMetricsEngineMockRecorder) Shutdown() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Shutdown", reflect.TypeOf((*MockMetricsEngine)(nil).Shutdown)) diff --git a/modules/pubmatic/openwrap/metrics/prometheus/prometheus.go b/modules/pubmatic/openwrap/metrics/prometheus/prometheus.go index 055cdf0a4ee..d5dad533551 100644 --- a/modules/pubmatic/openwrap/metrics/prometheus/prometheus.go +++ b/modules/pubmatic/openwrap/metrics/prometheus/prometheus.go @@ -94,6 +94,10 @@ type Metrics struct { //VMAP adrule pubProfAdruleEnabled *prometheus.CounterVec pubProfAdruleValidationfailure *prometheus.CounterVec + + //ApplovinMax + failedParsingItuneId *prometheus.CounterVec + endpointResponseSize *prometheus.HistogramVec } const ( @@ -119,7 +123,8 @@ const ( adapterCodeLabel = "adapter_code" ) -var standardTimeBuckets = []float64{0.1, 0.3, 0.75, 1} +var standardTimeBuckets = []float64{0.05, 0.1, 0.3, 0.75, 1} +var responseSizeBuckets = []float64{0, 4, 7, 10, 15} var once sync.Once var metric *Metrics @@ -262,7 +267,7 @@ func newMetrics(cfg *config.PrometheusMetrics, promRegistry *prometheus.Registry metrics.endpointBadRequest = newCounter(cfg, promRegistry, "bad_requests", "Count bad requests along with NBR code at endpoint level.", - []string{endpointLabel, nbrLabel}, + []string{pubIDLabel, endpointLabel, nbrLabel}, ) // publisher platform endpoint level metrics @@ -291,7 +296,7 @@ func newMetrics(cfg *config.PrometheusMetrics, promRegistry *prometheus.Registry metrics.loggerFailure = newCounter(cfg, promRegistry, "logger_send_failed", "Count of failures to send the logger to analytics endpoint at publisher and profile level", - []string{pubIDLabel, profileIDLabel}, + []string{pubIDLabel}, ) metrics.analyticsThrottle = newCounter(cfg, promRegistry, "analytics_throttle", @@ -362,6 +367,19 @@ func newMetrics(cfg *config.PrometheusMetrics, promRegistry *prometheus.Registry []string{pubIdLabel, profileIDLabel}, ) + metrics.failedParsingItuneId = newCounter(cfg, promRegistry, + "failed_parsing_itune_id", + "Count of failed parsing itune id", + []string{pubIdLabel, profileIDLabel}, + ) + + metrics.endpointResponseSize = newHistogramVec(cfg, promRegistry, + "endpoint_response_size", + "Size of response", + []string{endpointLabel}, + responseSizeBuckets, + ) + metrics.pubBidRecoveryTime = newHistogramVec(cfg, promRegistry, "bid_recovery_response_time", "Total time taken by request for secondary auction in ms at publisher profile level.", @@ -465,13 +483,6 @@ func (m *Metrics) RecordPublisherProfileRequests(publisherID, profileID string) }).Inc() } -func (m *Metrics) RecordPublisherInvalidProfileImpressions(publisherID, profileID string, impCount int) { - m.pubProfInvalidImps.With(prometheus.Labels{ - pubIDLabel: publisherID, - profileIDLabel: profileID, - }).Add(float64(impCount)) -} - func (m *Metrics) RecordNobidErrPrebidServerRequests(publisherID string, nbr int) { m.pubRequestValidationErrors.With(prometheus.Labels{ pubIDLabel: publisherID, @@ -522,9 +533,10 @@ func (m *Metrics) RecordPublisherInvalidProfileRequests(endpoint, publisherID, p }).Inc() } -func (m *Metrics) RecordBadRequests(endpoint string, errorCode int) { +func (m *Metrics) RecordBadRequests(endpoint, publisherID string, errorCode int) { m.endpointBadRequest.With(prometheus.Labels{ endpointLabel: endpoint, + pubIDLabel: publisherID, nbrLabel: strconv.Itoa(errorCode), }).Inc() } @@ -589,10 +601,9 @@ func (m *Metrics) RecordDBQueryFailure(queryType, publisher, profile string) { } // RecordPublisherWrapperLoggerFailure to record count of owlogger failures -func (m *Metrics) RecordPublisherWrapperLoggerFailure(publisher, profile, version string) { +func (m *Metrics) RecordPublisherWrapperLoggerFailure(publisher string) { m.loggerFailure.With(prometheus.Labels{ - pubIDLabel: publisher, - profileIDLabel: profile, + pubIDLabel: publisher, }).Inc() } @@ -614,6 +625,14 @@ func (m *Metrics) RecordSignalDataStatus(pubid, profileid, signalType string) { }).Inc() } +func (m *Metrics) RecordFailedParsingItuneID(pubId, profId string) { + m.failedParsingItuneId.With(prometheus.Labels{ + pubIDLabel: pubId, + profileIDLabel: profId, + }).Inc() + +} + // TODO - really need ? func (m *Metrics) RecordPBSAuctionRequestsStats() {} @@ -710,3 +729,9 @@ func (m *Metrics) RecordBidRecoveryResponseTime(publisherID, profileID string, r profileIDLabel: profileID, }).Observe(float64(responseTime.Milliseconds())) } + +func (m *Metrics) RecordEndpointResponseSize(endpoint string, bodySize float64) { + m.endpointResponseSize.With(prometheus.Labels{ + endpointLabel: endpoint, + }).Observe(float64(bodySize) / 1024) +} diff --git a/modules/pubmatic/openwrap/metrics/prometheus/prometheus_test.go b/modules/pubmatic/openwrap/metrics/prometheus/prometheus_test.go index 9872b7356a9..2503dfd3577 100644 --- a/modules/pubmatic/openwrap/metrics/prometheus/prometheus_test.go +++ b/modules/pubmatic/openwrap/metrics/prometheus/prometheus_test.go @@ -94,20 +94,6 @@ func TestRecordPublisherProfileRequests(t *testing.T) { }) } -func TestRecordPublisherInvalidProfileImpressions(t *testing.T) { - m := createMetricsForTesting() - - m.RecordPublisherInvalidProfileImpressions("5890", "1234", 3) - - expectedCount := float64(3) - assertCounterVecValue(t, "", "invalid_imps", m.pubProfInvalidImps, - expectedCount, - prometheus.Labels{ - pubIDLabel: "5890", - profileIDLabel: "1234", - }) -} - func TestRecordNobidErrPrebidServerRequests(t *testing.T) { m := createMetricsForTesting() @@ -183,12 +169,13 @@ func TestRecordPublisherInvalidProfileRequests(t *testing.T) { func TestRecordBadRequests(t *testing.T) { m := createMetricsForTesting() - m.RecordBadRequests(models.EndpointV25, int(nbr.AllPartnerThrottled)) + m.RecordBadRequests(models.EndpointV25, "5890", int(nbr.AllPartnerThrottled)) expectedCount := float64(1) assertCounterVecValue(t, "", "bad_requests", m.endpointBadRequest, expectedCount, prometheus.Labels{ + pubIDLabel: "5890", endpointLabel: models.EndpointV25, nbrLabel: strconv.Itoa(int(nbr.AllPartnerThrottled)), }) diff --git a/modules/pubmatic/openwrap/metrics/stats/tcp_stats.go b/modules/pubmatic/openwrap/metrics/stats/tcp_stats.go index 82599a54489..88275c5bde3 100644 --- a/modules/pubmatic/openwrap/metrics/stats/tcp_stats.go +++ b/modules/pubmatic/openwrap/metrics/stats/tcp_stats.go @@ -84,11 +84,6 @@ func (st *StatsTCP) RecordPublisherInvalidProfileRequests(endpoint, publisher, p } } -func (st *StatsTCP) RecordPublisherInvalidProfileImpressions(publisher, profileID string, impCount int) { - st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyPublisherInvProfileImpressions], publisher, profileID), impCount) - //TODO @viral ;previously by 1 but now by impCount -} - func (st *StatsTCP) RecordNobidErrPrebidServerRequests(publisher string, nbr int) { st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyNobidErrPrebidServerRequests], publisher), 1) } @@ -123,8 +118,8 @@ func (st *StatsTCP) RecordPublisherResponseTimeStats(publisher string, responseT st.statsClient.PublishStat(fmt.Sprintf(statKeys[statKeyIndex], publisher, "overall"), 1) } -func (st *StatsTCP) RecordPublisherWrapperLoggerFailure(publisher, profileID, versionID string) { - st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyLoggerErrorRequests], publisher, profileID, versionID), 1) +func (st *StatsTCP) RecordPublisherWrapperLoggerFailure(publisher string) { + st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyLoggerErrorRequests], publisher), 1) } func (st *StatsTCP) RecordPrebidTimeoutRequests(publisher, profileID string) { @@ -181,7 +176,7 @@ func (st *StatsTCP) RecordCTVRequests(endpoint string, platform string) { st.statsClient.PublishStat(fmt.Sprintf(statKeys[statsKeyCTVRequests], endpoint, platform), 1) } -func (st *StatsTCP) RecordBadRequests(endpoint string, errorCode int) { +func (st *StatsTCP) RecordBadRequests(endpoint, publisher string, errorCode int) { switch endpoint { case "amp": st.statsClient.PublishStat(statKeys[statsKeyAMPBadRequests], 1) @@ -355,3 +350,5 @@ func (st *StatsTCP) RecordBidRecoveryResponseTime(pubID string, profile string, } func (st *StatsTCP) RecordPrebidAuctionBidResponse(publisher string, partnerName string, bidderCode string, adapterCode string) { } +func (st *StatsTCP) RecordFailedParsingItuneID(pubId, profId string) {} +func (st *StatsTCP) RecordEndpointResponseSize(endpoint string, bodySize float64) {} diff --git a/modules/pubmatic/openwrap/metrics/stats/tcp_stats_test.go b/modules/pubmatic/openwrap/metrics/stats/tcp_stats_test.go index 70f6a3c1a36..66dd5a1c49c 100644 --- a/modules/pubmatic/openwrap/metrics/stats/tcp_stats_test.go +++ b/modules/pubmatic/openwrap/metrics/stats/tcp_stats_test.go @@ -307,25 +307,6 @@ func TestRecordFunctions(t *testing.T) { st.RecordPublisherInvalidProfileRequests("", "5890", "pubmatic") }, }, - { - name: "RecordPublisherInvalidProfileImpressions", - args: args{ - statTCP: &StatsTCP{ - &Client{ - pubChan: make(chan stat, 1), - }, - }, - }, - want: want{ - expectedkeyVal: map[string]int{ - fmt.Sprintf(statKeys[statsKeyPublisherInvProfileImpressions], "5890", "pubmatic"): 10, - }, - channelSize: 1, - }, - callRecord: func(st *StatsTCP) { - st.RecordPublisherInvalidProfileImpressions("5890", "pubmatic", 10) - }, - }, { name: "RecordNobidErrPrebidServerRequests", args: args{ @@ -541,12 +522,12 @@ func TestRecordFunctions(t *testing.T) { }, want: want{ expectedkeyVal: map[string]int{ - fmt.Sprintf(statKeys[statsKeyLoggerErrorRequests], "5890", "1234", "0"): 1, + fmt.Sprintf(statKeys[statsKeyLoggerErrorRequests], "5890"): 1, }, channelSize: 1, }, callRecord: func(st *StatsTCP) { - st.RecordPublisherWrapperLoggerFailure("5890", "1234", "0") + st.RecordPublisherWrapperLoggerFailure("5890") }, }, { @@ -752,13 +733,13 @@ func TestRecordFunctions(t *testing.T) { channelSize: 7, }, callRecord: func(st *StatsTCP) { - st.RecordBadRequests("amp", 1) - st.RecordBadRequests("video", 1) - st.RecordBadRequests("v25", 1) - st.RecordBadRequests("json", 100) - st.RecordBadRequests("openwrap", 200) - st.RecordBadRequests("ortb", 300) - st.RecordBadRequests("vast", 400) + st.RecordBadRequests("amp", "5890", 1) + st.RecordBadRequests("video", "5890", 1) + st.RecordBadRequests("v25", "5890", 1) + st.RecordBadRequests("json", "5890", 100) + st.RecordBadRequests("openwrap", "5890", 200) + st.RecordBadRequests("ortb", "5890", 300) + st.RecordBadRequests("vast", "5890", 400) }, }, { diff --git a/modules/pubmatic/openwrap/models/bidders.go b/modules/pubmatic/openwrap/models/bidders.go index c2ae4801516..e2a58da0db7 100644 --- a/modules/pubmatic/openwrap/models/bidders.go +++ b/modules/pubmatic/openwrap/models/bidders.go @@ -17,6 +17,7 @@ const ( BidderTrustxAlias = "trustx" BidderSynacormediaAlias = "synacormedia" BidderViewDeos = "viewdeos" + BidderAppStockAlias = "appstock" ) const ( diff --git a/modules/pubmatic/openwrap/models/constants.go b/modules/pubmatic/openwrap/models/constants.go index d55973c7bf0..6ce0d9e277b 100755 --- a/modules/pubmatic/openwrap/models/constants.go +++ b/modules/pubmatic/openwrap/models/constants.go @@ -34,6 +34,7 @@ const ( PLATFORM_KEY = "platform" SendAllBidsKey = "sendAllBids" VastUnwrapperEnableKey = "enableVastUnwrapper" + StrictVastModeKey = "strictVastMode" VastUnwrapTrafficPercentKey = "vastUnwrapTrafficPercent" SSTimeoutKey = "ssTimeout" PWC = "awc" @@ -374,21 +375,23 @@ const ( Enabled = "1" // VAST Unwrap - RequestContext = "rctx" - UnwrapCount = "unwrap-count" - UnwrapStatus = "unwrap-status" - Timeout = "Timeout" - UnwrapSucessStatus = "0" - UnwrapTimeout = "unwrap-timeout" - MediaTypeVideo = "video" - ProfileId = "profileID" - VersionId = "versionID" - DisplayId = "DisplayID" - XUserIP = "X-Forwarded-For" - XUserAgent = "X-Device-User-Agent" - CreativeID = "unwrap-ucrid" - PubID = "pub_id" - ImpressionID = "imr_id" + RequestContext = "rctx" + UnwrapCount = "unwrap-count" + UnwrapStatus = "unwrap-status" + Timeout = "Timeout" + UnwrapSucessStatus = "0" + UnwrapEmptyVASTStatus = "4" + UnwrapInvalidVASTStatus = "6" + UnwrapTimeout = "unwrap-timeout" + MediaTypeVideo = "video" + ProfileId = "profileID" + VersionId = "versionID" + DisplayId = "DisplayID" + XUserIP = "X-Forwarded-For" + XUserAgent = "X-Device-User-Agent" + CreativeID = "unwrap-ucrid" + PubID = "pub_id" + ImpressionID = "imr_id" //Constants for new SDK reporting ProfileTypeKey = "type" @@ -609,6 +612,7 @@ const ( MissingSignal = "missing" InvalidSignal = "invalid" AppStoreUrl = "appStoreUrl" + SendBurl = "sendburl" ) // constants for log level diff --git a/modules/pubmatic/openwrap/models/nbr/codes.go b/modules/pubmatic/openwrap/models/nbr/codes.go index 7eafb8a5d09..0a238ec95f2 100644 --- a/modules/pubmatic/openwrap/models/nbr/codes.go +++ b/modules/pubmatic/openwrap/models/nbr/codes.go @@ -4,11 +4,13 @@ import "github.com/prebid/openrtb/v20/openrtb3" // vendor specific NoBidReasons (500+) const ( - LossBidLostToHigherBid openrtb3.NoBidReason = 501 // Response Rejected - Lost to Higher Bid - LossBidLostToDealBid openrtb3.NoBidReason = 502 // Response Rejected - Lost to a Bid for a Deal - RequestBlockedSlotNotMapped openrtb3.NoBidReason = 503 - RequestBlockedPartnerThrottle openrtb3.NoBidReason = 504 - RequestBlockedPartnerFiltered openrtb3.NoBidReason = 505 + LossBidLostToHigherBid openrtb3.NoBidReason = 501 // Response Rejected - Lost to Higher Bid + LossBidLostToDealBid openrtb3.NoBidReason = 502 // Response Rejected - Lost to a Bid for a Deal + RequestBlockedSlotNotMapped openrtb3.NoBidReason = 503 + RequestBlockedPartnerThrottle openrtb3.NoBidReason = 504 + RequestBlockedPartnerFiltered openrtb3.NoBidReason = 505 + LossBidLostInVastUnwrap openrtb3.NoBidReason = 506 + LossBidLostInVastVersionValidation openrtb3.NoBidReason = 507 ) // Openwrap module specific codes @@ -32,4 +34,5 @@ const ( InvalidRedirectURL openrtb3.NoBidReason = 617 InvalidResponseFormat openrtb3.NoBidReason = 618 MissingOWRedirectURL openrtb3.NoBidReason = 619 + ResponseRejectedDSA openrtb3.NoBidReason = 620 // Response Rejected - DSA ) diff --git a/modules/pubmatic/openwrap/models/openwrap.go b/modules/pubmatic/openwrap/models/openwrap.go index 05c088284ea..e43d06ee866 100644 --- a/modules/pubmatic/openwrap/models/openwrap.go +++ b/modules/pubmatic/openwrap/models/openwrap.go @@ -156,6 +156,8 @@ type DeviceCtx struct { IFATypeID *DeviceIFAType Platform DevicePlatform Ext *ExtDevice + ID string + Model string } type ImpCtx struct { diff --git a/modules/pubmatic/openwrap/models/response.go b/modules/pubmatic/openwrap/models/response.go index 438908f8cba..2f535cb7e35 100644 --- a/modules/pubmatic/openwrap/models/response.go +++ b/modules/pubmatic/openwrap/models/response.go @@ -11,23 +11,24 @@ import ( type BidExt struct { openrtb_ext.ExtBid - ErrorCode int `json:"errorCode,omitempty"` - ErrorMsg string `json:"errorMessage,omitempty"` - RefreshInterval int `json:"refreshInterval,omitempty"` - CreativeType string `json:"crtype,omitempty"` - Summary []Summary `json:"summary,omitempty"` - SKAdnetwork json.RawMessage `json:"skadn,omitempty"` - Video *ExtBidVideo `json:"video,omitempty"` - Banner *ExtBidBanner `json:"banner,omitempty"` - DspId int `json:"dspid,omitempty"` - Winner int `json:"winner,omitempty"` - NetECPM float64 `json:"netecpm,omitempty"` - OriginalBidCPM float64 `json:"origbidcpm,omitempty"` - OriginalBidCur string `json:"origbidcur,omitempty"` - OriginalBidCPMUSD float64 `json:"origbidcpmusd,omitempty"` - Nbr *openrtb3.NoBidReason `json:"-"` // Reason for not bidding - Fsc int `json:"fsc,omitempty"` - AdPod *AdpodBidExt `json:"adpod,omitempty"` + ErrorCode int `json:"errorCode,omitempty"` + ErrorMsg string `json:"errorMessage,omitempty"` + RefreshInterval int `json:"refreshInterval,omitempty"` + CreativeType string `json:"crtype,omitempty"` + Summary []Summary `json:"summary,omitempty"` + SKAdnetwork json.RawMessage `json:"skadn,omitempty"` + Video *ExtBidVideo `json:"video,omitempty"` + Banner *ExtBidBanner `json:"banner,omitempty"` + DspId int `json:"dspid,omitempty"` + Winner int `json:"winner,omitempty"` + NetECPM float64 `json:"netecpm,omitempty"` + OriginalBidCPM float64 `json:"origbidcpm,omitempty"` + OriginalBidCur string `json:"origbidcur,omitempty"` + OriginalBidCPMUSD float64 `json:"origbidcpmusd,omitempty"` + Nbr *openrtb3.NoBidReason `json:"-"` // Reason for not bidding + Fsc int `json:"fsc,omitempty"` + AdPod *AdpodBidExt `json:"adpod,omitempty"` + MultiBidMultiFloorValue float64 `json:"mbmfv,omitempty"` } type AdpodBidExt struct { diff --git a/modules/pubmatic/openwrap/models/utils.go b/modules/pubmatic/openwrap/models/utils.go index 4775b93426f..96ec618c95a 100644 --- a/modules/pubmatic/openwrap/models/utils.go +++ b/modules/pubmatic/openwrap/models/utils.go @@ -377,6 +377,13 @@ func GetBidLevelFloorsDetails(bidExt BidExt, impCtx ImpCtx, var floorCurrency string frv = NotSet + //Set the fv from bid.ext.mbmf if it is set + if bidExt.MultiBidMultiFloorValue > 0 { + fv = RoundToTwoDigit(bidExt.MultiBidMultiFloorValue) + frv = RoundToTwoDigit(bidExt.MultiBidMultiFloorValue) + return + } + if bidExt.Prebid != nil && bidExt.Prebid.Floors != nil { floorCurrency = bidExt.Prebid.Floors.FloorCurrency fv = RoundToTwoDigit(bidExt.Prebid.Floors.FloorValue) diff --git a/modules/pubmatic/openwrap/models/utils_test.go b/modules/pubmatic/openwrap/models/utils_test.go index afff46c2bf4..883b78236cb 100644 --- a/modules/pubmatic/openwrap/models/utils_test.go +++ b/modules/pubmatic/openwrap/models/utils_test.go @@ -954,6 +954,18 @@ func TestGetBidLevelFloorsDetails(t *testing.T) { frv: 0, }, }, + { + name: "floor_values_set_from_bidExt_mbmfv_for_applovinmax", + args: args{ + bidExt: BidExt{ + MultiBidMultiFloorValue: 5.0, + }, + }, + want: want{ + fv: 5.0, + frv: 5.0, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/modules/pubmatic/openwrap/nonbids.go b/modules/pubmatic/openwrap/nonbids.go index b0d13c94627..888d453c5e1 100644 --- a/modules/pubmatic/openwrap/nonbids.go +++ b/modules/pubmatic/openwrap/nonbids.go @@ -30,9 +30,31 @@ func prepareSeatNonBids(rctx models.RequestCtx) openrtb_ext.NonBidCollection { seatNonBid.AddBid(nonBid, bidder) } } + + // Update seat-non-bids with default-bids for the web-s2s endpoint + // In other endpoints, default bids are added to response.seatbid, but for web-s2s, we must return a vanilla prebid response. + if rctx.Endpoint == models.EndpointWebS2S { + updateSeatNonBidsFromDefaultBids(rctx, &seatNonBid) + } + return seatNonBid } +func updateSeatNonBidsFromDefaultBids(rctx models.RequestCtx, seatNonBid *openrtb_ext.NonBidCollection) { + for impID, defaultBid := range rctx.DefaultBids { + for seat, bids := range defaultBid { + for _, bid := range bids { + if rctx.ImpBidCtx != nil && rctx.ImpBidCtx[impID].BidCtx != nil && rctx.ImpBidCtx[impID].BidCtx[bid.ID].Nbr != nil { + nbr := rctx.ImpBidCtx[impID].BidCtx[bid.ID].Nbr + nonBid := openrtb_ext.NewNonBid(openrtb_ext.NonBidParams{Bid: &openrtb2.Bid{ImpID: impID}, NonBidReason: int(*nbr)}) + seatNonBid.AddBid(nonBid, seat) + } + + } + } + } +} + // addSeatNonBidsInResponseExt adds the rctx.SeatNonBids in the response-ext func addSeatNonBidsInResponseExt(rctx models.RequestCtx, responseExt *openrtb_ext.ExtBidResponse) { if len(rctx.SeatNonBids) == 0 { diff --git a/modules/pubmatic/openwrap/nonbids_test.go b/modules/pubmatic/openwrap/nonbids_test.go index 0d00ea2a0b9..a3d0b7db039 100644 --- a/modules/pubmatic/openwrap/nonbids_test.go +++ b/modules/pubmatic/openwrap/nonbids_test.go @@ -3,7 +3,10 @@ package openwrap import ( "testing" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v2/exchange" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/nbr" "github.com/prebid/prebid-server/v2/openrtb_ext" @@ -130,6 +133,44 @@ func TestPrepareSeatNonBids(t *testing.T) { }, }), }, + { + name: "seatnonbid_should_be_updated_from_defaultbids_from_webs2s_endpoint", + args: args{ + rctx: models.RequestCtx{ + Endpoint: models.EndpointWebS2S, + DefaultBids: map[string]map[string][]openrtb2.Bid{ + "imp1": { + "pubmatic": { + { + ID: "bid-id-1", + ImpID: "imp1", + Ext: []byte(`{}`), + }, + }, + }, + }, + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + Nbr: exchange.ErrorGeneral.Ptr(), + }, + }, + }, + }, + }, + }, + }, + seatNonBids: getNonBids(map[string][]openrtb_ext.NonBidParams{ + "pubmatic": { + { + Bid: &openrtb2.Bid{ImpID: "imp1"}, + NonBidReason: int(exchange.ErrorGeneral), + }, + }, + }), + }, } for _, tt := range tests { @@ -832,3 +873,392 @@ func getNonBids(bidParamsMap map[string][]openrtb_ext.NonBidParams) openrtb_ext. } return nonBids } + +func TestUpdateSeatNonBidsFromDefaultBids(t *testing.T) { + type args struct { + rctx models.RequestCtx + seatNonBid *openrtb_ext.NonBidCollection + } + tests := []struct { + name string + args args + wantSeatNonBid []openrtb_ext.SeatNonBid + }{ + { + name: "no default bids", + args: args{ + rctx: models.RequestCtx{ + DefaultBids: nil, + }, + seatNonBid: &openrtb_ext.NonBidCollection{}, + }, + wantSeatNonBid: nil, + }, + { + name: "imp not present in impbidctx for default bid", + args: args{ + rctx: models.RequestCtx{ + DefaultBids: map[string]map[string][]openrtb2.Bid{ + "imp1": { + "pubmatic": { + { + ID: "bid-id-1", + ImpID: "imp1", + Ext: []byte(`{}`), + }, + }, + }, + }, + ImpBidCtx: map[string]models.ImpCtx{ + "imp2": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + Nbr: exchange.ErrorGeneral.Ptr(), + }, + }, + }, + }, + }, + }, + seatNonBid: &openrtb_ext.NonBidCollection{}, + }, + }, + { + name: "bid absent in impbidctx for default bid", + args: args{ + rctx: models.RequestCtx{ + DefaultBids: map[string]map[string][]openrtb2.Bid{ + "imp1": { + "pubmatic": { + { + ID: "bid-id-1", + ImpID: "imp1", + Ext: []byte(`{}`), + }, + }, + }, + }, + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-2": { + BidExt: models.BidExt{ + Nbr: exchange.ErrorGeneral.Ptr(), + }, + }, + }, + }, + }, + }, + seatNonBid: &openrtb_ext.NonBidCollection{}, + }, + }, + { + name: "default bid with no non-bid reason", + args: args{ + rctx: models.RequestCtx{ + DefaultBids: map[string]map[string][]openrtb2.Bid{ + "imp1": { + "pubmatic": { + { + ID: "bid-id-1", + ImpID: "imp1", + Ext: []byte(`{}`), + }, + }, + }, + }, + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + Nbr: nil, + }, + }, + }, + }, + }, + }, + seatNonBid: &openrtb_ext.NonBidCollection{}, + }, + wantSeatNonBid: nil, + }, + { + name: "singal default bid", + args: args{ + rctx: models.RequestCtx{ + DefaultBids: map[string]map[string][]openrtb2.Bid{ + "imp1": { + "pubmatic": { + { + ID: "bid-id-1", + ImpID: "imp1", + Ext: []byte(`{}`), + }, + }, + }, + }, + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + Nbr: exchange.ErrorGeneral.Ptr(), + }, + }, + }, + }, + }, + }, + seatNonBid: &openrtb_ext.NonBidCollection{}, + }, + wantSeatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "pubmatic", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: 100, + }, + }, + }, + }, + }, + { + name: "multiple default bids for same imp", + args: args{ + rctx: models.RequestCtx{ + DefaultBids: map[string]map[string][]openrtb2.Bid{ + "imp1": { + "pubmatic": { + { + ID: "bid-id-1", + ImpID: "imp1", + }, + }, + "appnexus": { + { + ID: "bid-id-2", + ImpID: "imp1", + }, + }, + "rubicon": { + { + ID: "bid-id-3", + ImpID: "imp1", + }, + }, + }, + }, + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + Nbr: exchange.ErrorGeneral.Ptr(), + }, + }, + "bid-id-2": { + BidExt: models.BidExt{ + Nbr: exchange.ErrorTimeout.Ptr(), + }, + }, + "bid-id-3": { + BidExt: models.BidExt{ + Nbr: exchange.ErrorBidderUnreachable.Ptr(), + }, + }, + }, + }, + }, + }, + seatNonBid: &openrtb_ext.NonBidCollection{}, + }, + wantSeatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "pubmatic", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: 100, + }, + }, + }, + { + Seat: "appnexus", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: 101, + }, + }, + }, + { + Seat: "rubicon", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: 103, + }, + }, + }, + }, + }, + { + name: "multiple default bids for different imp", + args: args{ + rctx: models.RequestCtx{ + DefaultBids: map[string]map[string][]openrtb2.Bid{ + "imp1": { + "pubmatic": { + { + ID: "bid-id-1", + ImpID: "imp1", + Ext: []byte(`{}`), + }, + }, + "appnexus": { + { + ID: "bid-id-2", + ImpID: "imp1", + Ext: []byte(`{}`), + }, + }, + "rubicon": { + { + ID: "bid-id-3", + ImpID: "imp1", + Ext: []byte(`{}`), + }, + }, + }, + "imp2": { + "pubmatic": { + { + ID: "bid-id-4", + ImpID: "imp2", + Ext: []byte(`{}`), + }, + }, + "appnexus": { + { + ID: "bid-id-5", + ImpID: "imp2", + Ext: []byte(`{}`), + }, + }, + "rubicon": { + { + ID: "bid-id-6", + ImpID: "imp2", + Ext: []byte(`{}`), + }, + }, + }, + }, + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + Nbr: exchange.ErrorGeneral.Ptr(), + }, + }, + "bid-id-2": { + BidExt: models.BidExt{ + Nbr: exchange.ErrorTimeout.Ptr(), + }, + }, + "bid-id-3": { + BidExt: models.BidExt{ + Nbr: exchange.ErrorBidderUnreachable.Ptr(), + }, + }, + }, + }, + "imp2": { + BidCtx: map[string]models.BidCtx{ + "bid-id-4": { + BidExt: models.BidExt{ + Nbr: exchange.ErrorGeneral.Ptr(), + }, + }, + "bid-id-5": { + BidExt: models.BidExt{ + Nbr: exchange.ErrorTimeout.Ptr(), + }, + }, + "bid-id-6": { + BidExt: models.BidExt{ + Nbr: exchange.ErrorBidderUnreachable.Ptr(), + }, + }, + }, + }, + }, + }, + seatNonBid: &openrtb_ext.NonBidCollection{}, + }, + wantSeatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "pubmatic", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: 100, + }, + { + ImpId: "imp2", + StatusCode: 100, + }, + }, + }, + { + Seat: "appnexus", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: 101, + }, + { + ImpId: "imp2", + StatusCode: 101, + }, + }, + }, + { + Seat: "rubicon", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: 103, + }, + { + ImpId: "imp2", + StatusCode: 103, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + updateSeatNonBidsFromDefaultBids(tt.args.rctx, tt.args.seatNonBid) + gotSetaNonBid := tt.args.seatNonBid.Get() + + cmp.Equal(tt.wantSeatNonBid, gotSetaNonBid, + cmpopts.SortSlices(func(a, b openrtb_ext.SeatNonBid) bool { + return a.Seat < b.Seat + }), + cmpopts.SortSlices(sortNonBids), + ) + }) + } +} + +func sortNonBids(a, b openrtb_ext.NonBid) bool { + return a.ImpId < b.ImpId +} diff --git a/modules/pubmatic/openwrap/openwrap.go b/modules/pubmatic/openwrap/openwrap.go index fe88c71e94a..e4fa5fa8c21 100644 --- a/modules/pubmatic/openwrap/openwrap.go +++ b/modules/pubmatic/openwrap/openwrap.go @@ -28,6 +28,7 @@ import ( "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/profilemetadata" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/publisherfeature" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/unwrap" + "github.com/prebid/prebid-server/v2/util/uuidutil" ) const ( @@ -43,6 +44,7 @@ type OpenWrap struct { pubFeatures publisherfeature.Feature unwrap unwrap.Unwrap profileMetaData profilemetadata.ProfileMetaData + uuidGenerator uuidutil.UUIDGenerator } var ow *OpenWrap @@ -125,6 +127,7 @@ func initOpenWrap(rawCfg json.RawMessage, moduleDeps moduledeps.ModuleDeps) (Ope pubFeatures: pubFeatures, unwrap: uw, profileMetaData: profileMetaData, + uuidGenerator: uuidutil.UUIDRandomGenerator{}, } }) diff --git a/modules/pubmatic/openwrap/tracker/create.go b/modules/pubmatic/openwrap/tracker/create.go index 9ed7bae433c..8354fd7030a 100644 --- a/modules/pubmatic/openwrap/tracker/create.go +++ b/modules/pubmatic/openwrap/tracker/create.go @@ -115,7 +115,6 @@ func createTrackers(rctx models.RequestCtx, trackers map[string]models.OWTracker bidId = bidExt.Prebid.BidId } if bidExt.Prebid.Meta != nil && len(bidExt.Prebid.Meta.AdapterCode) != 0 && seatBid.Seat != bidExt.Prebid.Meta.AdapterCode { - partnerID = bidExt.Prebid.Meta.AdapterCode if aliasSeat, ok := rctx.PrebidBidderCode[partnerID]; ok { if bidderMeta, ok := impCtx.Bidders[aliasSeat]; ok { diff --git a/modules/pubmatic/openwrap/tracker/create_test.go b/modules/pubmatic/openwrap/tracker/create_test.go index e49f4865bc9..6b899335acd 100644 --- a/modules/pubmatic/openwrap/tracker/create_test.go +++ b/modules/pubmatic/openwrap/tracker/create_test.go @@ -196,7 +196,7 @@ func Test_createTrackers(t *testing.T) { rctx: func() models.RequestCtx { testRctx := rctx testRctx.StartTime = startTime - testRctx.PrebidBidderCode["pubmatic"] = "pubmatic2" + testRctx.PrebidBidderCode["pubmatic2"] = "pubmatic" return testRctx }(), bidResponse: &openrtb2.BidResponse{ diff --git a/modules/pubmatic/openwrap/tracker/video.go b/modules/pubmatic/openwrap/tracker/video.go index f2542f9bf44..00037b1e863 100644 --- a/modules/pubmatic/openwrap/tracker/video.go +++ b/modules/pubmatic/openwrap/tracker/video.go @@ -128,7 +128,7 @@ func InjectPricingNodeInVAST(parent *etree.Element, price float64, model string, func updatePricingNode(node *etree.Element, price float64, model string, currency string) { //Update Price - + node.Child = nil node.SetText(fmt.Sprintf("%v", price)) //Update Pricing.Model diff --git a/modules/pubmatic/openwrap/tracker/video_test.go b/modules/pubmatic/openwrap/tracker/video_test.go index b8629f1afc9..61368cd6112 100644 --- a/modules/pubmatic/openwrap/tracker/video_test.go +++ b/modules/pubmatic/openwrap/tracker/video_test.go @@ -289,7 +289,7 @@ func TestInjectVideoCreativeTrackers(t *testing.T) { }, }, }, - wantAdm: ` `, + wantAdm: ``, wantErr: false, }, { @@ -449,7 +449,7 @@ func TestInjectVideoCreativeTrackers(t *testing.T) { }, }, wantBurl: "Tracker URL&owsspburl=https%3A%2F%2Fburl.com", - wantAdm: ` `, + wantAdm: ``, wantErr: false, }, } @@ -701,6 +701,26 @@ func Test_updatePricingNode(t *testing.T) { }, want: ``, }, + { + name: "adding_space_in_price", + args: args{ + doc: getXMLDocument(` 4.5 `), + price: 1.2, + model: "", + currency: "", + }, + want: ``, + }, + { + name: "adding_space_in_price_with_cdata", + args: args{ + doc: getXMLDocument(` `), + price: 1.2, + model: "", + currency: "", + }, + want: ``, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/modules/pubmatic/openwrap/unwrap/unwrap.go b/modules/pubmatic/openwrap/unwrap/unwrap.go index ef002929116..24c5b24ba38 100644 --- a/modules/pubmatic/openwrap/unwrap/unwrap.go +++ b/modules/pubmatic/openwrap/unwrap/unwrap.go @@ -40,21 +40,17 @@ func NewUnwrap(Endpoint string, DefaultTime int, handler http.HandlerFunc, Metri } -func (uw Unwrap) Unwrap(accountID, bidder string, bid *adapters.TypedBid, userAgent, ip string, isStatsEnabled bool) { +func (uw Unwrap) Unwrap(bid *adapters.TypedBid, accountID, bidder, userAgent, ip string) (unwrapStatus string) { startTime := time.Now() var wrapperCnt int64 - var respStatus string - if bid == nil || bid.Bid == nil || bid.Bid.AdM == "" { - return - } defer func() { if r := recover(); r != nil { glog.Errorf("AdM:[%s] Error:[%v] stacktrace:[%s]", bid.Bid.AdM, r, string(debug.Stack())) } respTime := time.Since(startTime) uw.metricEngine.RecordUnwrapRequestTime(accountID, bidder, respTime) - uw.metricEngine.RecordUnwrapRequestStatus(accountID, bidder, respStatus) - if respStatus == "0" { + uw.metricEngine.RecordUnwrapRequestStatus(accountID, bidder, unwrapStatus) + if unwrapStatus == "0" { uw.metricEngine.RecordUnwrapWrapperCount(accountID, bidder, strconv.Itoa(int(wrapperCnt))) uw.metricEngine.RecordUnwrapRespTime(accountID, strconv.Itoa(int(wrapperCnt)), respTime) } @@ -76,13 +72,14 @@ func (uw Unwrap) Unwrap(accountID, bidder string, bid *adapters.TypedBid, userAg httpReq.Header = headers httpResp := NewCustomRecorder() uw.unwrapRequest(httpResp, httpReq) - respStatus = httpResp.Header().Get(models.UnwrapStatus) + unwrapStatus = httpResp.Header().Get(models.UnwrapStatus) wrapperCnt, _ = strconv.ParseInt(httpResp.Header().Get(models.UnwrapCount), 10, 0) - if !isStatsEnabled && httpResp.Code == http.StatusOK && respStatus == models.UnwrapSucessStatus { + if httpResp.Code == http.StatusOK && unwrapStatus == models.UnwrapSucessStatus { respBody := httpResp.Body.Bytes() bid.Bid.AdM = string(respBody) } - glog.V(models.LogLevelDebug).Infof("[VAST_UNWRAPPER] pubid:[%v] bidder:[%v] impid:[%v] bidid:[%v] status_code:[%v] wrapper_cnt:[%v] httpRespCode= [%v] statsEnabled:[%v]", - accountID, bidder, bid.Bid.ImpID, bid.Bid.ID, respStatus, wrapperCnt, httpResp.Code, isStatsEnabled) + glog.V(models.LogLevelDebug).Infof("[VAST_UNWRAPPER] pubid:[%v] bidder:[%v] impid:[%v] bidid:[%v] status_code:[%v] wrapper_cnt:[%v] httpRespCode= [%v]", + accountID, bidder, bid.Bid.ImpID, bid.Bid.ID, unwrapStatus, wrapperCnt, httpResp.Code) + return unwrapStatus } diff --git a/modules/pubmatic/openwrap/unwrap/unwrap_test.go b/modules/pubmatic/openwrap/unwrap/unwrap_test.go index a7f3e7be050..763ba071c8c 100644 --- a/modules/pubmatic/openwrap/unwrap/unwrap_test.go +++ b/modules/pubmatic/openwrap/unwrap/unwrap_test.go @@ -8,7 +8,6 @@ import ( "github.com/golang/mock/gomock" "github.com/prebid/openrtb/v20/openrtb2" "github.com/prebid/prebid-server/v2/adapters" - metrics "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics" mock_metrics "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics/mock" "github.com/stretchr/testify/assert" ) @@ -18,35 +17,32 @@ var invalidVastXMLAdM = "PubMaticAcudeo CompatibleVAST 2.0 Instream Test 1VAST 2.0 Instream Test 1https://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&er=[ERRORCODE]https://track.dsptracker.com?p=1234&er=[ERRORCODE]https://aktrack.pubmatic.com/AdServer/AdDisplayTrackerServlet?operId=1&pubId=64195&siteId=47105&adId=1405154&adType=13&adServerId=243&kefact=1.000000&kaxefact=1.000000&kadNetFrequecy=0&kadwidth=0&kadheight=0&kadsizeid=97&kltstamp=1536933242&indirectAdId=0&adServerOptimizerId=2&ranreq=0.05969169352174375&kpbmtpfact=11.000000&dcId=1&tldId=0&passback=0&svr=ktk57&ekefact=er2bW2sDAwCra06ACbsIQySn5nqBtYsTl8fy5lupAexh37D_&ekaxefact=er2bW4EDAwB_LQpJJ23Fq0DcNC-NSAFXdpSQC8XBk_S33_Fa&ekpbmtpfact=er2bW5MDAwDJHdBnLBt5IrRuh7x0oqp_tjIALv_VvSQDAl6R&crID=m:1_x:3_y:3_p:11_va:3&lpu=ae.com&ucrid=678722001014421372&campaignId=16774&creativeId=0&pctr=0.000000&wDSPByrId=511&wDspId=27&wbId=0&wrId=0&wAdvID=3170&isRTB=1&rtbId=EBCA079F-8D7C-45B8-B733-92951F670AA1&imprId=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&oid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&pageURL=http%253A%252F%252Fowsdk-stagingams.pubmatic.com%253A8443%252Fvast-validator%252F%2523&sec=1&pmc=1https://DspImpressionTracker.com/https://mytracking.com/linear/closehttps://mytracking.com/linear/skiphttps://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&e=1https://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&e=2https://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&e=3https://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&e=4https://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&e=5https://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&e=600:00:04https://www.automationtester.inhttps://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&e=99https://stagingams.pubmatic.com:8443/openwrap/media/pubmatic.mp4https://stagingams.pubmatic.com:8443/openwrap/media/pubmatic.mp4https://stagingams.pubmatic.com:8443/openwrap/media/mp4-sample-3.mp4" func TestUnwrap_Unwrap(t *testing.T) { - ctrl := gomock.NewController(t) defer ctrl.Finish() mockMetricsEngine := mock_metrics.NewMockMetricsEngine(ctrl) type fields struct { - endpoint string - defaultTime int - metricEngine metrics.MetricsEngine - unwrapRequest http.HandlerFunc + endpoint string } type args struct { - accountID string - bidder string - bid *adapters.TypedBid - userAgent string - ip string - isStatsEnabled bool + accountID string + bidder string + bid *adapters.TypedBid + userAgent string + ip string } tests := []struct { - name string - fields fields - args args - setup func() - mockHandler http.HandlerFunc - expectedAdm string + name string + fields fields + args args + setup func() + mockHandler http.HandlerFunc + expectedAdm string + expectedUnwrapStatus string }{ { - name: "Stats enabled only", + name: "Unwrap enabled with valid adm", + fields: fields{endpoint: "http://localhost:8001/unwrap"}, args: args{ accountID: "5890", bidder: "pubmatic", @@ -55,9 +51,8 @@ func TestUnwrap_Unwrap(t *testing.T) { AdM: vastXMLAdM, }, }, - userAgent: "UA", - ip: "10.12.13.14", - isStatsEnabled: true, + userAgent: "UA", + ip: "10.12.13.14", }, setup: func() { mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", "0") @@ -69,40 +64,39 @@ func TestUnwrap_Unwrap(t *testing.T) { w.Header().Add("unwrap-status", "0") w.Header().Add("unwrap-count", "1") w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(vastXMLAdM)) + _, _ = w.Write([]byte(inlineXMLAdM)) }), - expectedAdm: vastXMLAdM, + expectedAdm: inlineXMLAdM, + expectedUnwrapStatus: "0", }, { - name: "Unwrap enabled with valid adm", + name: "Unwrap enabled with invalid adm", + fields: fields{endpoint: "http://localhost:8001/unwrap"}, args: args{ accountID: "5890", bidder: "pubmatic", bid: &adapters.TypedBid{ Bid: &openrtb2.Bid{ - AdM: vastXMLAdM, + AdM: invalidVastXMLAdM, }, }, - userAgent: "UA", - ip: "10.12.13.14", - isStatsEnabled: false, + userAgent: "UA", + ip: "10.12.13.14", }, setup: func() { - mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", "0") - mockMetricsEngine.EXPECT().RecordUnwrapWrapperCount("5890", "pubmatic", "1") + mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", "1") mockMetricsEngine.EXPECT().RecordUnwrapRequestTime("5890", "pubmatic", gomock.Any()) - mockMetricsEngine.EXPECT().RecordUnwrapRespTime("5890", "1", gomock.Any()) }, mockHandler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Add("unwrap-status", "0") - w.Header().Add("unwrap-count", "1") - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(inlineXMLAdM)) + w.Header().Add("unwrap-status", "1") + w.WriteHeader(http.StatusNoContent) }), - expectedAdm: inlineXMLAdM, + expectedAdm: invalidVastXMLAdM, + expectedUnwrapStatus: "1", }, { - name: "Unwrap enabled with invalid adm", + name: "Error while forming the HTTPRequest for unwrap process", + fields: fields{endpoint: ":"}, args: args{ accountID: "5890", bidder: "pubmatic", @@ -111,33 +105,29 @@ func TestUnwrap_Unwrap(t *testing.T) { AdM: invalidVastXMLAdM, }, }, - userAgent: "UA", - ip: "10.12.13.14", - isStatsEnabled: false, + userAgent: "UA", + ip: "10.12.13.14", }, setup: func() { - mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", "1") + mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", "") mockMetricsEngine.EXPECT().RecordUnwrapRequestTime("5890", "pubmatic", gomock.Any()) }, - mockHandler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Add("unwrap-status", "1") - w.WriteHeader(http.StatusNoContent) - }), - expectedAdm: invalidVastXMLAdM, + mockHandler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {}), + expectedAdm: invalidVastXMLAdM, + expectedUnwrapStatus: "", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if tt.setup != nil { tt.setup() } - uw := NewUnwrap("http://localhost:8001/unwrap", 200, tt.mockHandler, mockMetricsEngine) - uw.Unwrap(tt.args.accountID, tt.args.bidder, tt.args.bid, tt.args.userAgent, tt.args.ip, tt.args.isStatsEnabled) - if !tt.args.isStatsEnabled && strings.Compare(tt.args.bid.Bid.AdM, tt.expectedAdm) != 0 { + uw := NewUnwrap(tt.fields.endpoint, 200, tt.mockHandler, mockMetricsEngine) + unwrapStatus := uw.Unwrap(tt.args.bid, tt.args.accountID, tt.args.bidder, tt.args.userAgent, tt.args.ip) + if strings.Compare(tt.args.bid.Bid.AdM, tt.expectedAdm) != 0 { assert.Equal(t, inlineXMLAdM, tt.args.bid.Bid.AdM, "AdM is not updated correctly after unwrap ") - } + assert.Equal(t, tt.expectedUnwrapStatus, unwrapStatus, "mismatched unwrap status") }) } } diff --git a/modules/pubmatic/openwrap/util.go b/modules/pubmatic/openwrap/util.go index d0976403770..6bbd24f6523 100644 --- a/modules/pubmatic/openwrap/util.go +++ b/modules/pubmatic/openwrap/util.go @@ -10,7 +10,10 @@ import ( "strconv" "strings" + "golang.org/x/exp/slices" // Use standard library in next prebid upgrade + "github.com/buger/jsonparser" + "github.com/golang/glog" "github.com/prebid/openrtb/v20/adcom1" "github.com/prebid/openrtb/v20/openrtb2" "github.com/prebid/openrtb/v20/openrtb3" @@ -23,6 +26,8 @@ import ( "github.com/prebid/prebid-server/v2/openrtb_ext" ) +var uidRegexp = regexp.MustCompile(`^(UID2|ID5|BGID|euid|PAIRID|IDL|connectid|firstid|utiq):`) + var ( widthRegEx *regexp.Regexp heightRegEx *regexp.Regexp @@ -53,6 +58,10 @@ const ( test = "_test" ) +var ( + protocols = []adcom1.MediaCreativeSubtype{adcom1.CreativeVAST30, adcom1.CreativeVAST30Wrapper, adcom1.CreativeVAST40, adcom1.CreativeVAST40Wrapper} +) + func init() { widthRegEx = regexp.MustCompile(models.MACRO_WIDTH) heightRegEx = regexp.MustCompile(models.MACRO_HEIGHT) @@ -486,3 +495,71 @@ func getStringValueFromRequest(request []byte, key []string) (string, bool, erro } return string(val), true, nil } + +func ValidateEIDs(eids []openrtb2.EID) []openrtb2.EID { + validEIDs := make([]openrtb2.EID, 0, len(eids)) + for _, eid := range eids { + validUIDs := make([]openrtb2.UID, 0, len(eid.UIDs)) + for _, uid := range eid.UIDs { + uid.ID = uidRegexp.ReplaceAllString(uid.ID, "") + if uid.ID != "" { + validUIDs = append(validUIDs, uid) + } + } + if len(validUIDs) > 0 { + eid.UIDs = validUIDs + validEIDs = append(validEIDs, eid) + } + } + return validEIDs +} + +func UpdateUserExtWithValidValues(user *openrtb2.User) { + if user == nil { + return + } + + if user.Ext != nil { + var userExt openrtb_ext.ExtUser + err := json.Unmarshal(user.Ext, &userExt) + if err != nil { + return + } + if userExt.SessionDuration < 0 { + glog.Warningf("Invalid sessionduration value: %v. Only positive values are allowed.", userExt.SessionDuration) + userExt.SessionDuration = 0 + } + + if userExt.ImpDepth < 0 { + glog.Warningf("Invalid impdepth value: %v. Only positive values are allowed.", userExt.ImpDepth) + userExt.ImpDepth = 0 + } + eids := ValidateEIDs(userExt.Eids) + userExt.Eids = nil + if len(eids) > 0 { + userExt.Eids = eids + } + + userExtjson, err := json.Marshal(userExt) + if err == nil { + user.Ext = userExtjson + } + } + + if len(user.EIDs) > 0 { + eids := ValidateEIDs(user.EIDs) + user.EIDs = nil + if len(eids) > 0 { + user.EIDs = eids + } + } +} + +func UpdateImpProtocols(impProtocols []adcom1.MediaCreativeSubtype) []adcom1.MediaCreativeSubtype { + for _, protocol := range protocols { + if !slices.Contains(impProtocols, protocol) { + impProtocols = append(impProtocols, protocol) + } + } + return impProtocols +} diff --git a/modules/pubmatic/openwrap/util_test.go b/modules/pubmatic/openwrap/util_test.go index 89c487d7ccb..ff1a8b02b57 100644 --- a/modules/pubmatic/openwrap/util_test.go +++ b/modules/pubmatic/openwrap/util_test.go @@ -1782,3 +1782,229 @@ func TestGetStringValueFromRequest(t *testing.T) { assert.Equal(t, test.expectedError, err, "getStringValueFromRequest should return expected error for test case: %s", test.description) } } + +func TestUpdateUserExtWithValidValues(t *testing.T) { + type args struct { + user *openrtb2.User + } + tests := []struct { + name string + args args + want *openrtb2.User + }{ + { + name: "test_valid_user_eids", + args: args{ + user: &openrtb2.User{ + EIDs: []openrtb2.EID{ + { + Source: "uidapi.com", + UIDs: []openrtb2.UID{ + { + ID: "UID2:testUID", + }, + }, + }, + }, + }, + }, + want: &openrtb2.User{ + EIDs: []openrtb2.EID{ + { + Source: "uidapi.com", + UIDs: []openrtb2.UID{ + { + ID: "testUID", + }, + }, + }, + }, + }, + }, + { + name: "test_user_eids_and_user_ext_eids", + args: args{ + user: &openrtb2.User{ + Ext: json.RawMessage(`{"eids":[{"source":"uidapi.com","uids":[{"id":"UID2:testUID"},{"id":"testUID2"}]},{"source":"euid.eu","uids":[{"id":"testeuid"}]},{"source":"liveramp.com","uids":[{"id":""}]}]}`), + EIDs: []openrtb2.EID{ + { + Source: "uidapi.com", + UIDs: []openrtb2.UID{ + { + ID: "UID2:testUID", + }, + }, + }, + }, + }, + }, + want: &openrtb2.User{ + Ext: json.RawMessage(`{"eids":[{"source":"uidapi.com","uids":[{"id":"testUID"},{"id":"testUID2"}]},{"source":"euid.eu","uids":[{"id":"testeuid"}]}]}`), + EIDs: []openrtb2.EID{ + { + Source: "uidapi.com", + UIDs: []openrtb2.UID{ + { + ID: "testUID", + }, + }, + }, + }, + }, + }, + { + name: "test_user_ext_eids", + args: args{ + user: &openrtb2.User{ + Ext: json.RawMessage(`{"eids":[{"source":"uidapi.com","uids":[{"id":"UID2:testUID"},{"id":"testUID2"}]},{"source":"euid.eu","uids":[{"id":"testeuid"}]},{"source":"liveramp.com","uids":[{"id":""}]}]}`), + }, + }, + want: &openrtb2.User{ + Ext: json.RawMessage(`{"eids":[{"source":"uidapi.com","uids":[{"id":"testUID"},{"id":"testUID2"}]},{"source":"euid.eu","uids":[{"id":"testeuid"}]}]}`), + }, + }, + { + name: "test_user_ext_eids_invalid", + args: args{ + user: &openrtb2.User{ + Ext: json.RawMessage(`{"eids":[{"source":"uidapi.com","uids":[{"id":"UID2:"},{"id":""}]},{"source":"euid.eu","uids":[{"id":"euid:"}]},{"source":"liveramp.com","uids":[{"id":""}]}]}`), + }, + }, + want: &openrtb2.User{ + Ext: json.RawMessage(`{}`), + }, + }, + { + name: "test_valid_user_eids_invalid", + args: args{ + user: &openrtb2.User{ + EIDs: []openrtb2.EID{ + { + Source: "uidapi.com", + UIDs: []openrtb2.UID{ + { + ID: "UID2:", + }, + }, + }, + }, + }, + }, + want: &openrtb2.User{}, + }, + { + name: "test_valid_user_ext_sessionduration_impdepth", + args: args{ + user: &openrtb2.User{ + Ext: json.RawMessage(`{"sessionduration":40,"impdepth":10}`), + }, + }, + want: &openrtb2.User{ + Ext: json.RawMessage(`{"sessionduration":40,"impdepth":10}`), + }, + }, + { + name: "test_invalid_user_ext_sessionduration_impdepth", + args: args{ + user: &openrtb2.User{ + Ext: json.RawMessage(`{ + "sessionduration": -20, + "impdepth": -10 + }`), + }, + }, + want: &openrtb2.User{ + Ext: json.RawMessage(`{}`), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + UpdateUserExtWithValidValues(tt.args.user) + assert.Equal(t, tt.want, tt.args.user) + }) + } +} + +func TestUpdateImpProtocols(t *testing.T) { + tests := []struct { + name string + impProtocols []adcom1.MediaCreativeSubtype + want []adcom1.MediaCreativeSubtype + }{ + { + name: "Empty_Protocols", + impProtocols: []adcom1.MediaCreativeSubtype{}, + want: []adcom1.MediaCreativeSubtype{ + adcom1.CreativeVAST30, + adcom1.CreativeVAST30Wrapper, + adcom1.CreativeVAST40, + adcom1.CreativeVAST40Wrapper, + }, + }, + { + name: "VAST20_Protocols_Present", + impProtocols: []adcom1.MediaCreativeSubtype{ + adcom1.CreativeVAST20, + }, + want: []adcom1.MediaCreativeSubtype{ + adcom1.CreativeVAST20, + adcom1.CreativeVAST30, + adcom1.CreativeVAST30Wrapper, + adcom1.CreativeVAST40, + adcom1.CreativeVAST40Wrapper, + }, + }, + { + name: "VAST30_Protocols_Present", + impProtocols: []adcom1.MediaCreativeSubtype{ + adcom1.CreativeVAST30, + }, + want: []adcom1.MediaCreativeSubtype{ + adcom1.CreativeVAST30, + adcom1.CreativeVAST30Wrapper, + adcom1.CreativeVAST40, + adcom1.CreativeVAST40Wrapper, + }, + }, + { + name: "All_Protocols_Present", + impProtocols: []adcom1.MediaCreativeSubtype{ + adcom1.CreativeVAST30, + adcom1.CreativeVAST30Wrapper, + adcom1.CreativeVAST40, + adcom1.CreativeVAST40Wrapper, + }, + want: []adcom1.MediaCreativeSubtype{ + adcom1.CreativeVAST30, + adcom1.CreativeVAST30Wrapper, + adcom1.CreativeVAST40, + adcom1.CreativeVAST40Wrapper, + }, + }, + { + name: "Additional_Protocols_Present", + impProtocols: []adcom1.MediaCreativeSubtype{ + adcom1.CreativeVAST30, + adcom1.CreativeVAST30Wrapper, + adcom1.CreativeVAST40, + adcom1.CreativeVAST40Wrapper, + adcom1.CreativeVAST20, + }, + want: []adcom1.MediaCreativeSubtype{ + adcom1.CreativeVAST30, + adcom1.CreativeVAST30Wrapper, + adcom1.CreativeVAST40, + adcom1.CreativeVAST40Wrapper, + adcom1.CreativeVAST20, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := UpdateImpProtocols(tt.impProtocols) + assert.ElementsMatch(t, tt.want, got) + }) + } +} diff --git a/modules/pubmatic/openwrap/wakanda/wakanda.go b/modules/pubmatic/openwrap/wakanda/wakanda.go index 96e39130dd1..8ed29321924 100644 --- a/modules/pubmatic/openwrap/wakanda/wakanda.go +++ b/modules/pubmatic/openwrap/wakanda/wakanda.go @@ -90,3 +90,15 @@ func setCommandHandler() { commandExecutor: &CommandHandler{}, } } + +func TestInstance(pubId string, profileId string) func() { + wakandaRulesMap = &rulesMap{ + rules: make(map[string]*wakandaRule), + } + + key := "PUB:" + pubId + "__PROF:" + profileId + wakandaRulesMap.AddIfNotPresent(key, 2, "DC1") + return func() { + wakandaRulesMap = nil + } +} diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index b9d9d051fb1..2b58eb37acd 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -93,6 +93,7 @@ var coreBidderNames []BidderName = []BidderName{ BidderDeepintent, BidderDefinemedia, BidderDianomi, + BidderDisplayio, BidderEdge226, BidderDmx, BidderDXKulture, @@ -385,6 +386,7 @@ const ( BidderDeepintent BidderName = "deepintent" BidderDefinemedia BidderName = "definemedia" BidderDianomi BidderName = "dianomi" + BidderDisplayio BidderName = "displayio" BidderEdge226 BidderName = "edge226" BidderDmx BidderName = "dmx" BidderDXKulture BidderName = "dxkulture" diff --git a/openrtb_ext/imp_displayio.go b/openrtb_ext/imp_displayio.go new file mode 100644 index 00000000000..bb8c2020276 --- /dev/null +++ b/openrtb_ext/imp_displayio.go @@ -0,0 +1,7 @@ +package openrtb_ext + +type ExtImpDisplayio struct { + PublisherId string `json:"publisherId"` + InventoryId string `json:"inventoryId"` + PlacementId string `json:"placementId"` +} diff --git a/openrtb_ext/imp_pubmatic.go b/openrtb_ext/imp_pubmatic.go index f6c4b74f2bd..28891b959ee 100644 --- a/openrtb_ext/imp_pubmatic.go +++ b/openrtb_ext/imp_pubmatic.go @@ -19,7 +19,6 @@ type ExtImpPubmatic struct { BidViewabilityScore map[string]interface{} `json:"bidViewability,omitempty"` DealTier *DealTier `json:"dealtier,omitempty"` Floors []float64 `json:"floors,omitempty"` - SendBurl bool `json:"sendburl,omitempty"` } // ExtImpPubmaticKeyVal defines the contract for bidrequest.imp[i].ext.prebid.bidder.pubmatic.keywords[i] diff --git a/openrtb_ext/openwrap.go b/openrtb_ext/openwrap.go index 5371c78cf44..6c71f527150 100644 --- a/openrtb_ext/openwrap.go +++ b/openrtb_ext/openwrap.go @@ -43,6 +43,7 @@ type ExtOWRequestPrebid struct { Transparency *TransparencyExt `json:"transparency,omitempty"` KeyVal map[string]interface{} `json:"keyval,omitempty"` TrackerDisabled bool `json:"tracker_disabled,omitempty"` + StrictVastMode bool `json:"strictvastmode,omitempty"` } // ExtCTVBid defines the contract for bidresponse.seatbid.bid[i].ext diff --git a/openrtb_ext/user.go b/openrtb_ext/user.go index cd686a6ee75..85a0a5d047e 100644 --- a/openrtb_ext/user.go +++ b/openrtb_ext/user.go @@ -22,7 +22,9 @@ type ExtUser struct { Eids []openrtb2.EID `json:"eids,omitempty"` - Data json.RawMessage `json:"data,omitempty"` + Data json.RawMessage `json:"data,omitempty"` + SessionDuration int64 `json:"sessionduration,omitempty"` + ImpDepth int64 `json:"impdepth,omitempty"` } // ExtUserPrebid defines the contract for bidrequest.user.ext.prebid diff --git a/static/bidder-info/displayio.yaml b/static/bidder-info/displayio.yaml new file mode 100644 index 00000000000..4b3b28b745a --- /dev/null +++ b/static/bidder-info/displayio.yaml @@ -0,0 +1,16 @@ +endpoint: "https://prebid.display.io/?publisher={{.PublisherID}}" +endpointCompression: gzip +geoscope: + - global +maintainer: + email: contact@display.io +modifyingVastXmlAllowed: true +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video \ No newline at end of file diff --git a/static/bidder-params/displayio.json b/static/bidder-params/displayio.json new file mode 100644 index 00000000000..1a3fe3875d4 --- /dev/null +++ b/static/bidder-params/displayio.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Display.io Adapter Params", + "description": "A schema which validates params accepted by the Display.io adapter", + "type": "object", + "properties": { + "publisherId": { + "type": "string", + "description": "Publisher Id" + }, + "inventoryId": { + "type": "string", + "description": "Inventory Id" + }, + "placementId": { + "type": "string", + "description": "Placement Id" + } + }, + "required": [ + "publisherId", + "inventoryId", + "placementId" + ] +} \ No newline at end of file