diff --git a/adapters/criteo/criteotest/exemplary/simple-native.json b/adapters/criteo/criteotest/exemplary/simple-native.json new file mode 100644 index 00000000000..6181ee17305 --- /dev/null +++ b/adapters/criteo/criteotest/exemplary/simple-native.json @@ -0,0 +1,94 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "native": { + "ver": "1.1", + "request": "{\"ver\":\"1.0\",\"layout\":1,\"adunit\":1,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":60,\"hmin\":60,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":3,\"required\":0,\"data\":{\"type\":2,\"len\":75}},{\"id\":4,\"required\":0,\"data\":{\"type\":6,\"len\":1000}},{\"id\":5,\"required\":0,\"data\":{\"type\":7,\"len\":1000}},{\"id\":6,\"required\":0,\"data\":{\"type\":11,\"len\":1000}}]}" + }, + "ext": { + "bidder": { + "zoneid": 123456 + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://ssp-bidder.criteo.com/openrtb/pbs/auction/request?profile=230", + "body": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "native": { + "ver": "1.1", + "request": "{\"ver\":\"1.0\",\"layout\":1,\"adunit\":1,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":60,\"hmin\":60,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":3,\"required\":0,\"data\":{\"type\":2,\"len\":75}},{\"id\":4,\"required\":0,\"data\":{\"type\":6,\"len\":1000}},{\"id\":5,\"required\":0,\"data\":{\"type\":7,\"len\":1000}},{\"id\":6,\"required\":0,\"data\":{\"type\":11,\"len\":1000}}]}" + }, + "ext": { + "bidder": { + "zoneid": 123456 + } + } + }] + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "seat": "grid", + "bid": [{ + "id": "randomid", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "12345678", + "adm": "{\"ver\":\"1.0\",\"layout\":1,\"adunit\":1,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":60,\"hmin\":60,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":3,\"required\":0,\"data\":{\"type\":2,\"len\":75}},{\"id\":4,\"required\":0,\"data\":{\"type\":6,\"len\":1000}},{\"id\":5,\"required\":0,\"data\":{\"type\":7,\"len\":1000}},{\"id\":6,\"required\":0,\"data\":{\"type\":11,\"len\":1000}}]}", + "ext": { + "prebid": { + "type": "native" + } + }, + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300 + }] + }], + "cur": "USD" + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "randomid", + "impid": "test-imp-id", + "price": 0.5, + "adm": "{\"ver\":\"1.0\",\"layout\":1,\"adunit\":1,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":60,\"hmin\":60,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":3,\"required\":0,\"data\":{\"type\":2,\"len\":75}},{\"id\":4,\"required\":0,\"data\":{\"type\":6,\"len\":1000}},{\"id\":5,\"required\":0,\"data\":{\"type\":7,\"len\":1000}},{\"id\":6,\"required\":0,\"data\":{\"type\":11,\"len\":1000}}]}", + "adid": "12345678", + "cid": "987", + "crid": "12345678", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "native" + } + } + }, + "type": "native" + }] + }] +} 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 84eec48e74a..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 { @@ -264,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) @@ -645,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 @@ -709,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{}) 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 984458017e4..6bf8a8bf06d 100644 --- a/adapters/pubmatic/pubmatic_test.go +++ b/adapters/pubmatic/pubmatic_test.go @@ -659,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) { @@ -996,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 @@ -1550,59 +1103,6 @@ func TestPubmaticAdapter_buildAdapterRequest(t *testing.T) { } } -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", - }, - { - name: "input string contains pattern at the start", - args: args{ - input: "mf1_mf2_div123456789", - }, - 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) - }) - } -} - func TestGetDisplayManagerAndVer(t *testing.T) { type args struct { app *openrtb2.App diff --git a/adapters/vastbidder/etree_parser.go b/adapters/vastbidder/etree_parser.go index fcf06a40c98..f60e8f79b82 100644 --- a/adapters/vastbidder/etree_parser.go +++ b/adapters/vastbidder/etree_parser.go @@ -57,13 +57,7 @@ func (p *etreeXMLParser) Parse(vastXML []byte) (err error) { } func (p *etreeXMLParser) GetPricingDetails() (price float64, currency string) { - var node *etree.Element - - if int(p.vastVersion) == 2 { - node = p.adElement.FindElement("./Extensions/Extension/Price") - } else { - node = p.adElement.SelectElement("Pricing") - } + node := p.getPricingNode() if node == nil { return 0.0, "" @@ -81,6 +75,18 @@ func (p *etreeXMLParser) GetPricingDetails() (price float64, currency string) { return priceValue, currency } +func (p *etreeXMLParser) getPricingNode() *etree.Element { + node := p.adElement.SelectElement("Pricing") + if node == nil { + node = p.adElement.FindElement("./Extensions/Extension/Pricing") + } + if node == nil { + node = p.adElement.FindElement("./Extensions/Extension/Price") + } + + return node +} + func (p *etreeXMLParser) GetAdvertiser() (advertisers []string) { switch int(p.vastVersion) { case vastVersion2x, vastVersion3x: diff --git a/adapters/vastbidder/etree_parser_test.go b/adapters/vastbidder/etree_parser_test.go index 25afb18af31..7c22edf43cb 100644 --- a/adapters/vastbidder/etree_parser_test.go +++ b/adapters/vastbidder/etree_parser_test.go @@ -300,6 +300,24 @@ func getPricingDetailsTestCases() []struct { wantPrice: 12.05, wantCurrency: "USD", }, + { + name: "vast_2.0_empty_pricing", + vastXML: ``, + wantPrice: 0, + wantCurrency: "", + }, + { + name: "vast_2.0_cdata_pricing", + vastXML: ``, + wantPrice: 12.05, + wantCurrency: "USD", + }, + { + name: "vast_2.0_pricing", + vastXML: `12.05`, + wantPrice: 12.05, + wantCurrency: "USD", + }, { name: "vast_2.0_empty_currency", vastXML: `12.05`, @@ -354,6 +372,30 @@ func getPricingDetailsTestCases() []struct { wantPrice: 5, wantCurrency: "USD", }, + { + name: "vast_2.0_pricing_price_all_location", + vastXML: ``, + wantPrice: 15.05, + wantCurrency: "USD", + }, + { + name: "vast_3.0_pricing_price_all_location", + vastXML: ``, + wantPrice: 15.05, + wantCurrency: "USD", + }, + { + name: "vast_2.0_pricing_price_extension", + vastXML: ``, + wantPrice: 12.05, + wantCurrency: "USD", + }, + { + name: "vast_3.0_pricing_price_extension", + vastXML: ``, + wantPrice: 12.05, + wantCurrency: "USD", + }, // TODO: Add test cases. } } @@ -422,3 +464,61 @@ func Test_etreeXMLParser_GetPricingDetails(t *testing.T) { }) } } + +func getPricingNodeTestCases() []struct { + name string + vastXML string + wantNil bool + wantPrice string +} { + return []struct { + name string + vastXML string + wantNil bool + wantPrice string + }{ + { + name: "no_pricing_node", + vastXML: ``, + wantNil: true, + }, + { + name: "pricing_node_present", + vastXML: `12.50`, + wantNil: false, + wantPrice: "12.50", + }, + { + name: "pricing_node_in_extension_pricing", + vastXML: `12.10`, + wantNil: false, + wantPrice: "12.10", + }, + { + name: "price_node_in_extension_price", + vastXML: `12.05`, + wantNil: false, + wantPrice: "12.05", + }, + } +} + +func Test_etreeXMLParser_getPricingNode(t *testing.T) { + for _, tt := range getPricingNodeTestCases() { + t.Run(tt.name, func(t *testing.T) { + parser := newETreeXMLParser() + err := parser.Parse([]byte(tt.vastXML)) + if !assert.NoError(t, err) { + return + } + + node := parser.getPricingNode() + if tt.wantNil { + assert.Nil(t, node) + } else { + assert.NotNil(t, node) + assert.Equal(t, tt.wantPrice, node.Text()) + } + }) + } +} diff --git a/adapters/vastbidder/fastxml_parser.go b/adapters/vastbidder/fastxml_parser.go index a83cfd293f0..714e7bfd1dd 100644 --- a/adapters/vastbidder/fastxml_parser.go +++ b/adapters/vastbidder/fastxml_parser.go @@ -58,13 +58,7 @@ func (p *fastXMLParser) Parse(vastXML []byte) (err error) { } func (p *fastXMLParser) GetPricingDetails() (price float64, currency string) { - var node *fastxml.Element - - if int(p.vastVersion) == 2 { - node = p.reader.SelectElement(p.adElement, "Extensions", "Extension", "Price") - } else { - node = p.reader.SelectElement(p.adElement, "Pricing") - } + node := p.getPricingNode() if node == nil { return 0.0, "" @@ -82,6 +76,18 @@ func (p *fastXMLParser) GetPricingDetails() (price float64, currency string) { return priceValue, currency } +func (p *fastXMLParser) getPricingNode() *fastxml.Element { + node := p.reader.SelectElement(p.adElement, "Pricing") + if node == nil { + node = p.reader.SelectElement(p.adElement, "Extensions", "Extension", "Pricing") + } + if node == nil { + node = p.reader.SelectElement(p.adElement, "Extensions", "Extension", "Price") + } + + return node +} + func (p *fastXMLParser) GetAdvertiser() (advertisers []string) { switch int(p.vastVersion) { case vastVersion2x, vastVersion3x: diff --git a/adapters/vastbidder/fastxml_parser_test.go b/adapters/vastbidder/fastxml_parser_test.go index 9424ebc566c..94c0bd33858 100644 --- a/adapters/vastbidder/fastxml_parser_test.go +++ b/adapters/vastbidder/fastxml_parser_test.go @@ -2,6 +2,7 @@ package vastbidder import ( "sort" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -71,3 +72,22 @@ func Test_fastXMLParser_GetPricingDetails(t *testing.T) { }) } } + +func Test_fastXMLParser_getPricingNode(t *testing.T) { + for _, tt := range getPricingNodeTestCases() { + t.Run(tt.name, func(t *testing.T) { + parser := newFastXMLParser() + err := parser.Parse([]byte(tt.vastXML)) + if !assert.NoError(t, err) { + return + } + node := parser.getPricingNode() + if tt.wantNil { + assert.Nil(t, node) + } else { + assert.NotNil(t, node) + assert.Equal(t, tt.wantPrice, strings.TrimSpace(parser.reader.RawText(node))) + } + }) + } +} 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/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..d9412b7508c 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -77,6 +77,7 @@ import ( "github.com/prebid/prebid-server/v2/adapters/deepintent" "github.com/prebid/prebid-server/v2/adapters/definemedia" "github.com/prebid/prebid-server/v2/adapters/dianomi" + "github.com/prebid/prebid-server/v2/adapters/displayio" "github.com/prebid/prebid-server/v2/adapters/dmx" "github.com/prebid/prebid-server/v2/adapters/dxkulture" evolution "github.com/prebid/prebid-server/v2/adapters/e_volution" @@ -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 ccd5fa5a65f..3aba94ab8c7 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -426,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 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 := "" diff --git a/go.mod b/go.mod index fdf70090dc8..95df10bfc77 100644 --- a/go.mod +++ b/go.mod @@ -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,7 +89,6 @@ 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 google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect @@ -100,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 4451dc12c04..b5d85f1ecb8 100644 --- a/go.sum +++ b/go.sum @@ -55,7 +55,6 @@ git.pubmatic.com/PubMatic/go-netacuity-client v0.0.0-20240104092757-5d6f15e25fe3 git.pubmatic.com/PubMatic/go-netacuity-client v0.0.0-20240104092757-5d6f15e25fe3/go.mod h1:w733mqJnHt0hLR9mIFMzyDR0D94qzc7mFHsuE0tFQho= 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 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 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/modules/pubmatic/openwrap/adapters/bidder_alias.go b/modules/pubmatic/openwrap/adapters/bidder_alias.go index 32fb416988c..8639b9291eb 100644 --- a/modules/pubmatic/openwrap/adapters/bidder_alias.go +++ b/modules/pubmatic/openwrap/adapters/bidder_alias.go @@ -18,6 +18,7 @@ func Alias() map[string]string { models.BidderAndBeyondAlias: string(openrtb_ext.BidderAdkernel), models.BidderMediaFuseAlias: string(openrtb_ext.BidderAppnexus), models.BidderAppStockAlias: string(openrtb_ext.BidderLimelightDigital), + models.BidderAdsYieldAlias: string(openrtb_ext.BidderLimelightDigital), } } @@ -46,7 +47,7 @@ func ResolveOWBidder(bidderName string) string { coreBidderName = string(openrtb_ext.BidderImds) case models.BidderViewDeos: coreBidderName = string(openrtb_ext.BidderAdtelligent) - case models.BidderAppStockAlias: + case models.BidderAppStockAlias, models.BidderAdsYieldAlias: 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 dc326feab2c..73a452507ea 100644 --- a/modules/pubmatic/openwrap/adapters/bidder_alias_test.go +++ b/modules/pubmatic/openwrap/adapters/bidder_alias_test.go @@ -17,6 +17,7 @@ func TestAlias(t *testing.T) { models.BidderAndBeyondAlias: string(openrtb_ext.BidderAdkernel), models.BidderMediaFuseAlias: string(openrtb_ext.BidderAppnexus), models.BidderAppStockAlias: string(openrtb_ext.BidderLimelightDigital), + models.BidderAdsYieldAlias: 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/auctionresponsehook.go b/modules/pubmatic/openwrap/auctionresponsehook.go index cacda092cfa..2d7ecc2a406 100644 --- a/modules/pubmatic/openwrap/auctionresponsehook.go +++ b/modules/pubmatic/openwrap/auctionresponsehook.go @@ -6,6 +6,7 @@ import ( "strconv" "time" + "github.com/buger/jsonparser" "github.com/prebid/openrtb/v20/openrtb2" "github.com/prebid/prebid-server/v2/hooks/hookanalytics" "github.com/prebid/prebid-server/v2/hooks/hookstage" @@ -118,6 +119,16 @@ func (m OpenWrap) handleAuctionResponseHook( } } + // Explicitly set the bid.ext.mbmfv value if it is present in the bid.ext since we need it for logging but do not want it in the response + mbmfv, err := jsonparser.GetFloat(bid.Ext, models.MultiBidMultiFloorValue) + if err == nil && mbmfv > 0 { + bidExt.MultiBidMultiFloorValue = mbmfv + } + + if bidExt.InBannerVideo { + m.metricEngine.RecordIBVRequest(rctx.PubIDStr, rctx.ProfileIDStr) + } + if rctx.IsCTVRequest { if dur, ok := impCtx.BidIDToDur[bid.ID]; ok { bidExt.Prebid.Video.Duration = int(dur) @@ -327,6 +338,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..4346dc5033b 100644 --- a/modules/pubmatic/openwrap/auctionresponsehook_test.go +++ b/modules/pubmatic/openwrap/auctionresponsehook_test.go @@ -1664,7 +1664,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":"banner","video":{"duration":0,"primary_category":"","vasttagid":""}}}`), + Ext: json.RawMessage(`{"bidtype":0,"deal_channel":1,"dspid":6,"origbidcpm":8,"origbidcur":"USD","ibv":true,"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":"banner","video":{"duration":0,"primary_category":"","vasttagid":""}}}`), }, }, Seat: "pubmatic", @@ -1682,6 +1682,7 @@ func TestOpenWrapHandleAuctionResponseHook(t *testing.T) { mockEngine.EXPECT().RecordPublisherPartnerNoCookieStats("5890", gomock.Any()).AnyTimes() mockFeature.EXPECT().IsTBFFeatureEnabled(gomock.Any(), gomock.Any()).Return(false).AnyTimes() mockFeature.EXPECT().IsFscApplicable(gomock.Any(), gomock.Any(), gomock.Any()).Return(false) + mockEngine.EXPECT().RecordIBVRequest("5890", gomock.Any()) return mockEngine }, doMutate: true, @@ -1690,7 +1691,7 @@ func TestOpenWrapHandleAuctionResponseHook(t *testing.T) { DebugMessages: []string{`[{"PubID":5890,"ProfileID":0,"DisplayID":0,"VersionID":0,"DisplayVersionID":0,"SSAuction":0,"SummaryDisable":0,"SSAI":"","PartnerConfigMap":{"-1":{"displayVersionId":"1","refreshInterval":"30","rev_share":"0.5"},"123":{"bidderCode":"pubmatic","kgp":"_AU_@_W_x_H_","partnerId":"123","prebidPartnerName":"pubmatic","serverSideEnabled":"1","timeout":"200"}},"SupportDeals":false,"Platform":"web","LoggerImpressionID":"","ClientConfigFlag":1,"IP":"","TMax":0,"IsTestRequest":0,"ABTestConfig":0,"ABTestConfigApplied":0,"IsCTVRequest":false,"TrackerEndpoint":"","VideoErrorTrackerEndpoint":"","UA":"","Cookies":"","UidCookie":null,"KADUSERCookie":null,"ParsedUidCookie":null,"OriginCookie":"","Debug":true,"Trace":false,"PageURL":"","StartTime":0,"DevicePlatform":0,"Trackers":{"bid-id-1":{"Tracker":{"PubID":5890,"PageURL":"","Timestamp":0,"IID":"","ProfileID":"0","VersionID":"0","SlotID":"","Adunit":"","PartnerInfo":{"PartnerID":"pubmatic","BidderCode":"pubmatic","KGPV":"","GrossECPM":0,"NetECPM":0,"BidID":"bb57a9e3-fdc2-4772-8071-112dd7f50a6a","OrigBidID":"bid-id-1","AdSize":"0x0","AdDuration":0,"Adformat":"banner","ServerSide":1,"Advertiser":"","FloorValue":0,"FloorRuleValue":0,"DealID":"-1"},"RewardedInventory":0,"SURL":"","Platform":0,"SSAI":"","AdPodSlot":0,"TestGroup":0,"Origin":"","FloorSkippedFlag":null,"FloorModelVersion":"","FloorSource":null,"FloorType":0,"CustomDimensions":"","LoggerData":{"KGPSV":"","FloorProvider":"","FloorFetchStatus":null}},"TrackerURL":"https:?adv=\u0026af=banner\u0026aps=0\u0026au=\u0026bc=pubmatic\u0026bidid=bb57a9e3-fdc2-4772-8071-112dd7f50a6a\u0026di=-1\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","ErrorURL":"","Price":5,"PriceModel":"CPM","PriceCurrency":""}},"PrebidBidderCode":null,"ImpBidCtx":{"Div1":{"ImpID":"","TagID":"","Div":"","SlotName":"","AdUnitName":"","Secure":0,"BidFloor":0,"BidFloorCur":"","IsRewardInventory":null,"Banner":true,"Video":{"mimes":null},"Native":null,"IncomingSlots":null,"Type":"video","Bidders":{"pubmatic":{"PartnerID":123,"PrebidBidderCode":"pubmatic","MatchedSlot":"","KGP":"","KGPV":"","IsRegex":false,"Params":null,"VASTTagFlag":false,"VASTTagFlags":null}},"NonMapped":null,"NewExt":null,"BidCtx":{"bid-id-1":{"prebid":{"meta":{"adaptercode":"pubmatic","advertiserId":4098,"agencyId":4098,"demandSource":"6","mediaType":"banner","networkId":6},"type":"banner","bidid":"bb57a9e3-fdc2-4772-8071-112dd7f50a6a"},"refreshInterval":30,"crtype":"banner","dspid":6,"netecpm":5,"origbidcpm":8,"origbidcur":"USD","EG":0,"EN":0}},"BannerAdUnitCtx":{"MatchedSlot":"","IsRegex":false,"MatchedRegex":"","SelectedSlotAdUnitConfig":null,"AppliedSlotAdUnitConfig":null,"UsingDefaultConfig":false,"AllowedConnectionTypes":null},"VideoAdUnitCtx":{"MatchedSlot":"","IsRegex":false,"MatchedRegex":"","SelectedSlotAdUnitConfig":null,"AppliedSlotAdUnitConfig":null,"UsingDefaultConfig":false,"AllowedConnectionTypes":null},"BidderError":"","IsAdPodRequest":false}},"Aliases":null,"NewReqExt":null,"ResponseExt":{"responsetimemillis":{"pubmatic":8}},"MarketPlaceBidders":null,"AdapterThrottleMap":null,"AdUnitConfig":null,"Source":"","Origin":"","SendAllBids":false,"WinningBids":{"Div1":{"ID":"bid-id-1","NetEcpm":5,"BidDealTierSatisfied":false,"Nbr":null}},"DroppedBids":null,"DefaultBids":{},"SeatNonBids":{},"BidderResponseTimeMillis":{"pubmatic":8},"Endpoint":"","PubIDStr":"5890","ProfileIDStr":"","MetricsEngine":{},"ReturnAllBidStatus":true,"Sshb":"","DCName":"","CachePutMiss":0,"MatchedImpression":{"pubmatic":0},"CustomDimensions":null}]`}, }, err: nil, - bidResponse: json.RawMessage(`{"id":"12345","seatbid":[{"bid":[{"id":"bid-id-1","impid":"Div1","price":5,"adm":"\u003cimg src=\"http://ads.pubmatic.com/AdTag/728x90.png\"\u003e\u003c/img\u003e\u003cdiv style=\"position:absolute;left:0px;top:0px;visibility:hidden;\"\u003e\u003cimg src=\"https://t.pubmatic.com/wt?adv=\u0026af=banner\u0026aps=0\u0026au=%2F43743431%2FDMDemo\u0026bc=appnexus\u0026bidid=4033c510-6d67-4af6-b53f-682ff1a580c3\u0026di=-1\u0026eg=14\u0026en=14\u0026frv=1.57\u0026ft=0\u0026fv=1.57\u0026iid=429d469d-8cfb-495a-9f0c-5f48aa0ede40\u0026kgpv=\u0026orig=ebay.com\u0026origbidid=718825584\u0026pdvid=1\u0026pid=22503\u0026plt=1\u0026pn=appnexus\u0026psz=728x90\u0026pubid=5890\u0026purl=http%3A%2F%2Febay.com%2Finte%2Fautomation%2Fs2s_activation%2Fbanner-with-gdpr-pubmatic-denied-defaultbidder.html%3Fprofileid%3D22503%26pwtv%3D1%26pwtvc%3D1%26appnexus_banner_fixedbid%3D14%26fixedbid%3D1%26debug%3D1\u0026sl=1\u0026slot=%2F43743431%2FDMDemo\u0026ss=1\u0026tgid=0\u0026tst=1704357774\"\u003e\u003c/div\u003e\u003cdiv style=\"position:absolute;left:0px;top:0px;visibility:hidden;\"\u003e\u003cimg src=\"https:?adv=\u0026af=banner\u0026aps=0\u0026au=\u0026bc=pubmatic\u0026bidid=bb57a9e3-fdc2-4772-8071-112dd7f50a6a\u0026di=-1\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/div\u003e","ext":{"prebid":{"meta":{"adaptercode":"pubmatic","advertiserId":4098,"agencyId":4098,"demandSource":"6","mediaType":"banner","networkId":6},"type":"banner","bidid":"bb57a9e3-fdc2-4772-8071-112dd7f50a6a"},"refreshInterval":30,"crtype":"banner","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":"\u003cimg src=\"http://ads.pubmatic.com/AdTag/728x90.png\"\u003e\u003c/img\u003e\u003cdiv style=\"position:absolute;left:0px;top:0px;visibility:hidden;\"\u003e\u003cimg src=\"https://t.pubmatic.com/wt?adv=\u0026af=banner\u0026aps=0\u0026au=%2F43743431%2FDMDemo\u0026bc=appnexus\u0026bidid=4033c510-6d67-4af6-b53f-682ff1a580c3\u0026di=-1\u0026eg=14\u0026en=14\u0026frv=1.57\u0026ft=0\u0026fv=1.57\u0026iid=429d469d-8cfb-495a-9f0c-5f48aa0ede40\u0026kgpv=\u0026orig=ebay.com\u0026origbidid=718825584\u0026pdvid=1\u0026pid=22503\u0026plt=1\u0026pn=appnexus\u0026psz=728x90\u0026pubid=5890\u0026purl=http%3A%2F%2Febay.com%2Finte%2Fautomation%2Fs2s_activation%2Fbanner-with-gdpr-pubmatic-denied-defaultbidder.html%3Fprofileid%3D22503%26pwtv%3D1%26pwtvc%3D1%26appnexus_banner_fixedbid%3D14%26fixedbid%3D1%26debug%3D1\u0026sl=1\u0026slot=%2F43743431%2FDMDemo\u0026ss=1\u0026tgid=0\u0026tst=1704357774\"\u003e\u003c/div\u003e\u003cdiv style=\"position:absolute;left:0px;top:0px;visibility:hidden;\"\u003e\u003cimg src=\"https:?adv=\u0026af=banner\u0026aps=0\u0026au=\u0026bc=pubmatic\u0026bidid=bb57a9e3-fdc2-4772-8071-112dd7f50a6a\u0026di=-1\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/div\u003e","ext":{"prebid":{"meta":{"adaptercode":"pubmatic","advertiserId":4098,"agencyId":4098,"demandSource":"6","mediaType":"banner","networkId":6},"type":"banner","bidid":"bb57a9e3-fdc2-4772-8071-112dd7f50a6a"},"refreshInterval":30,"crtype":"banner","dspid":6,"netecpm":5,"origbidcpm":8,"origbidcur":"USD","ibv":true}}],"seat":"pubmatic"}],"ext":{"responsetimemillis":{"pubmatic":8},"matchedimpression":{"pubmatic":0}}}`), }, }, { @@ -1724,6 +1725,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 +1761,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 +1785,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"}}],"seat":"pubmatic"}],"ext":{"responsetimemillis":{"pubmatic":8},"matchedimpression":{"pubmatic":0}}}`), }, }, } diff --git a/modules/pubmatic/openwrap/beforevalidationhook.go b/modules/pubmatic/openwrap/beforevalidationhook.go index c500d5970a8..8fb761d9368 100644 --- a/modules/pubmatic/openwrap/beforevalidationhook.go +++ b/modules/pubmatic/openwrap/beforevalidationhook.go @@ -132,14 +132,9 @@ func (m OpenWrap) handleBeforeValidationHook( if err != nil || len(partnerConfigMap) == 0 { // TODO: seperate DB fetch errors as internal errors result.NbrCode = int(nbr.InvalidProfileConfiguration) - if err != nil { - err = errors.New("failed to get profile data: " + err.Error()) - } else { - err = errors.New("failed to get profile data: received empty data") - } rCtx.ImpBidCtx = getDefaultImpBidCtx(*payload.BidRequest) // for wrapper logger sz m.metricEngine.RecordPublisherInvalidProfileRequests(rCtx.Endpoint, rCtx.PubIDStr, rCtx.ProfileIDStr) - return result, err + return result, errors.New("invalid profile data") } if rCtx.IsCTVRequest && rCtx.Endpoint == models.EndpointJson { @@ -466,7 +461,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) @@ -719,7 +714,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) } diff --git a/modules/pubmatic/openwrap/beforevalidationhook_test.go b/modules/pubmatic/openwrap/beforevalidationhook_test.go index 4ea909b1918..7b9b7d41a4b 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" @@ -1840,6 +1841,522 @@ func TestOpenWrapApplyProfileChanges(t *testing.T) { }, 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.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}, + }, + }, + { + ID: "testImp2", + Video: &openrtb2.Video{ + W: ptrutil.ToPtr[int64](200), + H: ptrutil.ToPtr[int64](300), + Plcmt: 1, + Protocols: []adcom1.MediaCreativeSubtype{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, + 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}, + }, + }, + }, + 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_Empty_Protocols", + 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{}, + }, + }, + }, + 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{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_Protocols_Not_Present", + 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, + }, + }, + }, + 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{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_Disabled", + args: args{ + rctx: models.RequestCtx{ + IsTestRequest: 1, + PartnerConfigMap: map[int]map[string]string{ + -1: { + models.AdServerCurrency: "USD", + models.SChainDBKey: "1", + models.StrictVastModeKey: "0", + }, + }, + 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}, + }, + }, + }, + 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, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -3341,7 +3858,7 @@ func TestOpenWrapHandleBeforeValidationHook(t *testing.T) { hookResult: hookstage.HookResult[hookstage.BeforeValidationRequestPayload]{ Reject: true, NbrCode: int(nbr.InvalidProfileConfiguration), - Errors: []string{"failed to get profile data: received empty data"}, + Errors: []string{"invalid profile data"}, }, error: true, nilCurrencyConversion: true, diff --git a/modules/pubmatic/openwrap/cache/gocache/adpod_config.go b/modules/pubmatic/openwrap/cache/gocache/adpod_config.go index b1233d4fd4f..b152a60de7b 100644 --- a/modules/pubmatic/openwrap/cache/gocache/adpod_config.go +++ b/modules/pubmatic/openwrap/cache/gocache/adpod_config.go @@ -1,19 +1,28 @@ package gocache import ( + "database/sql" + "errors" "strconv" + "github.com/golang/glog" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adpodconfig" ) func (c *cache) populateCacheWithAdpodConfig(pubID, profileID, displayVersion int) (err error) { + cacheKey := key(PubAdpodConfig, pubID, profileID, displayVersion) adpodConfig, err := c.db.GetAdpodConfig(pubID, profileID, displayVersion) if err != nil { + if errors.Is(err, sql.ErrNoRows) { + c.metricEngine.RecordDBQueryFailure(models.LiveVersionInnerQuery, strconv.Itoa(pubID), strconv.Itoa(profileID)) + } else { + c.metricEngine.RecordDBQueryFailure(models.GetAdpodConfig, strconv.Itoa(pubID), strconv.Itoa(profileID)) + } + glog.Errorf(models.ErrDBQueryFailed, models.GetAdpodConfig, pubID, profileID, err) return err } - cacheKey := key(PubAdpodConfig, pubID, profileID, displayVersion) c.cache.Set(cacheKey, adpodConfig, getSeconds(c.cfg.CacheDefaultExpiry)) return } @@ -32,7 +41,6 @@ func (c *cache) GetAdpodConfig(pubID, profileID, displayVersion int) (*adpodconf if err := c.LockAndLoad(lockKey, func() error { return c.populateCacheWithAdpodConfig(pubID, profileID, displayVersion) }); err != nil { - c.metricEngine.RecordDBQueryFailure(models.GetAdpodConfig, strconv.Itoa(pubID), strconv.Itoa(profileID)) return nil, err } diff --git a/modules/pubmatic/openwrap/cache/gocache/app_integration_path.go b/modules/pubmatic/openwrap/cache/gocache/app_integration_path.go index 5e452d13b27..2e26dd49e6a 100644 --- a/modules/pubmatic/openwrap/cache/gocache/app_integration_path.go +++ b/modules/pubmatic/openwrap/cache/gocache/app_integration_path.go @@ -3,6 +3,7 @@ package gocache import ( "fmt" + "github.com/golang/glog" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" ) @@ -13,6 +14,7 @@ func (c *cache) GetAppIntegrationPaths() (map[string]int, error) { appIntegrationPathMap, err := c.db.GetAppIntegrationPaths() if err != nil { c.metricEngine.RecordDBQueryFailure(models.AppIntegrationPathMapQuery, "", "") + glog.Errorf(models.ErrDBQueryFailed, models.AppIntegrationPathMapQuery, "", "", err) return appIntegrationPathMap, fmt.Errorf(errorAppIntegrationPathMapUpdate, err) } return appIntegrationPathMap, nil diff --git a/modules/pubmatic/openwrap/cache/gocache/app_sub_integartion_path.go b/modules/pubmatic/openwrap/cache/gocache/app_sub_integartion_path.go index fdde2ae0805..87162016562 100644 --- a/modules/pubmatic/openwrap/cache/gocache/app_sub_integartion_path.go +++ b/modules/pubmatic/openwrap/cache/gocache/app_sub_integartion_path.go @@ -3,6 +3,7 @@ package gocache import ( "fmt" + "github.com/golang/glog" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" ) @@ -13,6 +14,7 @@ func (c *cache) GetAppSubIntegrationPaths() (map[string]int, error) { appSubIntegrationPathMap, err := c.db.GetAppSubIntegrationPaths() if err != nil { c.metricEngine.RecordDBQueryFailure(models.AppSubIntegrationPathMapQuery, "", "") + glog.Errorf(models.ErrDBQueryFailed, models.AppSubIntegrationPathMapQuery, "", "", err) return appSubIntegrationPathMap, fmt.Errorf(errorAppSubIntegrationPathMapUpdate, err) } return appSubIntegrationPathMap, nil diff --git a/modules/pubmatic/openwrap/cache/gocache/fullscreenclickability.go b/modules/pubmatic/openwrap/cache/gocache/fullscreenclickability.go index 4e8fd798083..c11bb0e34aa 100644 --- a/modules/pubmatic/openwrap/cache/gocache/fullscreenclickability.go +++ b/modules/pubmatic/openwrap/cache/gocache/fullscreenclickability.go @@ -3,6 +3,7 @@ package gocache import ( "fmt" + "github.com/golang/glog" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" ) @@ -15,6 +16,7 @@ func (c *cache) GetFSCThresholdPerDSP() (map[int]int, error) { fscThreshold, err := c.db.GetFSCThresholdPerDSP() if err != nil { c.metricEngine.RecordDBQueryFailure(models.AllDspFscPcntQuery, "", "") + glog.Errorf(models.ErrDBQueryFailed, models.AllDspFscPcntQuery, "", "", err) return fscThreshold, fmt.Errorf(errorFscDspMsg, err) } return fscThreshold, nil diff --git a/modules/pubmatic/openwrap/cache/gocache/partner_config.go b/modules/pubmatic/openwrap/cache/gocache/partner_config.go index 76d1ed676d1..755ea1ac978 100644 --- a/modules/pubmatic/openwrap/cache/gocache/partner_config.go +++ b/modules/pubmatic/openwrap/cache/gocache/partner_config.go @@ -1,11 +1,13 @@ package gocache import ( + "database/sql" "errors" "fmt" "strconv" "time" + "github.com/golang/glog" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" ) @@ -67,13 +69,19 @@ func (c *cache) getActivePartnerConfigAndPopulateWrapperMappings(pubID, profileI cacheKey := key(PUB_HB_PARTNER, pubID, profileID, displayVersion) partnerConfigMap, err := c.db.GetActivePartnerConfigurations(pubID, profileID, displayVersion) if err != nil { - c.metricEngine.RecordDBQueryFailure(models.PartnerConfigQuery, strconv.Itoa(pubID), strconv.Itoa(profileID)) - return + if errors.Is(err, sql.ErrNoRows) { + c.metricEngine.RecordDBQueryFailure(models.LiveVersionInnerQuery, strconv.Itoa(pubID), strconv.Itoa(profileID)) + c.cache.Set(cacheKey, partnerConfigMap, getSeconds(c.cfg.CacheDefaultExpiry)) + } else { + c.metricEngine.RecordDBQueryFailure(models.PartnerConfigQuery, strconv.Itoa(pubID), strconv.Itoa(profileID)) + } + glog.Errorf(models.ErrDBQueryFailed, models.PartnerConfigQuery, pubID, profileID, err) + return err } 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) + return fmt.Errorf(models.EmptyPartnerConfig, pubID, profileID, displayVersion) } err = c.populateCacheWithWrapperSlotMappings(pubID, partnerConfigMap, profileID, displayVersion) @@ -83,6 +91,7 @@ func (c *cache) getActivePartnerConfigAndPopulateWrapperMappings(pubID, profileI queryType = models.WrapperLiveVersionSlotMappings } c.metricEngine.RecordDBQueryFailure(queryType, strconv.Itoa(pubID), strconv.Itoa(profileID)) + glog.Errorf(models.ErrDBQueryFailed, queryType, pubID, profileID, err) return err } @@ -96,6 +105,7 @@ func (c *cache) getActivePartnerConfigAndPopulateWrapperMappings(pubID, profileI queryType = models.AdUnitFailUnmarshal } c.metricEngine.RecordDBQueryFailure(queryType, strconv.Itoa(pubID), strconv.Itoa(profileID)) + glog.Errorf(models.ErrDBQueryFailed, queryType, pubID, profileID, err) return err } diff --git a/modules/pubmatic/openwrap/cache/gocache/profile_type_platform.go b/modules/pubmatic/openwrap/cache/gocache/profile_type_platform.go index ad912e430d2..c83dc5fdb34 100644 --- a/modules/pubmatic/openwrap/cache/gocache/profile_type_platform.go +++ b/modules/pubmatic/openwrap/cache/gocache/profile_type_platform.go @@ -3,6 +3,7 @@ package gocache import ( "fmt" + "github.com/golang/glog" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" ) @@ -13,6 +14,7 @@ func (c *cache) GetProfileTypePlatforms() (map[string]int, error) { profileTypePlatformMap, err := c.db.GetProfileTypePlatforms() if err != nil { c.metricEngine.RecordDBQueryFailure(models.ProfileTypePlatformMapQuery, "", "") + glog.Errorf(models.ErrDBQueryFailed, models.ProfileTypePlatformMapQuery, "", "", err) return profileTypePlatformMap, fmt.Errorf(errorProfileTypePlatformMapUpdate, err) } return profileTypePlatformMap, nil diff --git a/modules/pubmatic/openwrap/cache/gocache/publisher_feature.go b/modules/pubmatic/openwrap/cache/gocache/publisher_feature.go index c27ac7d9214..d922ef2c51d 100644 --- a/modules/pubmatic/openwrap/cache/gocache/publisher_feature.go +++ b/modules/pubmatic/openwrap/cache/gocache/publisher_feature.go @@ -3,6 +3,7 @@ package gocache import ( "fmt" + "github.com/golang/glog" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" ) @@ -13,6 +14,7 @@ func (c *cache) GetPublisherFeatureMap() (map[int]map[int]models.FeatureData, er publisherFeatureMap, err := c.db.GetPublisherFeatureMap() if err != nil { c.metricEngine.RecordDBQueryFailure(models.PublisherFeatureMapQuery, "", "") + glog.Errorf(models.ErrDBQueryFailed, models.PublisherFeatureMapQuery, "", "", err) return publisherFeatureMap, fmt.Errorf(errorPubFeatureUpdate, err) } return publisherFeatureMap, nil diff --git a/modules/pubmatic/openwrap/cache/gocache/slot_mappings.go b/modules/pubmatic/openwrap/cache/gocache/slot_mappings.go index b25246a7937..fa938ead1af 100644 --- a/modules/pubmatic/openwrap/cache/gocache/slot_mappings.go +++ b/modules/pubmatic/openwrap/cache/gocache/slot_mappings.go @@ -6,6 +6,7 @@ import ( "strconv" "strings" + "github.com/golang/glog" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" "github.com/prebid/prebid-server/v2/openrtb_ext" ) @@ -15,6 +16,10 @@ func (c *cache) populateCacheWithPubSlotNameHash(pubID int) (err error) { cacheKey := key(PubSlotNameHash, pubID) publisherSlotNameHashMap, err := c.db.GetPublisherSlotNameHash(pubID) + if err != nil { + glog.Errorf(models.ErrDBQueryFailed, models.SlotNameHash, pubID, "", err) + return + } //This call may set nil publisherSlotNameHashMap in cache c.cache.Set(cacheKey, publisherSlotNameHashMap, getSeconds(c.cfg.CacheDefaultExpiry)) return @@ -23,6 +28,9 @@ func (c *cache) populateCacheWithPubSlotNameHash(pubID int) (err error) { // PopulateCacheWithWrapperSlotMappings will get the SlotMappings from database and put them in cache. func (c *cache) populateCacheWithWrapperSlotMappings(pubID int, partnerConfigMap map[int]map[string]string, profileID, displayVersion int) error { partnerSlotMappingMap, err := c.db.GetWrapperSlotMappings(partnerConfigMap, profileID, displayVersion) + if err != nil { + return err + } //put a version level dummy entry in cache denoting mappings are present for this version cacheKey := key(PUB_SLOT_INFO, pubID, profileID, displayVersion, 0) diff --git a/modules/pubmatic/openwrap/cache/gocache/slot_mappings_test.go b/modules/pubmatic/openwrap/cache/gocache/slot_mappings_test.go index 037cffbbb64..0d8017b70e9 100644 --- a/modules/pubmatic/openwrap/cache/gocache/slot_mappings_test.go +++ b/modules/pubmatic/openwrap/cache/gocache/slot_mappings_test.go @@ -41,8 +41,9 @@ func Test_cache_populateCacheWithPubSlotNameHash(t *testing.T) { pubid int } type want struct { - publisherSlotNameHashMap map[string]string + publisherSlotNameHashMap interface{} err error + foundCacheKey bool } tests := []struct { name string @@ -69,6 +70,7 @@ func Test_cache_populateCacheWithPubSlotNameHash(t *testing.T) { want: want{ publisherSlotNameHashMap: nil, err: fmt.Errorf("Error from the DB"), + foundCacheKey: false, }, }, { @@ -93,6 +95,7 @@ func Test_cache_populateCacheWithPubSlotNameHash(t *testing.T) { publisherSlotNameHashMap: map[string]string{ testSlotName: testHashValue, }, + foundCacheKey: true, }, }, { @@ -111,8 +114,9 @@ func Test_cache_populateCacheWithPubSlotNameHash(t *testing.T) { mockDatabase.EXPECT().GetPublisherSlotNameHash(5890).Return(nil, nil) }, want: want{ - publisherSlotNameHashMap: nil, + publisherSlotNameHashMap: map[string]string(nil), err: nil, + foundCacheKey: true, }, }, } @@ -131,7 +135,7 @@ func Test_cache_populateCacheWithPubSlotNameHash(t *testing.T) { assert.Equal(t, tt.want.err, err) cacheKey := key(PubSlotNameHash, tt.args.pubid) publisherSlotNameHashMap, found := c.cache.Get(cacheKey) - assert.True(t, found) + assert.Equal(t, tt.want.foundCacheKey, found) assert.Equal(t, tt.want.publisherSlotNameHashMap, publisherSlotNameHashMap) }) } @@ -157,8 +161,9 @@ func Test_cache_populateCacheWithWrapperSlotMappings(t *testing.T) { displayVersion int } type want struct { - partnerSlotMapping map[string]models.SlotMapping + partnerSlotMapping interface{} err error + foundCacheKey bool } tests := []struct { name string @@ -186,8 +191,9 @@ func Test_cache_populateCacheWithWrapperSlotMappings(t *testing.T) { mockDatabase.EXPECT().GetWrapperSlotMappings(formTestPartnerConfig(), testProfileID, testVersionID).Return(nil, fmt.Errorf("Error from the DB")) }, want: want{ - partnerSlotMapping: map[string]models.SlotMapping{}, + partnerSlotMapping: nil, err: fmt.Errorf("Error from the DB"), + foundCacheKey: false, }, }, { @@ -211,6 +217,7 @@ func Test_cache_populateCacheWithWrapperSlotMappings(t *testing.T) { want: want{ partnerSlotMapping: map[string]models.SlotMapping{}, err: nil, + foundCacheKey: true, }, }, { @@ -261,7 +268,8 @@ func Test_cache_populateCacheWithWrapperSlotMappings(t *testing.T) { OrderID: 0, }, }, - err: nil, + err: nil, + foundCacheKey: true, }, }, { @@ -295,7 +303,8 @@ func Test_cache_populateCacheWithWrapperSlotMappings(t *testing.T) { }, nil) }, want: want{ - err: nil, + err: nil, + foundCacheKey: true, partnerSlotMapping: map[string]models.SlotMapping{ "adunit@300x250": { PartnerId: testPartnerID, @@ -331,7 +340,7 @@ func Test_cache_populateCacheWithWrapperSlotMappings(t *testing.T) { cacheKey := key(PUB_SLOT_INFO, tt.args.pubid, tt.args.profileId, tt.args.displayVersion, testPartnerID) partnerSlotMapping, found := c.cache.Get(cacheKey) - assert.True(t, found) + assert.Equal(t, tt.want.foundCacheKey, found) assert.Equal(t, tt.want.partnerSlotMapping, partnerSlotMapping) }) diff --git a/modules/pubmatic/openwrap/cache/gocache/vast_tags.go b/modules/pubmatic/openwrap/cache/gocache/vast_tags.go index 8f2137a03ec..f75f22b4bfa 100644 --- a/modules/pubmatic/openwrap/cache/gocache/vast_tags.go +++ b/modules/pubmatic/openwrap/cache/gocache/vast_tags.go @@ -1,6 +1,7 @@ package gocache import ( + "github.com/golang/glog" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" ) @@ -11,13 +12,10 @@ func (c *cache) populatePublisherVASTTags(pubID int) error { //get publisher level vast tag details from DB publisherVASTTags, err := c.db.GetPublisherVASTTags(pubID) if err != nil { + glog.Errorf(models.ErrDBQueryFailed, models.PublisherVASTTagsQuery, pubID, "", err) return err } - if publisherVASTTags == nil { - publisherVASTTags = models.PublisherVASTTags{} - } - c.cache.Set(cacheKey, publisherVASTTags, getSeconds(c.cfg.VASTTagCacheExpiry)) return nil } diff --git a/modules/pubmatic/openwrap/cache/gocache/vast_tags_test.go b/modules/pubmatic/openwrap/cache/gocache/vast_tags_test.go index 3d529633551..d9d4e7358f7 100644 --- a/modules/pubmatic/openwrap/cache/gocache/vast_tags_test.go +++ b/modules/pubmatic/openwrap/cache/gocache/vast_tags_test.go @@ -111,7 +111,7 @@ func Test_cache_populatePublisherVASTTags(t *testing.T) { want: want{ cacheEntry: true, err: nil, - PublisherVASTTags: models.PublisherVASTTags{}, + PublisherVASTTags: models.PublisherVASTTags(nil), }, }, } diff --git a/modules/pubmatic/openwrap/database/mysql/adpod_config.go b/modules/pubmatic/openwrap/database/mysql/adpod_config.go index ea25a0246c7..d98b8b2d258 100644 --- a/modules/pubmatic/openwrap/database/mysql/adpod_config.go +++ b/modules/pubmatic/openwrap/database/mysql/adpod_config.go @@ -3,10 +3,10 @@ package mysql import ( "context" "encoding/json" + "fmt" "strings" "time" - "github.com/golang/glog" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adpodconfig" ) @@ -14,9 +14,19 @@ import ( func (db *mySqlDB) GetAdpodConfig(pubID, profileID, displayVersion int) (*adpodconfig.AdpodConfig, error) { versionID, displayVersion, _, _, err := db.getVersionIdAndProfileDetails(profileID, displayVersion, pubID) if err != nil { - return nil, err + return nil, fmt.Errorf("LiveVersionInnerQuery/DisplayVersionInnerQuery Failure Error: %w", err) } + config, err := db.getAdpodConfig(versionID) + if err != nil { + return nil, fmt.Errorf("GetAdpodConfigQuery Failure Error: %w", err) + } + + return config, nil +} + +func (db *mySqlDB) getAdpodConfig(versionID int) (*adpodconfig.AdpodConfig, error) { + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(time.Millisecond*time.Duration(db.cfg.MaxDbContextTimeout))) defer cancel() @@ -54,7 +64,7 @@ func (db *mySqlDB) GetAdpodConfig(pubID, profileID, displayVersion int) (*adpodc } if err = rows.Err(); err != nil { - glog.Errorf("adpod config row scan failed for publisher %d having profile %d with versionID %d", pubID, profileID, displayVersion) + return nil, err } return config, nil diff --git a/modules/pubmatic/openwrap/database/mysql/adpod_config_test.go b/modules/pubmatic/openwrap/database/mysql/adpod_config_test.go index 8757acc794c..650dd4a8c93 100644 --- a/modules/pubmatic/openwrap/database/mysql/adpod_config_test.go +++ b/modules/pubmatic/openwrap/database/mysql/adpod_config_test.go @@ -11,6 +11,7 @@ import ( "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/config" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adpodconfig" "github.com/prebid/prebid-server/v2/util/ptrutil" + "github.com/stretchr/testify/assert" ) func TestMySqlDBGetAdpodConfigs(t *testing.T) { @@ -28,7 +29,7 @@ func TestMySqlDBGetAdpodConfigs(t *testing.T) { args args setup func() *sql.DB want *adpodconfig.AdpodConfig - wantErr bool + wantErr error }{ { name: "Retrieve dynamic adpod configuration from database", @@ -51,10 +52,8 @@ func TestMySqlDBGetAdpodConfigs(t *testing.T) { if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } - rowsWrapperVersion := sqlmock.NewRows([]string{"versionId", "displayVersionId", "platform", "type"}).AddRow("4444", "4", "ctv", "1") mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM version (.+)")).WithArgs(123, 4, 5890).WillReturnRows(rowsWrapperVersion) - rows := sqlmock.NewRows([]string{"pod_type", "s2s_ad_slots_config"}).AddRow("DYNAMIC", `[{"maxduration":60,"maxseq":5,"poddur":180,"minduration":1}]`) mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM ad_pod (.+)")).WithArgs(4444).WillReturnRows(rows) return db @@ -69,7 +68,7 @@ func TestMySqlDBGetAdpodConfigs(t *testing.T) { }, }, }, - wantErr: false, + wantErr: nil, }, { name: "Retrieve dynamic adpod configuration from database for live version", @@ -94,7 +93,6 @@ func TestMySqlDBGetAdpodConfigs(t *testing.T) { } rowsWrapperVersion := sqlmock.NewRows([]string{"versionId", "displayVersionId", "platform", "type"}).AddRow("4444", "4", "ctv", "1") mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM version (.+) LIVE")).WithArgs(123, 5890).WillReturnRows(rowsWrapperVersion) - rows := sqlmock.NewRows([]string{"pod_type", "s2s_ad_slots_config"}).AddRow("DYNAMIC", `[{"maxduration":60,"maxseq":5,"poddur":180,"minduration":1}]`) mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM ad_pod (.+)")).WithArgs(4444).WillReturnRows(rows) return db @@ -109,7 +107,7 @@ func TestMySqlDBGetAdpodConfigs(t *testing.T) { }, }, }, - wantErr: false, + wantErr: nil, }, { name: "Retrieve dynamic adpod configuration from database where rqddurs provided", @@ -134,7 +132,6 @@ func TestMySqlDBGetAdpodConfigs(t *testing.T) { } rowsWrapperVersion := sqlmock.NewRows([]string{"versionId", "displayVersionId", "platform", "type"}).AddRow("4444", "4", "ctv", "1") mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM version (.+)")).WithArgs(123, 4, 5890).WillReturnRows(rowsWrapperVersion) - rows := sqlmock.NewRows([]string{"pod_type", "s2s_ad_slots_config"}).AddRow("DYNAMIC", `[{"maxseq":5,"poddur":600,"rqddurs":[6,60,120,600]}]`) mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM ad_pod (.+)")).WithArgs(4444).WillReturnRows(rows) return db @@ -148,7 +145,7 @@ func TestMySqlDBGetAdpodConfigs(t *testing.T) { }, }, }, - wantErr: false, + wantErr: nil, }, { name: "Retrieve dynamic adpod configuration from database for all types", @@ -171,10 +168,8 @@ func TestMySqlDBGetAdpodConfigs(t *testing.T) { if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } - rowsWrapperVersion := sqlmock.NewRows([]string{"versionId", "displayVersionId", "platform", "type"}).AddRow("4444", "4", "ctv", "1") mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM version (.+)")).WithArgs(123, 4, 5890).WillReturnRows(rowsWrapperVersion) - rows := sqlmock.NewRows([]string{"pod_type", "s2s_ad_slots_config"}). AddRow("DYNAMIC", `[{"maxduration":60,"maxseq":5,"poddur":180,"minduration":1}]`). AddRow("HYBRID", `[{"maxduration":20,"minduration":5},{"maxduration":20,"maxseq":3,"poddur":60,"minduration":5}]`). @@ -210,7 +205,7 @@ func TestMySqlDBGetAdpodConfigs(t *testing.T) { }, }, }, - wantErr: false, + wantErr: nil, }, { name: "No adpod configuration in database", @@ -235,12 +230,45 @@ func TestMySqlDBGetAdpodConfigs(t *testing.T) { } rowsWrapperVersion := sqlmock.NewRows([]string{"versionId", "displayVersionId", "platform", "type"}).AddRow("4444", "4", "ctv", "1") mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM version (.+)")).WithArgs(123, 4, 5890).WillReturnRows(rowsWrapperVersion) - mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM ad_pod (.+)")).WillReturnError(errors.New("context deadline exceeded")) return db }, want: nil, - wantErr: true, + wantErr: errors.New("GetAdpodConfigQuery Failure Error: context deadline exceeded"), + }, + { + name: "Error in row scan", + fields: fields{ + cfg: config.Database{ + MaxDbContextTimeout: 5, + Queries: config.Queries{ + GetAdpodConfig: "^SELECT (.+) FROM ad_pod (.+)", + DisplayVersionInnerQuery: "^SELECT (.+) FROM version (.+)", + }, + }, + }, + args: args{ + pubId: 5890, + profileID: 123, + displayVersion: 4, + }, + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + rowsWrapperVersion := sqlmock.NewRows([]string{"versionId", "displayVersionId", "platform", "type"}).AddRow("4444", "4", "ctv", "1") + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM version (.+)")).WithArgs(123, 4, 5890).WillReturnRows(rowsWrapperVersion) + rows := sqlmock.NewRows([]string{"pod_type", "s2s_ad_slots_config"}). + AddRow("DYNAMIC", `[{"maxduration":60,"maxseq":5,"poddur":180,"minduration":1}]`). + AddRow("HYBRID", `[{"maxduration":20,"minduration":5},{"maxduration":20,"maxseq":3,"poddur":60,"minduration":5}]`). + AddRow("STRUCTURED", `[{"maxduration":20,"minduration":5}]`) + rows = rows.RowError(1, errors.New("error in row scan")) + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM ad_pod (.+)")).WillReturnRows(rows) + return db + }, + want: nil, + wantErr: errors.New("GetAdpodConfigQuery Failure Error: error in row scan"), }, } for _, tt := range tests { @@ -250,9 +278,10 @@ func TestMySqlDBGetAdpodConfigs(t *testing.T) { cfg: tt.fields.cfg, } got, err := db.GetAdpodConfig(tt.args.pubId, tt.args.profileID, tt.args.displayVersion) - if (err != nil) != tt.wantErr { - t.Errorf("mySqlDB.GetAdpodConfigs() error = %v, wantErr %v", err, tt.wantErr) - return + if tt.wantErr == nil { + assert.NoError(t, err, tt.name) + } else { + assert.EqualError(t, err, tt.wantErr.Error(), tt.name) } if !reflect.DeepEqual(got, tt.want) { t.Errorf("mySqlDB.GetAdpodConfigs() = %v, want %v", got, tt.want) diff --git a/modules/pubmatic/openwrap/database/mysql/adunit_config_test.go b/modules/pubmatic/openwrap/database/mysql/adunit_config_test.go index 1f2c8499f92..332e84a45db 100644 --- a/modules/pubmatic/openwrap/database/mysql/adunit_config_test.go +++ b/modules/pubmatic/openwrap/database/mysql/adunit_config_test.go @@ -3,6 +3,7 @@ package mysql import ( "database/sql" "encoding/json" + "errors" "regexp" "testing" @@ -26,13 +27,13 @@ func Test_mySqlDB_GetAdunitConfig(t *testing.T) { fields fields args args want *adunitconfig.AdUnitConfig - wantErr bool + wantErr error setup func() *sql.DB }{ { name: "empty query in config file", want: nil, - wantErr: true, + wantErr: errors.New("context deadline exceeded"), setup: func() *sql.DB { db, _, err := sqlmock.New() if err != nil { @@ -56,7 +57,7 @@ func Test_mySqlDB_GetAdunitConfig(t *testing.T) { displayVersion: 0, }, want: nil, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -87,7 +88,7 @@ func Test_mySqlDB_GetAdunitConfig(t *testing.T) { "default": {BidFloor: ptrutil.ToPtr(2.0)}, }, }, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -118,7 +119,7 @@ func Test_mySqlDB_GetAdunitConfig(t *testing.T) { "default": {BidFloor: ptrutil.ToPtr(3.1)}, }, }, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -144,7 +145,7 @@ func Test_mySqlDB_GetAdunitConfig(t *testing.T) { displayVersion: 0, }, want: nil, - wantErr: true, + wantErr: errors.New("unmarshal error adunitconfig"), setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -185,7 +186,7 @@ func Test_mySqlDB_GetAdunitConfig(t *testing.T) { }, }, }, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -217,7 +218,7 @@ func Test_mySqlDB_GetAdunitConfig(t *testing.T) { "abc": {BidFloor: ptrutil.ToPtr(3.1)}, }, }, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -248,7 +249,7 @@ func Test_mySqlDB_GetAdunitConfig(t *testing.T) { "default": {}, }, }, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -269,9 +270,10 @@ func Test_mySqlDB_GetAdunitConfig(t *testing.T) { defer db.conn.Close() got, err := db.GetAdunitConfig(tt.args.profileID, tt.args.displayVersion) - if (err != nil) != tt.wantErr { - t.Errorf("mySqlDB.GetAdunitConfig() error = %v, wantErr %v", err, tt.wantErr) - return + if tt.wantErr == nil { + assert.NoError(t, err, tt.name) + } else { + assert.EqualError(t, err, tt.wantErr.Error(), tt.name) } assert.Equal(t, tt.want, got) }) diff --git a/modules/pubmatic/openwrap/database/mysql/app_integration_path.go b/modules/pubmatic/openwrap/database/mysql/app_integration_path.go index 89be33e6edf..ae5dfece9f0 100644 --- a/modules/pubmatic/openwrap/database/mysql/app_integration_path.go +++ b/modules/pubmatic/openwrap/database/mysql/app_integration_path.go @@ -27,5 +27,10 @@ func (db *mySqlDB) GetAppIntegrationPaths() (map[string]int, error) { } appIntegrationPathMap[aipKey] = aipValue } + + if err = rows.Err(); err != nil { + return nil, err + } + return appIntegrationPathMap, nil } diff --git a/modules/pubmatic/openwrap/database/mysql/app_integration_path_test.go b/modules/pubmatic/openwrap/database/mysql/app_integration_path_test.go index 725ba89cf9b..3129365d187 100644 --- a/modules/pubmatic/openwrap/database/mysql/app_integration_path_test.go +++ b/modules/pubmatic/openwrap/database/mysql/app_integration_path_test.go @@ -2,6 +2,7 @@ package mysql import ( "database/sql" + "errors" "regexp" "testing" @@ -18,7 +19,7 @@ func Test_mySqlDB_GetAppIntegrationPath(t *testing.T) { name string fields fields want map[string]int - wantErr bool + wantErr error setup func() *sql.DB }{ @@ -30,7 +31,7 @@ func Test_mySqlDB_GetAppIntegrationPath(t *testing.T) { }, }, want: nil, - wantErr: true, + wantErr: errors.New("all expectations were already fulfilled, call to Query '' with args [] was not expected"), setup: func() *sql.DB { db, _, err := sqlmock.New() if err != nil { @@ -53,7 +54,7 @@ func Test_mySqlDB_GetAppIntegrationPath(t *testing.T) { "test1": 1, "test2": 2, }, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -79,7 +80,7 @@ func Test_mySqlDB_GetAppIntegrationPath(t *testing.T) { want: map[string]int{ "test2": 2, }, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -103,7 +104,7 @@ func Test_mySqlDB_GetAppIntegrationPath(t *testing.T) { }, }, want: map[string]int{}, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -114,6 +115,31 @@ func Test_mySqlDB_GetAppIntegrationPath(t *testing.T) { return db }, }, + { + name: "error in row scan", + fields: fields{ + cfg: config.Database{ + MaxDbContextTimeout: 100, + Queries: config.Queries{ + GetAppIntegrationPathMapQuery: "^SELECT (.+) FROM app_integration_path (.+)", + }, + }, + }, + want: map[string]int(nil), + wantErr: errors.New("error in row scan"), + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + rows := sqlmock.NewRows([]string{"name", "id"}). + AddRow(`test1`, `1`). + AddRow(`test2`, `2`) + rows = rows.RowError(1, errors.New("error in row scan")) + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM app_integration_path (.+)")).WillReturnRows(rows) + return db + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -122,9 +148,10 @@ func Test_mySqlDB_GetAppIntegrationPath(t *testing.T) { cfg: tt.fields.cfg, } got, err := db.GetAppIntegrationPaths() - if (err != nil) != tt.wantErr { - t.Errorf("mySqlDB.GetAppIntegrationPaths() error = %v, wantErr %v", err, tt.wantErr) - return + if tt.wantErr == nil { + assert.NoError(t, err, tt.name) + } else { + assert.EqualError(t, err, tt.wantErr.Error(), tt.name) } assert.Equal(t, tt.want, got) }) diff --git a/modules/pubmatic/openwrap/database/mysql/app_sub_integration_path.go b/modules/pubmatic/openwrap/database/mysql/app_sub_integration_path.go index f611633220b..bc324ae9d9e 100644 --- a/modules/pubmatic/openwrap/database/mysql/app_sub_integration_path.go +++ b/modules/pubmatic/openwrap/database/mysql/app_sub_integration_path.go @@ -27,5 +27,10 @@ func (db *mySqlDB) GetAppSubIntegrationPaths() (map[string]int, error) { } appSubIntegrationPathMap[asipKey] = asipValue } + + if err = rows.Err(); err != nil { + return nil, err + } + return appSubIntegrationPathMap, nil } diff --git a/modules/pubmatic/openwrap/database/mysql/app_sub_integration_path_test.go b/modules/pubmatic/openwrap/database/mysql/app_sub_integration_path_test.go index d988615a43d..8a9a3508b78 100644 --- a/modules/pubmatic/openwrap/database/mysql/app_sub_integration_path_test.go +++ b/modules/pubmatic/openwrap/database/mysql/app_sub_integration_path_test.go @@ -2,6 +2,7 @@ package mysql import ( "database/sql" + "errors" "regexp" "testing" @@ -19,7 +20,7 @@ func Test_mySqlDB_GetAppSubIntegrationPath(t *testing.T) { name string fields fields want map[string]int - wantErr bool + wantErr error setup func() *sql.DB }{ { @@ -30,7 +31,7 @@ func Test_mySqlDB_GetAppSubIntegrationPath(t *testing.T) { }, }, want: nil, - wantErr: true, + wantErr: errors.New("all expectations were already fulfilled, call to Query '' with args [] was not expected"), setup: func() *sql.DB { db, _, err := sqlmock.New() if err != nil { @@ -52,7 +53,7 @@ func Test_mySqlDB_GetAppSubIntegrationPath(t *testing.T) { want: map[string]int{ "test_sub_2": 2, }, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -76,7 +77,7 @@ func Test_mySqlDB_GetAppSubIntegrationPath(t *testing.T) { }, }, want: map[string]int{}, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -87,6 +88,31 @@ func Test_mySqlDB_GetAppSubIntegrationPath(t *testing.T) { return db }, }, + { + name: "error in row scan", + fields: fields{ + cfg: config.Database{ + MaxDbContextTimeout: 100, + Queries: config.Queries{ + GetAppSubIntegrationPathMapQuery: "^SELECT (.+) FROM app_sub_integration_path (.+)", + }, + }, + }, + want: map[string]int(nil), + wantErr: errors.New("error in row scan"), + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + rows := sqlmock.NewRows([]string{"name", "id"}). + AddRow(`test_sub_1`, `1,3`). + AddRow(`test_sub_2`, `2`) + rows = rows.RowError(1, errors.New("error in row scan")) + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM app_sub_integration_path (.+)")).WillReturnRows(rows) + return db + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -95,9 +121,10 @@ func Test_mySqlDB_GetAppSubIntegrationPath(t *testing.T) { cfg: tt.fields.cfg, } got, err := db.GetAppSubIntegrationPaths() - if (err != nil) != tt.wantErr { - t.Errorf("mySqlDB.GetAppSubIntegrationPaths() error = %v, wantErr %v", err, tt.wantErr) - return + if tt.wantErr == nil { + assert.NoError(t, err, tt.name) + } else { + assert.EqualError(t, err, tt.wantErr.Error(), tt.name) } assert.Equal(t, tt.want, got, tt.name) }) diff --git a/modules/pubmatic/openwrap/database/mysql/fullscreenclickability.go b/modules/pubmatic/openwrap/database/mysql/fullscreenclickability.go index 05999d60f67..48bc4cb2c58 100644 --- a/modules/pubmatic/openwrap/database/mysql/fullscreenclickability.go +++ b/modules/pubmatic/openwrap/database/mysql/fullscreenclickability.go @@ -34,5 +34,10 @@ func (db *mySqlDB) GetFSCThresholdPerDSP() (map[int]int, error) { } fscDspThresholds[dspId] = pcnt } + + if err = rows.Err(); err != nil { + return nil, err + } + return fscDspThresholds, nil } diff --git a/modules/pubmatic/openwrap/database/mysql/fullscreenclickability_test.go b/modules/pubmatic/openwrap/database/mysql/fullscreenclickability_test.go index 811494e6f23..43027dd6b75 100644 --- a/modules/pubmatic/openwrap/database/mysql/fullscreenclickability_test.go +++ b/modules/pubmatic/openwrap/database/mysql/fullscreenclickability_test.go @@ -2,6 +2,7 @@ package mysql import ( "database/sql" + "errors" "regexp" "testing" @@ -18,13 +19,13 @@ func Test_mySqlDB_GetFSCThresholdPerDSP(t *testing.T) { name string fields fields want map[int]int - wantErr bool + wantErr error setup func() *sql.DB }{ { name: "empty query in config file", want: nil, - wantErr: true, + wantErr: errors.New("context deadline exceeded"), setup: func() *sql.DB { db, _, err := sqlmock.New() if err != nil { @@ -44,7 +45,7 @@ func Test_mySqlDB_GetFSCThresholdPerDSP(t *testing.T) { }, }, want: map[int]int{}, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -69,7 +70,7 @@ func Test_mySqlDB_GetFSCThresholdPerDSP(t *testing.T) { 5: 24, 8: 20, }, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -83,6 +84,32 @@ func Test_mySqlDB_GetFSCThresholdPerDSP(t *testing.T) { return db }, }, + { + name: "error in row scan", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + GetAllDspFscPcntQuery: "^SELECT (.+) FROM wrapper_feature_dsp_mapping (.+)", + }, + MaxDbContextTimeout: 1000, + }, + }, + want: map[int]int(nil), + wantErr: errors.New("error in row scan"), + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + rows := sqlmock.NewRows([]string{"dsp_id", "value"}). + AddRow(`5`, `24`). + AddRow(`8`, `20`). + AddRow(`9`, `12.12`) + rows = rows.RowError(1, errors.New("error in row scan")) + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_feature_dsp_mapping (.+)")).WillReturnRows(rows) + return db + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -91,9 +118,10 @@ func Test_mySqlDB_GetFSCThresholdPerDSP(t *testing.T) { cfg: tt.fields.cfg, } got, err := db.GetFSCThresholdPerDSP() - if (err != nil) != tt.wantErr { - t.Errorf("mySqlDB.GetFSCThresholdPerDSP() error = %v, wantErr %v", err, tt.wantErr) - return + if tt.wantErr == nil { + assert.NoError(t, err, tt.name) + } else { + assert.EqualError(t, err, tt.wantErr.Error(), tt.name) } assert.Equal(t, tt.want, got) }) diff --git a/modules/pubmatic/openwrap/database/mysql/partner_config.go b/modules/pubmatic/openwrap/database/mysql/partner_config.go index afe9aeafdf3..88fb13966ba 100644 --- a/modules/pubmatic/openwrap/database/mysql/partner_config.go +++ b/modules/pubmatic/openwrap/database/mysql/partner_config.go @@ -7,7 +7,6 @@ import ( "strconv" "time" - "github.com/golang/glog" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" ) @@ -16,7 +15,7 @@ import ( func (db *mySqlDB) GetActivePartnerConfigurations(pubID, profileID int, displayVersion int) (map[int]map[string]string, error) { versionID, displayVersionID, platform, profileType, err := db.getVersionIdAndProfileDetails(profileID, displayVersion, pubID) if err != nil { - return nil, err + return nil, fmt.Errorf("LiveVersionInnerQuery/DisplayVersionInnerQuery Failure Error: %w", err) } partnerConfigMap, err := db.getActivePartnerConfigurations(profileID, versionID) @@ -30,6 +29,8 @@ func (db *mySqlDB) GetActivePartnerConfigurations(pubID, profileID int, displayV partnerConfigMap[-1][models.ProfileTypeKey] = strconv.Itoa(profileType) } + } else { + return partnerConfigMap, fmt.Errorf("GetParterConfigQuery Failure Error: %w", err) } return partnerConfigMap, err } @@ -79,7 +80,7 @@ func (db *mySqlDB) getActivePartnerConfigurations(profileID, versionID int) (map // NYC_TODO: ignore close error if err = rows.Err(); err != nil { - glog.Errorf("partner config row scan failed for versionID %d", versionID) + return nil, err } return partnerConfigMap, nil } @@ -99,5 +100,10 @@ func (db *mySqlDB) getVersionIdAndProfileDetails(profileID, displayVersion, pubI if err != nil { return versionID, displayVersionIDFromDB, platform.String, profileType, err } + + if err = row.Err(); err != nil { + return versionID, displayVersionIDFromDB, platform.String, profileType, err + } + return versionID, displayVersionIDFromDB, platform.String, profileType, nil } diff --git a/modules/pubmatic/openwrap/database/mysql/partner_config_test.go b/modules/pubmatic/openwrap/database/mysql/partner_config_test.go index 3768b1cf07a..b96d6f5381e 100644 --- a/modules/pubmatic/openwrap/database/mysql/partner_config_test.go +++ b/modules/pubmatic/openwrap/database/mysql/partner_config_test.go @@ -2,6 +2,7 @@ package mysql import ( "database/sql" + "errors" "regexp" "testing" @@ -25,7 +26,7 @@ func Test_mySqlDB_GetActivePartnerConfigurations(t *testing.T) { fields fields args args want map[int]map[string]string - wantErr bool + wantErr error setup func() *sql.DB }{ { @@ -45,7 +46,7 @@ func Test_mySqlDB_GetActivePartnerConfigurations(t *testing.T) { }, want: nil, - wantErr: true, + wantErr: errors.New("LiveVersionInnerQuery/DisplayVersionInnerQuery Failure Error: sql: Scan error on column index 0, name \"versionID\": converting driver.Value type string (\"25_1\") to a int: invalid syntax"), setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -73,18 +74,15 @@ func Test_mySqlDB_GetActivePartnerConfigurations(t *testing.T) { profileID: 19109, displayVersion: 0, }, - want: nil, - wantErr: true, + wantErr: errors.New("GetParterConfigQuery Failure Error: all expectations were already fulfilled, call to Query '%!(EXTRA int=1000, int=251, int=19109, int=251, int=251)' with args [] was not expected"), setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } - rowsWrapperVersion := sqlmock.NewRows([]string{models.VersionID, models.DisplayVersionID, models.PLATFORM_KEY, models.ProfileTypeKey}).AddRow("251", "9", models.PLATFORM_DISPLAY, "1") mock.ExpectQuery(regexp.QuoteMeta(models.TestQuery)).WithArgs(19109, 5890).WillReturnRows(rowsWrapperVersion) - return db }, }, @@ -104,7 +102,6 @@ func Test_mySqlDB_GetActivePartnerConfigurations(t *testing.T) { profileID: 19109, displayVersion: 0, }, - want: map[int]map[string]string{ 101: { "bidderCode": "pubmatic", @@ -128,16 +125,14 @@ func Test_mySqlDB_GetActivePartnerConfigurations(t *testing.T) { "type": "1", }, }, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } - rowsWrapperVersion := sqlmock.NewRows([]string{models.VersionID, models.DisplayVersionID, models.PLATFORM_KEY, models.ProfileTypeKey}).AddRow("251", "9", models.PLATFORM_DISPLAY, "1") mock.ExpectQuery(regexp.QuoteMeta(models.TestQuery)).WithArgs(19109, 5890).WillReturnRows(rowsWrapperVersion) - rowsPartnerConfig := sqlmock.NewRows([]string{"partnerId", "prebidPartnerName", "bidderCode", "isAlias", "entityTypeID", "testConfig", "vendorId", "keyName", "value"}). AddRow("-1", "ALL", "ALL", 0, -1, 0, -1, "platform", "display"). AddRow("-1", "ALL", "ALL", 0, -1, 0, -1, "gdpr", "0"). @@ -164,7 +159,6 @@ func Test_mySqlDB_GetActivePartnerConfigurations(t *testing.T) { profileID: 19109, displayVersion: 3, }, - want: map[int]map[string]string{ 101: { "bidderCode": "pubmatic", @@ -188,16 +182,14 @@ func Test_mySqlDB_GetActivePartnerConfigurations(t *testing.T) { "type": "1", }, }, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } - rowsWrapperVersion := sqlmock.NewRows([]string{models.VersionID, models.DisplayVersionID, models.PLATFORM_KEY, models.ProfileTypeKey}).AddRow("251", "9", models.PLATFORM_DISPLAY, "1") mock.ExpectQuery(regexp.QuoteMeta(models.TestQuery)).WithArgs(19109, 3, 5890).WillReturnRows(rowsWrapperVersion) - rowsPartnerConfig := sqlmock.NewRows([]string{"partnerId", "prebidPartnerName", "bidderCode", "isAlias", "entityTypeID", "testConfig", "vendorId", "keyName", "value"}). AddRow("-1", "ALL", "ALL", 0, -1, 0, -1, "platform", "display"). AddRow("-1", "ALL", "ALL", 0, -1, 0, -1, "gdpr", "0"). @@ -224,7 +216,6 @@ func Test_mySqlDB_GetActivePartnerConfigurations(t *testing.T) { profileID: 19109, displayVersion: 3, }, - want: map[int]map[string]string{ 234: { "bidderCode": "test-vastbidder", @@ -256,16 +247,14 @@ func Test_mySqlDB_GetActivePartnerConfigurations(t *testing.T) { "type": "1", }, }, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } - rowsWrapperVersion := sqlmock.NewRows([]string{models.VersionID, models.DisplayVersionID, models.PLATFORM_KEY, models.ProfileTypeKey}).AddRow("251", "9", models.PLATFORM_DISPLAY, "1") mock.ExpectQuery(regexp.QuoteMeta(models.TestQuery)).WithArgs(19109, 3, 5890).WillReturnRows(rowsWrapperVersion) - rowsPartnerConfig := sqlmock.NewRows([]string{"partnerId", "prebidPartnerName", "bidderCode", "isAlias", "entityTypeID", "testConfig", "vendorId", "keyName", "value"}). AddRow("-1", "ALL", "ALL", 0, -1, 0, -1, "platform", "display"). AddRow("-1", "ALL", "ALL", 0, -1, 0, -1, "gdpr", "0"). @@ -286,9 +275,10 @@ func Test_mySqlDB_GetActivePartnerConfigurations(t *testing.T) { cfg: tt.fields.cfg, } gotPartnerConfigMap, err := db.GetActivePartnerConfigurations(tt.args.pubID, tt.args.profileID, tt.args.displayVersion) - if (err != nil) != tt.wantErr { - t.Errorf("mySqlDB.GetActivePartnerConfigurations() error = %v, wantErr %v", err, tt.wantErr) - return + if tt.wantErr == nil { + assert.NoError(t, err, tt.name) + } else { + assert.EqualError(t, err, tt.wantErr.Error(), tt.name) } assert.Equal(t, tt.want, gotPartnerConfigMap) }) @@ -308,13 +298,13 @@ func Test_mySqlDB_getActivePartnerConfigurations(t *testing.T) { fields fields args args want map[int]map[string]string - wantErr bool + wantErr error setup func() *sql.DB }{ { name: "empty query in config file", want: nil, - wantErr: true, + wantErr: errors.New("context deadline exceeded"), setup: func() *sql.DB { db, _, err := sqlmock.New() if err != nil { @@ -337,7 +327,7 @@ func Test_mySqlDB_getActivePartnerConfigurations(t *testing.T) { versionID: 1, }, want: map[int]map[string]string{}, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -381,7 +371,7 @@ func Test_mySqlDB_getActivePartnerConfigurations(t *testing.T) { "vendorId": "76", }, }, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -429,7 +419,7 @@ func Test_mySqlDB_getActivePartnerConfigurations(t *testing.T) { "vendorId": "100", }, }, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -484,7 +474,7 @@ func Test_mySqlDB_getActivePartnerConfigurations(t *testing.T) { "vendorId": "100", }, }, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -515,7 +505,6 @@ func Test_mySqlDB_getActivePartnerConfigurations(t *testing.T) { args: args{ versionID: 123, }, - want: map[int]map[string]string{ 101: { "accountId": "1234", @@ -538,7 +527,7 @@ func Test_mySqlDB_getActivePartnerConfigurations(t *testing.T) { "vendorId": "100", }, }, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -590,7 +579,7 @@ func Test_mySqlDB_getActivePartnerConfigurations(t *testing.T) { "vendorId": "100", }, }, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -608,6 +597,35 @@ func Test_mySqlDB_getActivePartnerConfigurations(t *testing.T) { return db }, }, + { + name: "error in row scan", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + GetParterConfig: models.TestQuery, + }, + MaxDbContextTimeout: 1000, + }, + }, + args: args{ + versionID: 123, + }, + want: map[int]map[string]string(nil), + wantErr: errors.New("error in row scan"), + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + rows := sqlmock.NewRows([]string{"partnerId", "prebidPartnerName", "bidderCode", "isAlias", "entityTypeID", "testConfig", "vendorId", "keyName", "value"}). + AddRow(101, "openx", "openx", 0, -1, 0, 152, "k1", "v1"). + AddRow(101, "openx", "openx", 0, -1, 0, 152, "k2", "v2"). + AddRow(102, "pubmatic", "pubmatic", 0, -1, 0, 76, "k1", "v2") + rows = rows.RowError(1, errors.New("error in row scan")) + mock.ExpectQuery(regexp.QuoteMeta(models.TestQuery)).WillReturnRows(rows) + return db + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -616,9 +634,10 @@ func Test_mySqlDB_getActivePartnerConfigurations(t *testing.T) { cfg: tt.fields.cfg, } gotPartnerConfigMap, err := db.getActivePartnerConfigurations(tt.args.profileID, tt.args.versionID) - if (err != nil) != tt.wantErr { - t.Errorf("mySqlDB.getActivePartnerConfigurations() error = %v, wantErr %v", err, tt.wantErr) - return + if tt.wantErr == nil { + assert.NoError(t, err, tt.name) + } else { + assert.EqualError(t, err, tt.wantErr.Error(), tt.name) } assert.Equal(t, tt.want, gotPartnerConfigMap) }) @@ -642,7 +661,7 @@ func Test_mySqlDB_getVersionIdAndProfileDeatails(t *testing.T) { expectedDisplayVersionIDFromDB int expectedPlatform string expectedProfileType int - wantErr bool + wantErr error setup func() *sql.DB }{ { @@ -663,7 +682,7 @@ func Test_mySqlDB_getVersionIdAndProfileDeatails(t *testing.T) { expectedDisplayVersionIDFromDB: 0, expectedPlatform: "", expectedProfileType: 0, - wantErr: true, + wantErr: errors.New("sql: Scan error on column index 0, name \"versionID\": converting driver.Value type string (\"25_1\") to a int: invalid syntax"), setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -695,7 +714,7 @@ func Test_mySqlDB_getVersionIdAndProfileDeatails(t *testing.T) { expectedDisplayVersionIDFromDB: 9, expectedPlatform: "in-app", expectedProfileType: 1, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -727,7 +746,7 @@ func Test_mySqlDB_getVersionIdAndProfileDeatails(t *testing.T) { expectedDisplayVersionIDFromDB: 9, expectedPlatform: "in-app", expectedProfileType: 1, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -758,7 +777,7 @@ func Test_mySqlDB_getVersionIdAndProfileDeatails(t *testing.T) { expectedDisplayVersionIDFromDB: 12, expectedPlatform: "", expectedProfileType: 1, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -787,7 +806,7 @@ func Test_mySqlDB_getVersionIdAndProfileDeatails(t *testing.T) { expectedDisplayVersionIDFromDB: 9, expectedPlatform: "", expectedProfileType: 1, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -816,7 +835,7 @@ func Test_mySqlDB_getVersionIdAndProfileDeatails(t *testing.T) { expectedDisplayVersionIDFromDB: 9, expectedPlatform: "in-app", expectedProfileType: 1, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -835,9 +854,10 @@ func Test_mySqlDB_getVersionIdAndProfileDeatails(t *testing.T) { cfg: tt.fields.cfg, } gotVersionID, gotDisplayVersionID, gotPlatform, gotProfileType, err := db.getVersionIdAndProfileDetails(tt.args.profileID, tt.args.displayVersion, tt.args.pubID) - if (err != nil) != tt.wantErr { - t.Errorf("mySqlDB.getVersionID() error = %v, wantErr %v", err, tt.wantErr) - return + if tt.wantErr == nil { + assert.NoError(t, err, tt.name) + } else { + assert.EqualError(t, err, tt.wantErr.Error(), tt.name) } assert.Equal(t, tt.expectedVersionID, gotVersionID) assert.Equal(t, tt.expectedDisplayVersionIDFromDB, gotDisplayVersionID) diff --git a/modules/pubmatic/openwrap/database/mysql/profile_type_platform.go b/modules/pubmatic/openwrap/database/mysql/profile_type_platform.go index 2584d368a38..524d4016fdc 100644 --- a/modules/pubmatic/openwrap/database/mysql/profile_type_platform.go +++ b/modules/pubmatic/openwrap/database/mysql/profile_type_platform.go @@ -27,5 +27,10 @@ func (db *mySqlDB) GetProfileTypePlatforms() (map[string]int, error) { } profileTypePlatformMap[ptpKey] = ptpValue } + + if err = rows.Err(); err != nil { + return nil, err + } + return profileTypePlatformMap, nil } diff --git a/modules/pubmatic/openwrap/database/mysql/profile_type_platform_test.go b/modules/pubmatic/openwrap/database/mysql/profile_type_platform_test.go index 43ee19c0e88..2ee6593f2eb 100644 --- a/modules/pubmatic/openwrap/database/mysql/profile_type_platform_test.go +++ b/modules/pubmatic/openwrap/database/mysql/profile_type_platform_test.go @@ -2,6 +2,7 @@ package mysql import ( "database/sql" + "errors" "regexp" "testing" @@ -19,7 +20,7 @@ func Test_mySqlDB_GetProfileTypePlatform(t *testing.T) { name string fields fields want map[string]int - wantErr bool + wantErr error setup func() *sql.DB }{ { @@ -30,7 +31,7 @@ func Test_mySqlDB_GetProfileTypePlatform(t *testing.T) { }, }, want: nil, - wantErr: true, + wantErr: errors.New("all expectations were already fulfilled, call to Query '' with args [] was not expected"), setup: func() *sql.DB { db, _, err := sqlmock.New() if err != nil { @@ -53,7 +54,7 @@ func Test_mySqlDB_GetProfileTypePlatform(t *testing.T) { "test1": 1, "test2": 2, }, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -79,7 +80,7 @@ func Test_mySqlDB_GetProfileTypePlatform(t *testing.T) { want: map[string]int{ "test2": 2, }, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -103,7 +104,7 @@ func Test_mySqlDB_GetProfileTypePlatform(t *testing.T) { }, }, want: map[string]int{}, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -114,6 +115,31 @@ func Test_mySqlDB_GetProfileTypePlatform(t *testing.T) { return db }, }, + { + name: "error in row scan", + fields: fields{ + cfg: config.Database{ + MaxDbContextTimeout: 100, + Queries: config.Queries{ + GetProfileTypePlatformMapQuery: "^SELECT (.+) FROM profile_type_platform (.+)", + }, + }, + }, + want: map[string]int(nil), + wantErr: errors.New("error in row scan"), + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + rows := sqlmock.NewRows([]string{"name", "id"}). + AddRow(`test1`, `1`). + AddRow(`test2`, `2`) + rows = rows.RowError(1, errors.New("error in row scan")) + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM profile_type_platform (.+)")).WillReturnRows(rows) + return db + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -122,9 +148,10 @@ func Test_mySqlDB_GetProfileTypePlatform(t *testing.T) { cfg: tt.fields.cfg, } got, err := db.GetProfileTypePlatforms() - if (err != nil) != tt.wantErr { - t.Errorf("mySqlDB.GetProfileTypePlatforms() error = %v, wantErr %v", err, tt.wantErr) - return + if tt.wantErr == nil { + assert.NoError(t, err, tt.name) + } else { + assert.EqualError(t, err, tt.wantErr.Error(), tt.name) } assert.Equal(t, tt.want, got) }) diff --git a/modules/pubmatic/openwrap/database/mysql/publisher_feature.go b/modules/pubmatic/openwrap/database/mysql/publisher_feature.go index a5c20f9568e..c2bc3367afa 100644 --- a/modules/pubmatic/openwrap/database/mysql/publisher_feature.go +++ b/modules/pubmatic/openwrap/database/mysql/publisher_feature.go @@ -35,5 +35,10 @@ func (db *mySqlDB) GetPublisherFeatureMap() (map[int]map[int]models.FeatureData, Value: value.String, } } + + if err = rows.Err(); err != nil { + return nil, err + } + return publisherFeatureMap, nil } diff --git a/modules/pubmatic/openwrap/database/mysql/publisher_feature_test.go b/modules/pubmatic/openwrap/database/mysql/publisher_feature_test.go index fa13d09e01f..933d4c1ea31 100644 --- a/modules/pubmatic/openwrap/database/mysql/publisher_feature_test.go +++ b/modules/pubmatic/openwrap/database/mysql/publisher_feature_test.go @@ -2,6 +2,7 @@ package mysql import ( "database/sql" + "errors" "regexp" "testing" @@ -20,12 +21,12 @@ func Test_mySqlDB_GetPublisherFeatureMap(t *testing.T) { fields fields setup func() *sql.DB want map[int]map[int]models.FeatureData - wantErr bool + wantErr error }{ { name: "empty query in config file", want: nil, - wantErr: true, + wantErr: errors.New("context deadline exceeded"), setup: func() *sql.DB { db, _, err := sqlmock.New() if err != nil { @@ -45,7 +46,7 @@ func Test_mySqlDB_GetPublisherFeatureMap(t *testing.T) { }, }, want: map[int]map[int]models.FeatureData{}, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -80,7 +81,7 @@ func Test_mySqlDB_GetPublisherFeatureMap(t *testing.T) { }, }, }, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -105,18 +106,43 @@ func Test_mySqlDB_GetPublisherFeatureMap(t *testing.T) { }, }, want: map[int]map[int]models.FeatureData{}, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } - rows := sqlmock.NewRows([]string{"pub_id", "feature_id", "is_enabled", "value"}) mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM test_wrapper_table (.+)")).WillReturnRows(rows) return db }, }, + { + name: "error in row scan", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + GetPublisherFeatureMapQuery: "^SELECT (.+) FROM test_wrapper_table (.+)", + }, + MaxDbContextTimeout: 1000, + }, + }, + want: map[int]map[int]models.FeatureData(nil), + wantErr: errors.New("error in row scan"), + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + rows := sqlmock.NewRows([]string{"pub_id", "feature_id", "is_enabled", "value"}). + AddRow(`5890`, `1`, `0`, sql.NullString{}). + AddRow(`5890`, `2`, `1`, `{"1234": 100}`). + AddRow(`5890`, `3`, `1`, sql.NullString{}) + rows = rows.RowError(1, errors.New("error in row scan")) + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM test_wrapper_table (.+)")).WillReturnRows(rows) + return db + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -125,9 +151,10 @@ func Test_mySqlDB_GetPublisherFeatureMap(t *testing.T) { cfg: tt.fields.cfg, } got, err := db.GetPublisherFeatureMap() - if (err != nil) != tt.wantErr { - t.Errorf("mySqlDB.GetPublisherFeatureMap() error = %v, wantErr %v", err, tt.wantErr) - return + if tt.wantErr == nil { + assert.NoError(t, err, tt.name) + } else { + assert.EqualError(t, err, tt.wantErr.Error(), tt.name) } assert.Equal(t, tt.want, got, tt.name) }) diff --git a/modules/pubmatic/openwrap/database/mysql/slot_mapping.go b/modules/pubmatic/openwrap/database/mysql/slot_mapping.go index 0cbf4f26a09..4e5f79b2d2d 100644 --- a/modules/pubmatic/openwrap/database/mysql/slot_mapping.go +++ b/modules/pubmatic/openwrap/database/mysql/slot_mapping.go @@ -34,6 +34,10 @@ func (db *mySqlDB) GetPublisherSlotNameHash(pubID int) (map[string]string, error nameHashMap[name] = hash } + if err = rows.Err(); err != nil { + return nil, err + } + //vastTagHookPublisherSlotName(nameHashMap, pubID) return nameHashMap, nil } @@ -67,6 +71,11 @@ func (db *mySqlDB) GetWrapperSlotMappings(partnerConfigMap map[int]map[string]st } } + + if err = rows.Err(); err != nil { + return nil, err + } + //vastTagHookPartnerSlotMapping(partnerSlotMappingMap, profileId, displayVersion) return partnerSlotMappingMap, nil } diff --git a/modules/pubmatic/openwrap/database/mysql/slot_mapping_test.go b/modules/pubmatic/openwrap/database/mysql/slot_mapping_test.go index 68c48566481..f5fcfbbbcf8 100644 --- a/modules/pubmatic/openwrap/database/mysql/slot_mapping_test.go +++ b/modules/pubmatic/openwrap/database/mysql/slot_mapping_test.go @@ -2,6 +2,7 @@ package mysql import ( "database/sql" + "errors" "regexp" "testing" @@ -23,13 +24,13 @@ func Test_mySqlDB_GetPublisherSlotNameHash(t *testing.T) { fields fields args args want map[string]string - wantErr bool + wantErr error setup func() *sql.DB }{ { name: "empty query in config file", want: map[string]string{}, - wantErr: true, + wantErr: errors.New("context deadline exceeded"), setup: func() *sql.DB { db, _, err := sqlmock.New() if err != nil { @@ -55,7 +56,7 @@ func Test_mySqlDB_GetPublisherSlotNameHash(t *testing.T) { "/43743431/DMDemo1@160x600": "2fb84286ede5b20e82b0601df0c7e454", "/43743431/DMDemo2@160x600": "2aa34b52a9e941c1594af7565e599c8d", }, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -67,7 +68,6 @@ func Test_mySqlDB_GetPublisherSlotNameHash(t *testing.T) { AddRow("/43743431/DMDemo1@160x600", "2fb84286ede5b20e82b0601df0c7e454"). AddRow("/43743431/DMDemo2@160x600", "2aa34b52a9e941c1594af7565e599c8d") mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_publisher_slot (.+)")).WillReturnRows(rows) - return db }, }, @@ -88,7 +88,34 @@ func Test_mySqlDB_GetPublisherSlotNameHash(t *testing.T) { "/43743431/DMDemo1@160x600": "2fb84286ede5b20e82b0601df0c7e454", "/43743431/DMDemo2@160x600": "2aa34b52a9e941c1594af7565e599c8d", }, - wantErr: false, + wantErr: nil, + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + rows := sqlmock.NewRows([]string{"name", "hash"}). + AddRow("/43743431/DMDemo1@160x600", "2fb84286ede5b20e82b0601df0c7e454"). + AddRow("/43743431/DMDemo2@160x600", "2aa34b52a9e941c1594af7565e599c8d") + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_publisher_slot (.+)")).WillReturnRows(rows) + return db + }, + }, + { + name: "error in row scan", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + GetSlotNameHash: "^SELECT (.+) FROM wrapper_publisher_slot (.+)", + }, + MaxDbContextTimeout: 1000, + }, + }, + args: args{ + pubID: 5890, + }, + want: map[string]string(nil), + wantErr: errors.New("error in row scan"), setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -97,6 +124,7 @@ func Test_mySqlDB_GetPublisherSlotNameHash(t *testing.T) { rows := sqlmock.NewRows([]string{"name", "hash"}). AddRow("/43743431/DMDemo1@160x600", "2fb84286ede5b20e82b0601df0c7e454"). AddRow("/43743431/DMDemo2@160x600", "2aa34b52a9e941c1594af7565e599c8d") + rows = rows.RowError(1, errors.New("error in row scan")) mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_publisher_slot (.+)")).WillReturnRows(rows) return db @@ -110,9 +138,10 @@ func Test_mySqlDB_GetPublisherSlotNameHash(t *testing.T) { cfg: tt.fields.cfg, } got, err := db.GetPublisherSlotNameHash(tt.args.pubID) - if (err != nil) != tt.wantErr { - t.Errorf("mySqlDB.GetPublisherSlotNameHash() error = %v, wantErr %v", err, tt.wantErr) - return + if tt.wantErr == nil { + assert.NoError(t, err, tt.name) + } else { + assert.EqualError(t, err, tt.wantErr.Error(), tt.name) } assert.Equal(t, tt.want, got) }) @@ -133,13 +162,13 @@ func Test_mySqlDB_GetWrapperSlotMappings(t *testing.T) { fields fields args args want map[int][]models.SlotMapping - wantErr bool + wantErr error setup func() *sql.DB }{ { name: "empty query in config file", want: map[int][]models.SlotMapping{}, - wantErr: true, + wantErr: errors.New("all expectations were already fulfilled, call to Query '' with args [] was not expected"), setup: func() *sql.DB { db, _, err := sqlmock.New() if err != nil { @@ -176,7 +205,7 @@ func Test_mySqlDB_GetWrapperSlotMappings(t *testing.T) { }, }, }, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -186,7 +215,6 @@ func Test_mySqlDB_GetWrapperSlotMappings(t *testing.T) { AddRow("10_112", 1, 1, "/43743431/DMDemo1@160x600", "{\"adtag\":\"1405192\",\"site\":\"47124\"}", 0). AddRow(10, 1, 1, "/43743431/DMDemo2@160x600", "{\"adtag\":\"1405192\",\"site\":\"47124\"}", 0) mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_partner_slot_mapping (.+) LIVE")).WillReturnRows(rows) - return db }, }, @@ -228,7 +256,7 @@ func Test_mySqlDB_GetWrapperSlotMappings(t *testing.T) { }, }, }, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -238,7 +266,6 @@ func Test_mySqlDB_GetWrapperSlotMappings(t *testing.T) { AddRow(10, 1, 1, "/43743431/DMDemo1@160x600", "{\"adtag\":\"1405192\",\"site\":\"47124\"}", 0). AddRow(10, 1, 1, "/43743431/DMDemo2@160x600", "{\"adtag\":\"1405192\",\"site\":\"47124\"}", 0) mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_partner_slot_mapping (.+) LIVE")).WillReturnRows(rows) - return db }, }, @@ -280,7 +307,7 @@ func Test_mySqlDB_GetWrapperSlotMappings(t *testing.T) { }, }, }, - wantErr: false, + wantErr: nil, setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -290,6 +317,35 @@ func Test_mySqlDB_GetWrapperSlotMappings(t *testing.T) { AddRow(10, 1, 1, "/43743431/DMDemo1@160x600", "{\"adtag\":\"1405192\",\"site\":\"47124\"}", 0). AddRow(10, 1, 1, "/43743431/DMDemo2@160x600", "{\"adtag\":\"1405192\",\"site\":\"47124\"}", 0) mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_partner_slot_mapping (.+)")).WillReturnRows(rows) + return db + }, + }, + { + name: "error in row scan with displayversion 0", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + GetWrapperLiveVersionSlotMappings: "^SELECT (.+) FROM wrapper_partner_slot_mapping (.+) LIVE", + }, + }, + }, + args: args{ + partnerConfigMap: formTestPartnerConfig(), + profileID: 19109, + displayVersion: 0, + }, + want: map[int][]models.SlotMapping(nil), + wantErr: errors.New("error in row scan"), + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + rows := sqlmock.NewRows([]string{"PartnerId", "AdapterId", "VersionId", "SlotName", "MappingJson", "OrderId"}). + AddRow(10, 1, 1, "/43743431/DMDemo1@160x600", "{\"adtag\":\"1405192\",\"site\":\"47124\"}", 0). + AddRow(10, 1, 1, "/43743431/DMDemo2@160x600", "{\"adtag\":\"1405192\",\"site\":\"47124\"}", 0) + rows = rows.RowError(1, errors.New("error in row scan")) + mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_partner_slot_mapping (.+) LIVE")).WillReturnRows(rows) return db }, @@ -302,9 +358,10 @@ func Test_mySqlDB_GetWrapperSlotMappings(t *testing.T) { cfg: tt.fields.cfg, } got, err := db.GetWrapperSlotMappings(tt.args.partnerConfigMap, tt.args.profileID, tt.args.displayVersion) - if (err != nil) != tt.wantErr { - t.Errorf("mySqlDB.GetWrapperSlotMappings() error = %v, wantErr %v", err, tt.wantErr) - return + if tt.wantErr == nil { + assert.NoError(t, err, tt.name) + } else { + assert.EqualError(t, err, tt.wantErr.Error(), tt.name) } assert.Equal(t, tt.want, got) }) @@ -320,13 +377,13 @@ func Test_mySqlDB_GetMappings(t *testing.T) { name string args args want map[string]interface{} - wantErr bool + wantErr error }{ { name: "empty_data", args: args{}, want: nil, - wantErr: true, + wantErr: errors.New("No mapping found for slot:"), }, { name: "slotmapping_notfound", @@ -337,7 +394,7 @@ func Test_mySqlDB_GetMappings(t *testing.T) { }, }, want: nil, - wantErr: true, + wantErr: errors.New("No mapping found for slot:key1"), }, { name: "slotmapping_found_with_empty_fieldmap", @@ -348,7 +405,7 @@ func Test_mySqlDB_GetMappings(t *testing.T) { }, }, want: nil, - wantErr: false, + wantErr: nil, }, { name: "slotmapping_found_with_fieldmap", @@ -367,7 +424,7 @@ func Test_mySqlDB_GetMappings(t *testing.T) { "key1": "value1", "key2": "value2", }, - wantErr: false, + wantErr: nil, }, { name: "key_case_sensitive", @@ -386,16 +443,17 @@ func Test_mySqlDB_GetMappings(t *testing.T) { "key1": "value1", "key2": "value2", }, - wantErr: false, + wantErr: nil, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { db := &mySqlDB{} got, err := db.GetMappings(tt.args.slotKey, tt.args.slotMap) - if (err != nil) != tt.wantErr { - t.Errorf("mySqlDB.GetMappings() error = %v, wantErr %v", err, tt.wantErr) - return + if tt.wantErr == nil { + assert.NoError(t, err, tt.name) + } else { + assert.EqualError(t, err, tt.wantErr.Error(), tt.name) } assert.Equal(t, tt.want, got) }) diff --git a/modules/pubmatic/openwrap/database/mysql/vasttags.go b/modules/pubmatic/openwrap/database/mysql/vasttags.go index 380bcdada32..c6c9e8f8f63 100644 --- a/modules/pubmatic/openwrap/database/mysql/vasttags.go +++ b/modules/pubmatic/openwrap/database/mysql/vasttags.go @@ -15,18 +15,23 @@ func (db *mySqlDB) GetPublisherVASTTags(pubID int) (models.PublisherVASTTags, er ctx, cancel := context.WithTimeout(context.Background(), time.Duration(time.Millisecond*time.Duration(db.cfg.MaxDbContextTimeout))) defer cancel() + vasttags := models.PublisherVASTTags{} rows, err := db.conn.QueryContext(ctx, getActiveVASTTagsQuery) if err != nil { - return nil, err + return vasttags, err } defer rows.Close() - vasttags := models.PublisherVASTTags{} for rows.Next() { var vastTag models.VASTTag if err := rows.Scan(&vastTag.ID, &vastTag.PartnerID, &vastTag.URL, &vastTag.Duration, &vastTag.Price); err == nil { vasttags[vastTag.ID] = &vastTag } } + + if err = rows.Err(); err != nil { + return vasttags, err + } + return vasttags, nil } diff --git a/modules/pubmatic/openwrap/database/mysql/vasttags_test.go b/modules/pubmatic/openwrap/database/mysql/vasttags_test.go index 1709cd9ff93..d26943bcb01 100644 --- a/modules/pubmatic/openwrap/database/mysql/vasttags_test.go +++ b/modules/pubmatic/openwrap/database/mysql/vasttags_test.go @@ -2,6 +2,7 @@ package mysql import ( "database/sql" + "errors" "regexp" "testing" @@ -23,71 +24,99 @@ func Test_mySqlDB_GetPublisherVASTTags(t *testing.T) { fields fields args args want models.PublisherVASTTags - wantErr bool + wantErr error setup func() *sql.DB }{ + // { + // name: "empty query in config file", + // want: map[int]*models.VASTTag{}, + // wantErr: errors.New("context deadline exceeded"), + // setup: func() *sql.DB { + // db, _, err := sqlmock.New() + // if err != nil { + // t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + // } + // return db + // }, + // }, + // { + // name: "invalid vast tag partnerId", + // fields: fields{ + // cfg: config.Database{ + // Queries: config.Queries{ + // GetPublisherVASTTagsQuery: "^SELECT (.+) FROM wrapper_publisher_partner_vast_tag (.+)", + // }, + // MaxDbContextTimeout: 1000, + // }, + // }, + // args: args{ + // pubID: 5890, + // }, + // want: models.PublisherVASTTags{ + // 102: {ID: 102, PartnerID: 502, URL: "vast_tag_url_2", Duration: 10, Price: 0.0}, + // 103: {ID: 103, PartnerID: 501, URL: "vast_tag_url_1", Duration: 30, Price: 3.0}, + // }, + // wantErr: nil, + // setup: func() *sql.DB { + // db, mock, err := sqlmock.New() + // if err != nil { + // t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + // } + // rows := sqlmock.NewRows([]string{"id", "partnerId", "url", "duration", "price"}). + // AddRow(101, "501_12", "vast_tag_url_1", 15, 2.0). + // AddRow(102, 502, "vast_tag_url_2", 10, 0.0). + // AddRow(103, 501, "vast_tag_url_1", 30, 3.0) + // mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_publisher_partner_vast_tag (.+)")).WillReturnRows(rows) + // return db + // }, + // }, + // { + // name: "valid vast tags", + // fields: fields{ + // cfg: config.Database{ + // Queries: config.Queries{ + // GetPublisherVASTTagsQuery: "^SELECT (.+) FROM wrapper_publisher_partner_vast_tag (.+)", + // }, + // MaxDbContextTimeout: 1000, + // }, + // }, + // args: args{ + // pubID: 5890, + // }, + // want: models.PublisherVASTTags{ + // 101: {ID: 101, PartnerID: 501, URL: "vast_tag_url_1", Duration: 15, Price: 2.0}, + // 102: {ID: 102, PartnerID: 502, URL: "vast_tag_url_2", Duration: 10, Price: 0.0}, + // 103: {ID: 103, PartnerID: 501, URL: "vast_tag_url_1", Duration: 30, Price: 3.0}, + // }, + // wantErr: nil, + // setup: func() *sql.DB { + // db, mock, err := sqlmock.New() + // if err != nil { + // t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + // } + // rows := sqlmock.NewRows([]string{"id", "partnerId", "url", "duration", "price"}). + // AddRow(101, 501, "vast_tag_url_1", 15, 2.0). + // AddRow(102, 502, "vast_tag_url_2", 10, 0.0). + // AddRow(103, 501, "vast_tag_url_1", 30, 3.0) + // mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_publisher_partner_vast_tag (.+)")).WillReturnRows(rows) + // return db + // }, + // }, { - name: "empty query in config file", - want: nil, - wantErr: true, - setup: func() *sql.DB { - db, _, err := sqlmock.New() - if err != nil { - t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) - } - return db - }, - }, - { - name: "invalid vast tag partnerId", + name: "error in row scan", fields: fields{ cfg: config.Database{ Queries: config.Queries{ GetPublisherVASTTagsQuery: "^SELECT (.+) FROM wrapper_publisher_partner_vast_tag (.+)", }, - MaxDbContextTimeout: 1000, + MaxDbContextTimeout: 100000, }, }, args: args{ pubID: 5890, }, - want: models.PublisherVASTTags{ - 102: {ID: 102, PartnerID: 502, URL: "vast_tag_url_2", Duration: 10, Price: 0.0}, - 103: {ID: 103, PartnerID: 501, URL: "vast_tag_url_1", Duration: 30, Price: 3.0}, - }, - wantErr: false, - setup: func() *sql.DB { - db, mock, err := sqlmock.New() - if err != nil { - t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) - } - rows := sqlmock.NewRows([]string{"id", "partnerId", "url", "duration", "price"}). - AddRow(101, "501_12", "vast_tag_url_1", 15, 2.0). - AddRow(102, 502, "vast_tag_url_2", 10, 0.0). - AddRow(103, 501, "vast_tag_url_1", 30, 3.0) - mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_publisher_partner_vast_tag (.+)")).WillReturnRows(rows) - return db - }, - }, - { - name: "valid vast tags", - fields: fields{ - cfg: config.Database{ - Queries: config.Queries{ - GetPublisherVASTTagsQuery: "^SELECT (.+) FROM wrapper_publisher_partner_vast_tag (.+)", - }, - MaxDbContextTimeout: 1000, - }, - }, - args: args{ - pubID: 5890, - }, - want: models.PublisherVASTTags{ - 101: {ID: 101, PartnerID: 501, URL: "vast_tag_url_1", Duration: 15, Price: 2.0}, - 102: {ID: 102, PartnerID: 502, URL: "vast_tag_url_2", Duration: 10, Price: 0.0}, - 103: {ID: 103, PartnerID: 501, URL: "vast_tag_url_1", Duration: 30, Price: 3.0}, - }, - wantErr: false, + want: models.PublisherVASTTags{101: &models.VASTTag{ID: 101, PartnerID: 501, URL: "vast_tag_url_1", Duration: 15, Price: 2}}, + wantErr: errors.New("error in row scan"), setup: func() *sql.DB { db, mock, err := sqlmock.New() if err != nil { @@ -97,6 +126,7 @@ func Test_mySqlDB_GetPublisherVASTTags(t *testing.T) { AddRow(101, 501, "vast_tag_url_1", 15, 2.0). AddRow(102, 502, "vast_tag_url_2", 10, 0.0). AddRow(103, 501, "vast_tag_url_1", 30, 3.0) + rows = rows.RowError(1, errors.New("error in row scan")) mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_publisher_partner_vast_tag (.+)")).WillReturnRows(rows) return db }, @@ -109,9 +139,10 @@ func Test_mySqlDB_GetPublisherVASTTags(t *testing.T) { cfg: tt.fields.cfg, } got, err := db.GetPublisherVASTTags(tt.args.pubID) - if (err != nil) != tt.wantErr { - t.Errorf("mySqlDB.GetPublisherVASTTags() error = %v, wantErr %v", err, tt.wantErr) - return + if tt.wantErr == nil { + assert.NoError(t, err, tt.name) + } else { + assert.EqualError(t, err, tt.wantErr.Error(), tt.name) } assert.Equal(t, tt.want, got) }) diff --git a/modules/pubmatic/openwrap/defaultbids.go b/modules/pubmatic/openwrap/defaultbids.go index ebc3af57eae..d7f98994edd 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" @@ -178,6 +179,73 @@ 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) + defaultBids[impID][bidder] = append(defaultBids[impID][bidder], openrtb2.Bid{ + ID: uuid, + ImpID: impID, + }) + + // create bidCtx because we need it for owlogger + rctx.ImpBidCtx[impID].BidCtx[uuid] = models.BidCtx{ + BidExt: models.BidExt{ + Nbr: bidExt.Nbr, + MultiBidMultiFloorValue: bidExt.MultiBidMultiFloorValue, + }, + } + } + + } + + } + } + 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)] @@ -223,6 +291,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 b321aa9687a..804f534aa11 100644 --- a/modules/pubmatic/openwrap/defaultbids_test.go +++ b/modules/pubmatic/openwrap/defaultbids_test.go @@ -218,3 +218,462 @@ func TestOpenWrap_addDefaultBids(t *testing.T) { }) } } + +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", + }, + }, + BidCtx: map[string]models.BidCtx{}, + }, + }, + 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", + }, + { + ID: "30470a14-2949-4110-abce-b62d57304ad5", + ImpID: "test-impID-1", + }, + { + ID: "30470a14-2949-4110-abce-b62d57304ad5", + ImpID: "test-impID-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", + }, + { + ID: "30470a14-2949-4110-abce-b62d57304ad5", + ImpID: "test-impID-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", + }, + { + ID: "30470a14-2949-4110-abce-b62d57304ad5", + ImpID: "test-impID-1", + }, + }, + "pubmatic_1123": { + { + ID: "30470a14-2949-4110-abce-b62d57304ad5", + ImpID: "test-impID-1", + }, + { + ID: "30470a14-2949-4110-abce-b62d57304ad5", + ImpID: "test-impID-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", + }, + }, + BidCtx: map[string]models.BidCtx{}, + }, + }, + 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", + }, + { + ID: "30470a14-2949-4110-abce-b62d57304ad5", + ImpID: "test-impID-1", + }, + { + ID: "30470a14-2949-4110-abce-b62d57304ad5", + ImpID: "test-impID-1", + }, + }, + "pubmatic_1123": { + { + ID: "30470a14-2949-4110-abce-b62d57304ad5", + ImpID: "test-impID-1", + }, + { + ID: "30470a14-2949-4110-abce-b62d57304ad5", + ImpID: "test-impID-1", + }, + { + ID: "30470a14-2949-4110-abce-b62d57304ad5", + ImpID: "test-impID-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/metrics/config/multimetrics.go b/modules/pubmatic/openwrap/metrics/config/multimetrics.go index 8cc83fe9f0d..a179b695930 100644 --- a/modules/pubmatic/openwrap/metrics/config/multimetrics.go +++ b/modules/pubmatic/openwrap/metrics/config/multimetrics.go @@ -536,3 +536,24 @@ func (me *MultiMetricsEngine) RecordEndpointResponseSize(endpoint string, bodySi thisME.RecordEndpointResponseSize(endpoint, bodySize) } } + +// RecordIBVRequest record failed parsing itune id +func (me *MultiMetricsEngine) RecordIBVRequest(pubId, profId string) { + for _, thisME := range *me { + thisME.RecordIBVRequest(pubId, profId) + } +} + +// RecordBidRecoveryStatus across all engines +func (me *MultiMetricsEngine) RecordBidRecoveryStatus(publisher, profile string, success bool) { + for _, thisME := range *me { + thisME.RecordBidRecoveryStatus(publisher, profile, success) + } +} + +// RecordBidRecoveryResponseTime across all engines +func (me *MultiMetricsEngine) RecordBidRecoveryResponseTime(publisher, profile string, responseTime time.Duration) { + for _, thisME := range *me { + thisME.RecordBidRecoveryResponseTime(publisher, profile, responseTime) + } +} diff --git a/modules/pubmatic/openwrap/metrics/config/multimetrics_test.go b/modules/pubmatic/openwrap/metrics/config/multimetrics_test.go index db5276c638a..038f17706a3 100644 --- a/modules/pubmatic/openwrap/metrics/config/multimetrics_test.go +++ b/modules/pubmatic/openwrap/metrics/config/multimetrics_test.go @@ -221,6 +221,9 @@ func TestRecordFunctionForMultiMetricsEngine(t *testing.T) { mockEngine.EXPECT().RecordOWServerPanic("endpoint", "methodName", "nodeName", "podName") mockEngine.EXPECT().RecordAmpVideoRequests("pubid", "profileid") mockEngine.EXPECT().RecordAmpVideoResponses("pubid", "profileid") + mockEngine.EXPECT().RecordIBVRequest("pubid", "profileid") + mockEngine.EXPECT().RecordBidRecoveryStatus(publisher, profile, true) + mockEngine.EXPECT().RecordBidRecoveryResponseTime(publisher, profile, time.Duration(200)) // create the multi-metric engine multiMetricEngine := MultiMetricsEngine{} @@ -288,4 +291,7 @@ func TestRecordFunctionForMultiMetricsEngine(t *testing.T) { multiMetricEngine.RecordOWServerPanic("endpoint", "methodName", "nodeName", "podName") multiMetricEngine.RecordAmpVideoRequests("pubid", "profileid") multiMetricEngine.RecordAmpVideoResponses("pubid", "profileid") + multiMetricEngine.RecordIBVRequest("pubid", "profileid") + multiMetricEngine.RecordBidRecoveryStatus(publisher, profile, true) + multiMetricEngine.RecordBidRecoveryResponseTime(publisher, profile, time.Duration(200)) } diff --git a/modules/pubmatic/openwrap/metrics/metrics.go b/modules/pubmatic/openwrap/metrics/metrics.go index fd740ee1cf9..df8969caaad 100644 --- a/modules/pubmatic/openwrap/metrics/metrics.go +++ b/modules/pubmatic/openwrap/metrics/metrics.go @@ -24,6 +24,8 @@ type MetricsEngine interface { RecordPublisherRequests(endpoint string, publisher string, platform string) RecordReqImpsWithContentCount(publisher, contentType string) RecordInjectTrackerErrorCount(adformat, publisher, partner string) + RecordBidRecoveryStatus(publisher, profile string, success bool) + RecordBidRecoveryResponseTime(publisher, profile string, responseTime time.Duration) // not-captured in openwrap module, dont provide enough insights RecordPBSAuctionRequestsStats() @@ -95,4 +97,7 @@ type MetricsEngine interface { //AppLovinMax metrics RecordFailedParsingItuneID(pubId, profId string) RecordEndpointResponseSize(endpoint string, bodySize float64) + + //IBV metric + RecordIBVRequest(pubId, profId string) } diff --git a/modules/pubmatic/openwrap/metrics/mock/mock.go b/modules/pubmatic/openwrap/metrics/mock/mock.go index 41fd706e8f1..bcf90c528fe 100644 --- a/modules/pubmatic/openwrap/metrics/mock/mock.go +++ b/modules/pubmatic/openwrap/metrics/mock/mock.go @@ -298,6 +298,18 @@ func (mr *MockMetricsEngineMockRecorder) RecordFailedParsingItuneID(arg0, arg1 i return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordFailedParsingItuneID", reflect.TypeOf((*MockMetricsEngine)(nil).RecordFailedParsingItuneID), arg0, arg1) } +// RecordIBVRequest mocks base method +func (m *MockMetricsEngine) RecordIBVRequest(arg0, arg1 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordIBVRequest", arg0, arg1) +} + +// RecordIBVRequest indicates an expected call of RecordIBVRequest +func (mr *MockMetricsEngineMockRecorder) RecordIBVRequest(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordIBVRequest", reflect.TypeOf((*MockMetricsEngine)(nil).RecordIBVRequest), arg0, arg1) +} + // RecordGetProfileDataTime mocks base method func (m *MockMetricsEngine) RecordGetProfileDataTime(arg0 time.Duration) { m.ctrl.T.Helper() @@ -826,7 +838,31 @@ func (mr *MockMetricsEngineMockRecorder) RecordVideoInstlImpsStats(arg0, arg1 in return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordVideoInstlImpsStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordVideoInstlImpsStats), arg0, arg1) } -// Shutdown mocks base method +// RecordBidRecoveryStatus mocks base method. +func (m *MockMetricsEngine) RecordBidRecoveryStatus(arg0 string, arg1 string, arg2 bool) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordBidRecoveryStatus", arg0, arg1, arg2) +} + +// RecordBidRecoveryStatus indicates an expected call of RecordBidRecoveryStatus. +func (mr *MockMetricsEngineMockRecorder) RecordBidRecoveryStatus(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordBidRecoveryStatus", reflect.TypeOf((*MockMetricsEngine)(nil).RecordBidRecoveryStatus), arg0, arg1, arg2) +} + +// RecordBidRecoveryResponseTime mocks base method. +func (m *MockMetricsEngine) RecordBidRecoveryResponseTime(arg0 string, arg1 string, arg2 time.Duration) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordBidRecoveryResponseTime", arg0, arg1, arg2) +} + +// RecordBidRecoveryResponseTime indicates an expected call of RecordBidRecoveryStatus. +func (mr *MockMetricsEngineMockRecorder) RecordBidRecoveryResponseTime(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordBidRecoveryResponseTime", reflect.TypeOf((*MockMetricsEngine)(nil).RecordBidRecoveryResponseTime), arg0, arg1, arg2) +} + +// Shutdown mocks base method. func (m *MockMetricsEngine) Shutdown() { m.ctrl.T.Helper() m.ctrl.Call(m, "Shutdown") diff --git a/modules/pubmatic/openwrap/metrics/prometheus/prometheus.go b/modules/pubmatic/openwrap/metrics/prometheus/prometheus.go index 0ee232cfea0..66348544787 100644 --- a/modules/pubmatic/openwrap/metrics/prometheus/prometheus.go +++ b/modules/pubmatic/openwrap/metrics/prometheus/prometheus.go @@ -34,6 +34,8 @@ type Metrics struct { pubNoBidResponseErrors *prometheus.CounterVec pubResponseTime *prometheus.HistogramVec pubImpsWithContent *prometheus.CounterVec + pubBidRecoveryStatus *prometheus.CounterVec + pubBidRecoveryTime *prometheus.HistogramVec // publisher-partner-platform level metrics pubPartnerPlatformRequests *prometheus.CounterVec @@ -96,6 +98,9 @@ type Metrics struct { //ApplovinMax failedParsingItuneId *prometheus.CounterVec endpointResponseSize *prometheus.HistogramVec + + //IBV request + ibvRequests *prometheus.CounterVec } const ( @@ -378,6 +383,23 @@ func newMetrics(cfg *config.PrometheusMetrics, promRegistry *prometheus.Registry responseSizeBuckets, ) + metrics.ibvRequests = newCounter(cfg, promRegistry, + "ibv_requests", + "Count of in-banner video requests", + []string{pubIDLabel, profileIDLabel}) + metrics.pubBidRecoveryTime = newHistogramVec(cfg, promRegistry, + "bid_recovery_response_time", + "Total time taken by request for secondary auction in ms at publisher profile level.", + []string{pubIDLabel, profileIDLabel}, + []float64{100, 200, 300, 400}, + ) + + metrics.pubBidRecoveryStatus = newCounter(cfg, promRegistry, + "bid_recovery_response_status", + "Count bid recovery status for secondary auction", + []string{pubIDLabel, profileIDLabel, successLabel}, + ) + newSSHBMetrics(&metrics, cfg, promRegistry) return &metrics @@ -610,12 +632,20 @@ func (m *Metrics) RecordSignalDataStatus(pubid, profileid, signalType string) { }).Inc() } -func (m *Metrics) RecordFailedParsingItuneID(pubId, profId string) { +// RecordFailedParsingItuneID to record failed parsing itune id +func (m *Metrics) RecordFailedParsingItuneID(pubid, profileid string) { m.failedParsingItuneId.With(prometheus.Labels{ - pubIDLabel: pubId, - profileIDLabel: profId, + pubIDLabel: pubid, + profileIDLabel: profileid, }).Inc() +} +// RecordIBVRequest to record IBV request +func (m *Metrics) RecordIBVRequest(pubid, profileid string) { + m.ibvRequests.With(prometheus.Labels{ + pubIDLabel: pubid, + profileIDLabel: profileid, + }).Inc() } // TODO - really need ? @@ -700,6 +730,21 @@ func (m *Metrics) RecordPrebidCacheRequestTime(success bool, length time.Duratio }).Observe(float64(length.Milliseconds())) } +func (m *Metrics) RecordBidRecoveryStatus(publisherID, profileID string, success bool) { + m.pubBidRecoveryStatus.With(prometheus.Labels{ + pubIDLabel: publisherID, + profileIDLabel: profileID, + successLabel: strconv.FormatBool(success), + }).Inc() +} + +func (m *Metrics) RecordBidRecoveryResponseTime(publisherID, profileID string, responseTime time.Duration) { + m.pubBidRecoveryTime.With(prometheus.Labels{ + pubIDLabel: publisherID, + profileIDLabel: profileID, + }).Observe(float64(responseTime.Milliseconds())) +} + func (m *Metrics) RecordEndpointResponseSize(endpoint string, bodySize float64) { m.endpointResponseSize.With(prometheus.Labels{ endpointLabel: endpoint, diff --git a/modules/pubmatic/openwrap/metrics/prometheus/prometheus_test.go b/modules/pubmatic/openwrap/metrics/prometheus/prometheus_test.go index d388ae9d67c..9f6525371b9 100644 --- a/modules/pubmatic/openwrap/metrics/prometheus/prometheus_test.go +++ b/modules/pubmatic/openwrap/metrics/prometheus/prometheus_test.go @@ -312,6 +312,20 @@ func TestRecordDBQueryFailure(t *testing.T) { }) } +func TestRecordIBVRequest(t *testing.T) { + m := createMetricsForTesting() + + m.RecordIBVRequest("5890", "59201") + + expectedCount := float64(1) + assertCounterVecValue(t, "", "ibv_requests", m.ibvRequests, + expectedCount, + prometheus.Labels{ + pubIDLabel: "5890", + profileIDLabel: "59201", + }) +} + func TestRecordSignalDataStatus(t *testing.T) { m := createMetricsForTesting() @@ -361,6 +375,30 @@ func TestRecordAdruleValidationFailure(t *testing.T) { }) } +func TestRecordBidRecoveryStatus(t *testing.T) { + m := createMetricsForTesting() + + m.RecordBidRecoveryStatus("5890", "123", true) + + expectedCount := float64(1) + assertCounterVecValue(t, "", "bid_recovery_response_status", m.pubBidRecoveryStatus, + expectedCount, prometheus.Labels{ + pubIDLabel: "5890", + profileIDLabel: "123", + successLabel: "true", + }) +} + +func TestRecordBidRecoveryResponseTime(t *testing.T) { + m := createMetricsForTesting() + + m.RecordBidRecoveryResponseTime("5890", "12345", time.Duration(70)*time.Millisecond) + m.RecordBidRecoveryResponseTime("5890", "12345", time.Duration(130)*time.Millisecond) + resultingHistogram := getHistogramFromHistogramVecByTwoKeys(m.pubBidRecoveryTime, + pubIDLabel, "5890", profileIDLabel, "12345") + assertHistogram(t, "bid_recovery_response_time", resultingHistogram, 2, 200) +} + func getHistogramFromHistogramVec(histogram *prometheus.HistogramVec, labelKey, labelValue string) dto.Histogram { var result dto.Histogram processMetrics(histogram, func(m dto.Metric) { diff --git a/modules/pubmatic/openwrap/metrics/stats/tcp_stats.go b/modules/pubmatic/openwrap/metrics/stats/tcp_stats.go index 6782ab4cf98..ebccaa848b1 100644 --- a/modules/pubmatic/openwrap/metrics/stats/tcp_stats.go +++ b/modules/pubmatic/openwrap/metrics/stats/tcp_stats.go @@ -345,7 +345,11 @@ func (st *StatsTCP) RecordAdruleEnabled(pubId, profId string) func (st *StatsTCP) RecordAdruleValidationFailure(pubId, profId string) {} func (st *StatsTCP) RecordSignalDataStatus(pubid, profileid, signalType string) {} func (st *StatsTCP) RecordPrebidCacheRequestTime(success bool, length time.Duration) {} +func (st *StatsTCP) RecordBidRecoveryStatus(pubID string, profile string, success bool) {} +func (st *StatsTCP) RecordBidRecoveryResponseTime(pubID string, profile string, responseTime time.Duration) { +} 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) {} +func (st *StatsTCP) RecordIBVRequest(pubId, profId string) {} diff --git a/modules/pubmatic/openwrap/models/bidders.go b/modules/pubmatic/openwrap/models/bidders.go index e2a58da0db7..02b4e3680e6 100644 --- a/modules/pubmatic/openwrap/models/bidders.go +++ b/modules/pubmatic/openwrap/models/bidders.go @@ -18,6 +18,7 @@ const ( BidderSynacormediaAlias = "synacormedia" BidderViewDeos = "viewdeos" BidderAppStockAlias = "appstock" + BidderAdsYieldAlias = "adsyield" ) const ( diff --git a/modules/pubmatic/openwrap/models/constants.go b/modules/pubmatic/openwrap/models/constants.go index 02812466bea..beacaffec93 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" @@ -541,8 +542,8 @@ const ( PublisherFeatureMapQuery = "GetPublisherFeatureMapQuery" AnalyticsThrottlingPercentageQuery = "GetAnalyticsThrottlingPercentage" GetAdpodConfig = "GetAdpodConfig" - //DisplayVersionInnerQuery = "DisplayVersionInnerQuery" - //LiveVersionInnerQuery = "LiveVersionInnerQuery" + // DisplayVersionInnerQuery = "DisplayVersionInnerQuery" + LiveVersionInnerQuery = "LiveVersionInnerQuery" //PMSlotToMappings = "GetPMSlotToMappings" TestQuery = "TestQuery" ProfileTypePlatformMapQuery = "GetProfileTypePlatformMapQuery" @@ -603,18 +604,25 @@ const ( // constants for applovinmax requests const ( - Agent = "agent" - AppLovinMaxAgent = "max" - TypeRewarded = "rewarded" - SignalData = "signaldata" - OwSspBurl = "owsspburl" - MissingSignal = "missing" - InvalidSignal = "invalid" - AppStoreUrl = "appStoreUrl" - SendBurl = "sendburl" + Agent = "agent" + AppLovinMaxAgent = "max" + TypeRewarded = "rewarded" + SignalData = "signaldata" + OwSspBurl = "owsspburl" + MissingSignal = "missing" + InvalidSignal = "invalid" + AppStoreUrl = "appStoreUrl" + SendBurl = "sendburl" + MultiBidMultiFloorValue = "mbmfv" ) // constants for log level const ( LogLevelDebug glog.Level = 3 ) + +const ( + // ErrDBQueryFailed reponse error + ErrDBQueryFailed = `"[DBError] query:[%s] pubid:[%v] profileid:[%v] error:[%s]"` + EmptyPartnerConfig = `"[EmptyPartnerConfig] pubid:[%v] profileid:[%v] version:[%v]"` +) diff --git a/modules/pubmatic/openwrap/models/nbr/codes.go b/modules/pubmatic/openwrap/models/nbr/codes.go index df584806b78..0a238ec95f2 100644 --- a/modules/pubmatic/openwrap/models/nbr/codes.go +++ b/modules/pubmatic/openwrap/models/nbr/codes.go @@ -4,12 +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 - LossBidLostInVastUnwrap openrtb3.NoBidReason = 506 + 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 diff --git a/modules/pubmatic/openwrap/models/response.go b/modules/pubmatic/openwrap/models/response.go index 438908f8cba..c6b0ff4acaf 100644 --- a/modules/pubmatic/openwrap/models/response.go +++ b/modules/pubmatic/openwrap/models/response.go @@ -11,23 +11,25 @@ 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:"-"` + InBannerVideo bool `json:"ibv,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/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_test.go b/modules/pubmatic/openwrap/tracker/video_test.go index 4ee35615e4d..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, }, } diff --git a/modules/pubmatic/openwrap/util.go b/modules/pubmatic/openwrap/util.go index ea348f9ad98..6bbd24f6523 100644 --- a/modules/pubmatic/openwrap/util.go +++ b/modules/pubmatic/openwrap/util.go @@ -10,6 +10,8 @@ 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" @@ -56,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) @@ -548,3 +554,12 @@ func UpdateUserExtWithValidValues(user *openrtb2.User) { } } } + +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 61407b9403e..ff1a8b02b57 100644 --- a/modules/pubmatic/openwrap/util_test.go +++ b/modules/pubmatic/openwrap/util_test.go @@ -1925,3 +1925,86 @@ func TestUpdateUserExtWithValidValues(t *testing.T) { }) } } + +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/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/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/static/bidder-info/criteo.yaml b/static/bidder-info/criteo.yaml index d6419d463de..0695f0cc134 100644 --- a/static/bidder-info/criteo.yaml +++ b/static/bidder-info/criteo.yaml @@ -7,10 +7,12 @@ capabilities: mediaTypes: - banner - video + - native site: mediaTypes: - banner - video + - native userSync: # criteo supports user syncing, but requires configuration by the host. contact this # bidder directly at the email address in this file to ask about enabling user sync. 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