From 131439b55d04c93c8ddf1c76c9c92ac7637988b7 Mon Sep 17 00:00:00 2001 From: pm-avinash-kapre <112699665+AvinashKapre@users.noreply.github.com> Date: Wed, 9 Oct 2024 14:17:11 +0530 Subject: [PATCH 1/9] UOE-11191: Refactor: consume StoreURL from New portal UI and forward appstoreurl and appsource for applovinmax (#925) * UOE-11191: Refactor: consume StoreURL from New portal UI and forward appstoreurl and appsource for applovinmax * review comment * UOE-11185: Stats for parsing failed for ituneid * go fmt update --- .../pubmatic/openwrap/beforevalidationhook.go | 59 +++++---- .../openwrap/beforevalidationhook_test.go | 123 ++++++++++++++---- .../openwrap/metrics/config/multimetrics.go | 7 + modules/pubmatic/openwrap/metrics/metrics.go | 3 + .../pubmatic/openwrap/metrics/mock/mock.go | 12 ++ .../openwrap/metrics/prometheus/prometheus.go | 17 +++ .../openwrap/metrics/stats/tcp_stats.go | 1 + 7 files changed, 175 insertions(+), 47 deletions(-) diff --git a/modules/pubmatic/openwrap/beforevalidationhook.go b/modules/pubmatic/openwrap/beforevalidationhook.go index 9da5cc366f3..243e819d5ee 100644 --- a/modules/pubmatic/openwrap/beforevalidationhook.go +++ b/modules/pubmatic/openwrap/beforevalidationhook.go @@ -569,7 +569,10 @@ func (m OpenWrap) handleBeforeValidationHook( } if rCtx.Endpoint == models.EndpointAppLovinMax && payload.BidRequest.App != nil && payload.BidRequest.App.StoreURL == "" { - rCtx.AppLovinMax.AppStoreUrl = getProfileAppStoreUrlAndUpdateItunesID(rCtx, payload.BidRequest, impExt) + var isValidAppStoreUrl bool + if rCtx.AppLovinMax.AppStoreUrl, isValidAppStoreUrl = getProfileAppStoreUrl(rCtx); isValidAppStoreUrl { + m.updateSkadnSourceapp(rCtx, payload.BidRequest, impExt) + } rCtx.PageURL = rCtx.AppLovinMax.AppStoreUrl } impExt.Wrapper = nil @@ -1344,36 +1347,44 @@ func isValidURL(urlVal string) bool { return validator.IsRequestURL(urlVal) && validator.IsURL(urlVal) } -func getProfileAppStoreUrlAndUpdateItunesID(rctx models.RequestCtx, bidRequest *openrtb2.BidRequest, impExt *models.ImpExtension) string { +func getProfileAppStoreUrl(rctx models.RequestCtx) (string, bool) { + isValidAppStoreUrl := false appStoreUrl := rctx.PartnerConfigMap[models.VersionLevelConfigID][models.AppStoreUrl] if appStoreUrl == "" { - glog.Errorf("[AppLovinMax] [PubID]: %d [ProfileID]: %d [Error]: app storeurl not present in DB", rctx.PubID, rctx.ProfileID) - return appStoreUrl + glog.Errorf("[AppLovinMax] [PubID]: %d [ProfileID]: %d [Error]: app store url not present in DB", rctx.PubID, rctx.ProfileID) + return appStoreUrl, isValidAppStoreUrl } appStoreUrl = strings.TrimSpace(appStoreUrl) if !isValidURL(appStoreUrl) { - glog.Errorf("[AppLovinMax] [PubID]: %d [ProfileID]: %d [AppStoreUrl]: %s [Error]: Invalid app storeurl", rctx.PubID, rctx.ProfileID, appStoreUrl) - return appStoreUrl + glog.Errorf("[AppLovinMax] [PubID]: %d [ProfileID]: %d [AppStoreUrl]: %s [Error]: Invalid app store url", rctx.PubID, rctx.ProfileID, appStoreUrl) + return appStoreUrl, isValidAppStoreUrl } - var err error - if bidRequest.Device != nil && strings.ToLower(bidRequest.Device.OS) == "ios" { - //no multiple imp supported for AppLovinMax - if impExt != nil { - if impExt.SKAdnetwork == nil { - glog.Errorf("[AppLovinMax] [PubID]: %d [ProfileID]: %d [Error]: skadn is missing in imp.ext", rctx.PubID, rctx.ProfileID, appStoreUrl) - return appStoreUrl - } - var itunesID string - if itunesID = extractItunesIdFromAppStoreUrl(appStoreUrl); itunesID == "" { - glog.Errorf("[AppLovinMax] [PubID]: %d [ProfileID]: %d [AppStoreUrl]: %s [Error]: itunes id is missing in app store url", rctx.PubID, rctx.ProfileID, appStoreUrl) - return appStoreUrl - } - if impExt.SKAdnetwork, err = jsonparser.Set(impExt.SKAdnetwork, []byte(strconv.Quote(itunesID)), "sourceapp"); err != nil { - glog.Errorf("[AppLovinMax] [PubID]: %d [ProfileID]: %d [AppStoreUrl]: %s [Error]: %s", rctx.PubID, rctx.ProfileID, appStoreUrl, err.Error()) - } - } + isValidAppStoreUrl = true + return appStoreUrl, isValidAppStoreUrl +} + +func (m *OpenWrap) updateSkadnSourceapp(rctx models.RequestCtx, bidRequest *openrtb2.BidRequest, impExt *models.ImpExtension) { + if bidRequest.Device == nil || strings.ToLower(bidRequest.Device.OS) != "ios" { + return + } + + if impExt == nil || impExt.SKAdnetwork == nil { + glog.Errorf("[AppLovinMax] [PubID]: %d [ProfileID]: %d [Error]: skadn is missing in imp.ext", rctx.PubID, rctx.ProfileID) + return + } + + itunesID := extractItunesIdFromAppStoreUrl(rctx.AppLovinMax.AppStoreUrl) + if itunesID == "" { + m.metricEngine.RecordFailedParsingItuneID(rctx.PubIDStr, rctx.ProfileIDStr) + glog.Errorf("[AppLovinMax] [PubID]: %d [ProfileID]: %d [AppStoreUrl]: %s [Error]: itunes id is missing in app store url", rctx.PubID, rctx.ProfileID, rctx.AppLovinMax.AppStoreUrl) + return + } + + if updatedSKAdnetwork, err := jsonparser.Set(impExt.SKAdnetwork, []byte(strconv.Quote(itunesID)), "sourceapp"); err != nil { + glog.Errorf("[AppLovinMax] [PubID]: %d [ProfileID]: %d [AppStoreUrl]: %s [Error]: %s", rctx.PubID, rctx.ProfileID, rctx.AppLovinMax.AppStoreUrl, err.Error()) + } else { + impExt.SKAdnetwork = updatedSKAdnetwork } - return appStoreUrl } func extractItunesIdFromAppStoreUrl(url string) string { diff --git a/modules/pubmatic/openwrap/beforevalidationhook_test.go b/modules/pubmatic/openwrap/beforevalidationhook_test.go index 4b3de0603c8..1d683f2aaf3 100644 --- a/modules/pubmatic/openwrap/beforevalidationhook_test.go +++ b/modules/pubmatic/openwrap/beforevalidationhook_test.go @@ -6572,37 +6572,83 @@ func TestSetImpBidFloorParams(t *testing.T) { } } -func TestUpdateProfileAppStoreUrl(t *testing.T) { +func TestGetProfileAppStoreUrl(t *testing.T) { + type args struct { + rctx models.RequestCtx + } tests := []struct { - name string - rctx models.RequestCtx - bidRequest *openrtb2.BidRequest - impExt *models.ImpExtension - wantAppStoreURL string - wantSourceApp string + name string + args args + want string + want1 bool }{ { name: "AppStoreUrl missing in DB", - rctx: models.RequestCtx{ - PartnerConfigMap: map[int]map[string]string{ - models.VersionLevelConfigID: {}, + args: args{ + rctx: models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + models.VersionLevelConfigID: {}, + }, }, }, - bidRequest: &openrtb2.BidRequest{App: &openrtb2.App{}}, - wantAppStoreURL: "", + want: "", + want1: false, }, { name: "Invalid AppStoreUrl", - rctx: models.RequestCtx{ - PartnerConfigMap: map[int]map[string]string{ - models.VersionLevelConfigID: { - models.AppStoreUrl: "invalid-url", + args: args{ + rctx: models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + models.VersionLevelConfigID: { + models.AppStoreUrl: "invalid-url", + }, + }, + }, + }, + want: "invalid-url", + want1: false, + }, + { + name: "Valid AppStoreUrl", + args: args{ + rctx: models.RequestCtx{ + PartnerConfigMap: map[int]map[string]string{ + models.VersionLevelConfigID: { + models.AppStoreUrl: "https://apps.apple.com/app/id123456789", + }, }, }, }, - bidRequest: &openrtb2.BidRequest{App: &openrtb2.App{}}, - wantAppStoreURL: "invalid-url", + want: "https://apps.apple.com/app/id123456789", + want1: true, }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, got1 := getProfileAppStoreUrl(tt.args.rctx) + assert.Equal(t, tt.want, got) + assert.Equal(t, tt.want1, got1) + }) + } +} + +func TestUpdateSkadnSourceapp(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + type feilds struct { + m *OpenWrap + } + tests := []struct { + name string + rctx models.RequestCtx + bidRequest *openrtb2.BidRequest + impExt *models.ImpExtension + wantAppStoreURL string + wantSourceApp string + fe feilds + setup func() *mock_metrics.MockMetricsEngine + }{ { name: "Valid AppStoreUrl os is ios and SKAdnetwork is present in imp.ext", rctx: models.RequestCtx{ @@ -6611,6 +6657,9 @@ func TestUpdateProfileAppStoreUrl(t *testing.T) { models.AppStoreUrl: "https://apps.apple.com/app/id123456789", }, }, + AppLovinMax: models.AppLovinMax{ + AppStoreUrl: "https://apps.apple.com/app/id123456789", + }, }, bidRequest: &openrtb2.BidRequest{ App: &openrtb2.App{}, @@ -6626,6 +6675,9 @@ func TestUpdateProfileAppStoreUrl(t *testing.T) { impExt: &models.ImpExtension{ SKAdnetwork: json.RawMessage(`{}`), }, + setup: func() *mock_metrics.MockMetricsEngine { + return mock_metrics.NewMockMetricsEngine(ctrl) + }, wantAppStoreURL: "https://apps.apple.com/app/id123456789", wantSourceApp: "123456789", }, @@ -6634,9 +6686,12 @@ func TestUpdateProfileAppStoreUrl(t *testing.T) { rctx: models.RequestCtx{ PartnerConfigMap: map[int]map[string]string{ models.VersionLevelConfigID: { - models.AppStoreUrl: "https://apps.apple.com/app/id123456789", + models.AppStoreUrl: "https://apps.apple.com/app/id", }, }, + AppLovinMax: models.AppLovinMax{ + AppStoreUrl: "https://apps.apple.com/app/id", + }, }, bidRequest: &openrtb2.BidRequest{ App: &openrtb2.App{}, @@ -6649,7 +6704,10 @@ func TestUpdateProfileAppStoreUrl(t *testing.T) { }, }, }, - wantAppStoreURL: "https://apps.apple.com/app/id123456789", + setup: func() *mock_metrics.MockMetricsEngine { + return mock_metrics.NewMockMetricsEngine(ctrl) + }, + wantAppStoreURL: "https://apps.apple.com/app/id", }, { name: "Valid AppStoreUrl os is ios but SKAdnetwork missing in imp.ext", @@ -6659,6 +6717,9 @@ func TestUpdateProfileAppStoreUrl(t *testing.T) { models.AppStoreUrl: "https://apps.apple.com/app/id123456789", }, }, + AppLovinMax: models.AppLovinMax{ + AppStoreUrl: "https://apps.apple.com/app/id123456789", + }, }, bidRequest: &openrtb2.BidRequest{ App: &openrtb2.App{}, @@ -6671,17 +6732,25 @@ func TestUpdateProfileAppStoreUrl(t *testing.T) { }, }, }, + setup: func() *mock_metrics.MockMetricsEngine { + return mock_metrics.NewMockMetricsEngine(ctrl) + }, impExt: &models.ImpExtension{}, wantAppStoreURL: "https://apps.apple.com/app/id123456789", }, { - name: "Valid AppStoreUrl os is ios but Itunes ID missing in AppStoreUrl", + name: "Valid AppStoreUrl os is ios but Itunes ID missing in AppStoreUrl(url is of Android)", rctx: models.RequestCtx{ PartnerConfigMap: map[int]map[string]string{ models.VersionLevelConfigID: { models.AppStoreUrl: "https://apps.apple.com/app/", }, }, + AppLovinMax: models.AppLovinMax{ + AppStoreUrl: "https://apps.apple.com/app/", + }, + PubIDStr: "5890", + ProfileIDStr: "1234", }, bidRequest: &openrtb2.BidRequest{ App: &openrtb2.App{}, @@ -6694,6 +6763,11 @@ func TestUpdateProfileAppStoreUrl(t *testing.T) { }, }, }, + setup: func() *mock_metrics.MockMetricsEngine { + mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) + mockEngine.EXPECT().RecordFailedParsingItuneID("5890", "1234") + return mockEngine + }, impExt: &models.ImpExtension{ SKAdnetwork: json.RawMessage(`{}`), }, @@ -6703,8 +6777,11 @@ func TestUpdateProfileAppStoreUrl(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - gotAppStoreURL := getProfileAppStoreUrlAndUpdateItunesID(tt.rctx, tt.bidRequest, tt.impExt) - assert.Equal(t, tt.wantAppStoreURL, gotAppStoreURL) + metricsEngine := tt.setup() + tt.fe.m = &OpenWrap{ + metricEngine: metricsEngine, + } + tt.fe.m.updateSkadnSourceapp(tt.rctx, tt.bidRequest, tt.impExt) if tt.impExt != nil { if tt.impExt.SKAdnetwork != nil { var skAdnetwork map[string]interface{} diff --git a/modules/pubmatic/openwrap/metrics/config/multimetrics.go b/modules/pubmatic/openwrap/metrics/config/multimetrics.go index 72a31262ea5..5689e5a1b99 100644 --- a/modules/pubmatic/openwrap/metrics/config/multimetrics.go +++ b/modules/pubmatic/openwrap/metrics/config/multimetrics.go @@ -529,3 +529,10 @@ func (me *MultiMetricsEngine) RecordSignalDataStatus(pubid, profileid, signalTyp thisME.RecordSignalDataStatus(pubid, profileid, signalType) } } + +// RecordFailedParsingItuneID record failed parsing itune id +func (me *MultiMetricsEngine) RecordFailedParsingItuneID(pubId, profId string) { + for _, thisME := range *me { + thisME.RecordFailedParsingItuneID(pubId, profId) + } +} diff --git a/modules/pubmatic/openwrap/metrics/metrics.go b/modules/pubmatic/openwrap/metrics/metrics.go index dfe45a8d500..393238b657f 100644 --- a/modules/pubmatic/openwrap/metrics/metrics.go +++ b/modules/pubmatic/openwrap/metrics/metrics.go @@ -92,4 +92,7 @@ type MetricsEngine interface { //VMAP-adrule RecordAdruleEnabled(pubId, profId string) RecordAdruleValidationFailure(pubId, profId string) + + //AppLovinMax metrics + RecordFailedParsingItuneID(pubId, profId string) } diff --git a/modules/pubmatic/openwrap/metrics/mock/mock.go b/modules/pubmatic/openwrap/metrics/mock/mock.go index b98fde7c783..5715087285b 100644 --- a/modules/pubmatic/openwrap/metrics/mock/mock.go +++ b/modules/pubmatic/openwrap/metrics/mock/mock.go @@ -826,3 +826,15 @@ func (mr *MockMetricsEngineMockRecorder) Shutdown() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Shutdown", reflect.TypeOf((*MockMetricsEngine)(nil).Shutdown)) } + +// RecordFailedParsingItuneID mocks base method. +func (m *MockMetricsEngine) RecordFailedParsingItuneID(arg0, arg1 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordFailedParsingItuneID", arg0, arg1) +} + +// RecordFailedParsingItuneID indicates an expected call of RecordFailedParsingItuneID. +func (mr *MockMetricsEngineMockRecorder) RecordFailedParsingItuneID(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordFailedParsingItuneID", reflect.TypeOf((*MockMetricsEngine)(nil).RecordFailedParsingItuneID), arg0, arg1) +} diff --git a/modules/pubmatic/openwrap/metrics/prometheus/prometheus.go b/modules/pubmatic/openwrap/metrics/prometheus/prometheus.go index 2117caabaff..491ec5312ca 100644 --- a/modules/pubmatic/openwrap/metrics/prometheus/prometheus.go +++ b/modules/pubmatic/openwrap/metrics/prometheus/prometheus.go @@ -92,6 +92,9 @@ type Metrics struct { //VMAP adrule pubProfAdruleEnabled *prometheus.CounterVec pubProfAdruleValidationfailure *prometheus.CounterVec + + //ApplovinMax + failedParsingItuneId *prometheus.CounterVec } const ( @@ -360,6 +363,12 @@ func newMetrics(cfg *config.PrometheusMetrics, promRegistry *prometheus.Registry []string{pubIdLabel, profileIDLabel}, ) + metrics.failedParsingItuneId = newCounter(cfg, promRegistry, + "failed_parsing_itune_id", + "Count of failed parsing itune id", + []string{pubIdLabel, profileIDLabel}, + ) + newSSHBMetrics(&metrics, cfg, promRegistry) return &metrics @@ -599,6 +608,14 @@ func (m *Metrics) RecordSignalDataStatus(pubid, profileid, signalType string) { }).Inc() } +func (m *Metrics) RecordFailedParsingItuneID(pubId, profId string) { + m.failedParsingItuneId.With(prometheus.Labels{ + pubIDLabel: pubId, + profileIDLabel: profId, + }).Inc() + +} + // TODO - really need ? func (m *Metrics) RecordPBSAuctionRequestsStats() {} diff --git a/modules/pubmatic/openwrap/metrics/stats/tcp_stats.go b/modules/pubmatic/openwrap/metrics/stats/tcp_stats.go index 94ba5931eaa..8f8e007471b 100644 --- a/modules/pubmatic/openwrap/metrics/stats/tcp_stats.go +++ b/modules/pubmatic/openwrap/metrics/stats/tcp_stats.go @@ -352,3 +352,4 @@ func (st *StatsTCP) RecordSignalDataStatus(pubid, profileid, signalType string) func (st *StatsTCP) RecordPrebidCacheRequestTime(success bool, length time.Duration) {} func (st *StatsTCP) RecordPrebidAuctionBidResponse(publisher string, partnerName string, bidderCode string, adapterCode string) { } +func (st *StatsTCP) RecordFailedParsingItuneID(pubId, profId string) {} From c8c00120c24efb4a6ab1a5dfd0f323a8c0b47592 Mon Sep 17 00:00:00 2001 From: Viral Vala <63396712+pm-viral-vala@users.noreply.github.com> Date: Wed, 9 Oct 2024 14:34:05 +0530 Subject: [PATCH 2/9] OTT-1807-P1: discard emptyVAST and invalidVAST bids detected by VAST unwrap module (#933) Co-authored-by: ashishshinde-pubm <109787960+ashishshinde-pubm@users.noreply.github.com> Co-authored-by: supriya-patil Co-authored-by: dhruv.sonone --- analytics/pubmatic/logger.go | 32 + analytics/pubmatic/logger_test.go | 713 ++++++++--- go.mod | 3 +- go.sum | 4 +- hooks/hookexecution/executor.go | 4 +- hooks/hookexecution/mocks_test.go | 4 +- hooks/hookstage/rawbidderresponse.go | 4 +- .../hookstage/rawbidderresponse_mutations.go | 2 +- .../ortb2blocking/hook_raw_bidder_response.go | 4 +- modules/prebid/ortb2blocking/module_test.go | 308 +++-- modules/pubmatic/openwrap/config/config.go | 1 - .../openwrap/hook_raw_bidder_response.go | 92 +- .../openwrap/hook_raw_bidder_response_test.go | 1058 ++++++++++++++--- modules/pubmatic/openwrap/models/constants.go | 32 +- modules/pubmatic/openwrap/models/nbr/codes.go | 1 + modules/pubmatic/openwrap/unwrap/unwrap.go | 19 +- .../pubmatic/openwrap/unwrap/unwrap_test.go | 94 +- 17 files changed, 1794 insertions(+), 581 deletions(-) diff --git a/analytics/pubmatic/logger.go b/analytics/pubmatic/logger.go index 597c4320801..75364070d55 100644 --- a/analytics/pubmatic/logger.go +++ b/analytics/pubmatic/logger.go @@ -383,6 +383,9 @@ func getPartnerRecordsByImp(ao analytics.AuctionObject, rCtx *models.RequestCtx) } price := bid.Price + // If bids are rejected before setting bidExt.OriginalBidCPM, calculate the price and ocpm values based on the currency and revshare. + price = computeBidPriceForBidsRejectedBeforeSettingOCPM(rCtx, &bidExt, price, revShare, ao) + bid.Price = price if ao.Response.Cur != models.USD { if bidCtx.EN != 0 { // valid-bids + dropped-bids+ default-bids price = bidCtx.EN @@ -564,3 +567,32 @@ func getAdPodSlot(adPodConfig *models.AdPod) *AdPodSlot { return &adPodSlot } + +func GetBidPriceAfterCurrencyConversion(price float64, requestCurrencies []string, responseCurrency string, + currencyConverter func(fromCurrency string, toCurrency string, value float64) (float64, error)) float64 { + if len(requestCurrencies) == 0 { + requestCurrencies = []string{models.USD} + } + for _, requestCurrency := range requestCurrencies { + if value, err := currencyConverter(responseCurrency, requestCurrency, price); err == nil { + return value + } + } + return 0 // in case of error, send 0 value to make it consistent with prebid +} + +func computeBidPriceForBidsRejectedBeforeSettingOCPM(rCtx *models.RequestCtx, bidExt *models.BidExt, + price, revshare float64, ao analytics.AuctionObject) float64 { + if price != 0 && bidExt.OriginalBidCPM == 0 { + if len(bidExt.OriginalBidCur) == 0 { + bidExt.OriginalBidCur = models.USD + } + bidExt.OriginalBidCPM = price + price = price * models.GetBidAdjustmentValue(revshare) + if cpmUSD, err := rCtx.CurrencyConversion(bidExt.OriginalBidCur, models.USD, price); err == nil { + bidExt.OriginalBidCPMUSD = cpmUSD + } + price = GetBidPriceAfterCurrencyConversion(price, ao.RequestWrapper.Cur, bidExt.OriginalBidCur, rCtx.CurrencyConversion) + } + return price +} diff --git a/analytics/pubmatic/logger_test.go b/analytics/pubmatic/logger_test.go index b01f1f7b501..41ef74a283f 100644 --- a/analytics/pubmatic/logger_test.go +++ b/analytics/pubmatic/logger_test.go @@ -2,6 +2,7 @@ package pubmatic import ( "encoding/json" + "fmt" "net/http" "net/url" "testing" @@ -294,7 +295,8 @@ func TestGetPartnerRecordsByImp(t *testing.T) { BidCtx: map[string]models.BidCtx{ "bid-id-1": { BidExt: models.BidExt{ - ExtBid: openrtb_ext.ExtBid{}, + ExtBid: openrtb_ext.ExtBid{}, + OriginalBidCPM: 10, }, }, }, @@ -347,7 +349,8 @@ func TestGetPartnerRecordsByImp(t *testing.T) { BidCtx: map[string]models.BidCtx{ "bid-id-1": { BidExt: models.BidExt{ - ExtBid: openrtb_ext.ExtBid{}, + ExtBid: openrtb_ext.ExtBid{}, + OriginalBidCPM: 10, }, }, }, @@ -403,7 +406,8 @@ func TestGetPartnerRecordsByImp(t *testing.T) { BidCtx: map[string]models.BidCtx{ "bid-id-1": { BidExt: models.BidExt{ - ExtBid: openrtb_ext.ExtBid{}, + ExtBid: openrtb_ext.ExtBid{}, + OriginalBidCPM: 10, }, }, }, @@ -459,7 +463,8 @@ func TestGetPartnerRecordsByImp(t *testing.T) { BidCtx: map[string]models.BidCtx{ "bid-id-1": { BidExt: models.BidExt{ - ExtBid: openrtb_ext.ExtBid{}, + ExtBid: openrtb_ext.ExtBid{}, + OriginalBidCPM: 10, }, }, }, @@ -512,7 +517,8 @@ func TestGetPartnerRecordsByImp(t *testing.T) { BidCtx: map[string]models.BidCtx{ "bid-id-1": { BidExt: models.BidExt{ - ExtBid: openrtb_ext.ExtBid{}, + ExtBid: openrtb_ext.ExtBid{}, + OriginalBidCPM: 10, }, }, }, @@ -564,7 +570,8 @@ func TestGetPartnerRecordsByImp(t *testing.T) { BidCtx: map[string]models.BidCtx{ "bid-id-1": { BidExt: models.BidExt{ - ExtBid: openrtb_ext.ExtBid{}, + ExtBid: openrtb_ext.ExtBid{}, + OriginalBidCPM: 10, }, }, }, @@ -709,7 +716,8 @@ func TestGetPartnerRecordsByImpForTracker(t *testing.T) { BidCtx: map[string]models.BidCtx{ "bid-id-1": { BidExt: models.BidExt{ - ExtBid: openrtb_ext.ExtBid{}, + OriginalBidCPM: 10, + ExtBid: openrtb_ext.ExtBid{}, }, }, }, @@ -771,6 +779,7 @@ func TestGetPartnerRecordsByImpForTracker(t *testing.T) { BidCtx: map[string]models.BidCtx{ "bid-id-1": { BidExt: models.BidExt{ + OriginalBidCPM: 12, ExtBid: openrtb_ext.ExtBid{ Prebid: &openrtb_ext.ExtBidPrebid{ Floors: &openrtb_ext.ExtBidPrebidFloors{ @@ -1015,7 +1024,8 @@ func TestGetPartnerRecordsByImpForDefaultBids(t *testing.T) { BidCtx: map[string]models.BidCtx{ "bid-id-1": { BidExt: models.BidExt{ - ExtBid: openrtb_ext.ExtBid{}, + ExtBid: openrtb_ext.ExtBid{}, + OriginalBidCPM: 10, }, }, }, @@ -1287,6 +1297,7 @@ func TestGetPartnerRecordsByImpForDefaultBids(t *testing.T) { } } func TestGetPartnerRecordsByImpForSeatNonBid(t *testing.T) { + pg, _ := openrtb_ext.NewPriceGranularityFromLegacyID("med") type args struct { ao analytics.AuctionObject rCtx *models.RequestCtx @@ -1420,6 +1431,301 @@ func TestGetPartnerRecordsByImpForSeatNonBid(t *testing.T) { }, }, }, + { + name: "log rejected non-bid having bidder_response_currency EUR and request_currency USD", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Cur: []string{models.USD}, + }, + }, + Response: &openrtb2.BidResponse{Cur: models.USD}, + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "appnexus", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: int(nbr.LossBidLostInVastUnwrap), + Ext: openrtb_ext.ExtNonBid{ + Prebid: openrtb_ext.ExtNonBidPrebid{ + Bid: openrtb_ext.ExtNonBidPrebidBid{ + Price: 10, + ID: "bid-id-1", + W: 10, + H: 50, + OriginalBidCur: "EUR", + }, + }, + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + PriceGranularity: &pg, + CurrencyConversion: func(from, to string, value float64) (float64, error) { + if from == "USD" && to == "EUR" { + return value * 1.2, nil + } + if from == "EUR" && to == "USD" { + return value * 0.8, nil + } + return 0, nil + }, + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + Bidders: map[string]models.PartnerData{ + "appnexus": { + PartnerID: 1, + PrebidBidderCode: "appnexus", + KGP: "kgp", + KGPV: "kgpv", + }, + }, + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + Nbr: ptrutil.ToPtr(nbr.LossBidLostInVastUnwrap), + }, + }, + }, + BidFloor: 10.5, + BidFloorCur: "USD", + }, + }, + PartnerConfigMap: map[int]map[string]string{ + 1: { + "rev_share": "0", + }, + }, + WinningBids: make(models.WinningBids), + Platform: models.PLATFORM_APP, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "appnexus", + BidderCode: "appnexus", + KGPV: "kgpv", + KGPSV: "kgpv", + PartnerSize: "10x50", + GrossECPM: 8, + NetECPM: 8, + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCPM: 10, + OriginalCur: "EUR", + FloorValue: 10.5, + FloorRuleValue: 10.5, + PriceBucket: "8.00", + Nbr: ptrutil.ToPtr(nbr.LossBidLostInVastUnwrap), + }, + }, + }, + }, + { + name: "log rejected non-bid having bidder_response_currency EUR and request_currency USD and having 50% revshare", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Cur: []string{models.USD}, + }, + }, + Response: &openrtb2.BidResponse{Cur: models.USD}, + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "appnexus", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: int(nbr.LossBidLostInVastUnwrap), + Ext: openrtb_ext.ExtNonBid{ + Prebid: openrtb_ext.ExtNonBidPrebid{ + Bid: openrtb_ext.ExtNonBidPrebidBid{ + Price: 10, + ID: "bid-id-1", + W: 10, + H: 50, + OriginalBidCur: "EUR", + }, + }, + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + PriceGranularity: &pg, + CurrencyConversion: func(from, to string, value float64) (float64, error) { + if from == "USD" && to == "EUR" { + return value * 1.2, nil + } + if from == "EUR" && to == "USD" { + return value * 0.8, nil + } + return 0, nil + }, + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + Bidders: map[string]models.PartnerData{ + "appnexus": { + PartnerID: 1, + PrebidBidderCode: "appnexus", + KGP: "kgp", + KGPV: "kgpv", + }, + }, + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + Nbr: ptrutil.ToPtr(nbr.LossBidLostInVastUnwrap), + }, + }, + }, + BidFloor: 10.5, + BidFloorCur: "USD", + }, + }, + PartnerConfigMap: map[int]map[string]string{ + 1: { + "rev_share": "50", + }, + }, + WinningBids: make(models.WinningBids), + Platform: models.PLATFORM_APP, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "appnexus", + BidderCode: "appnexus", + KGPV: "kgpv", + KGPSV: "kgpv", + PartnerSize: "10x50", + GrossECPM: 8, + NetECPM: 4, + PriceBucket: "4.00", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCPM: 10, + OriginalCur: "EUR", + FloorValue: 10.5, + FloorRuleValue: 10.5, + Nbr: ptrutil.ToPtr(nbr.LossBidLostInVastUnwrap), + }, + }, + }, + }, + { + name: "log rejected non-bid having response_currency USD and request_currency EUR and having 50% revshare", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Cur: []string{"EUR"}, + }, + }, + Response: &openrtb2.BidResponse{Cur: "EUR"}, + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "appnexus", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: int(nbr.LossBidLostInVastUnwrap), + Ext: openrtb_ext.ExtNonBid{ + Prebid: openrtb_ext.ExtNonBidPrebid{ + Bid: openrtb_ext.ExtNonBidPrebidBid{ + Price: 10, + ID: "bid-id-1", + W: 10, + H: 50, + OriginalBidCur: models.USD, + }, + }, + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + CurrencyConversion: func(from, to string, value float64) (float64, error) { + if from == "USD" && to == "EUR" { + return value * 1.2, nil + } + if from == "EUR" && to == "USD" { + return value * 0.8, nil + } + return value, nil + }, + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + Bidders: map[string]models.PartnerData{ + "appnexus": { + PartnerID: 1, + PrebidBidderCode: "appnexus", + KGP: "kgp", + KGPV: "kgpv", + }, + }, + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + Nbr: ptrutil.ToPtr(nbr.LossBidLostInVastUnwrap), + }, + }, + }, + BidFloor: 10.5, + BidFloorCur: "USD", + }, + }, + PartnerConfigMap: map[int]map[string]string{ + 1: { + "rev_share": "50", + }, + }, + WinningBids: make(models.WinningBids), + Platform: models.PLATFORM_APP, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "appnexus", + BidderCode: "appnexus", + KGPV: "kgpv", + KGPSV: "kgpv", + PartnerSize: "10x50", + GrossECPM: 10, + NetECPM: 5, + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCPM: 10, + OriginalCur: "USD", + FloorValue: 10.5, + FloorRuleValue: 10.5, + Nbr: ptrutil.ToPtr(nbr.LossBidLostInVastUnwrap), + }, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -1461,6 +1767,7 @@ func TestGetPartnerRecordsByImpForSeatNonBidForFloors(t *testing.T) { FloorValue: 1, FloorCurrency: models.USD, }, + OriginalBidCPM: 10, }, }, }, @@ -1527,6 +1834,7 @@ func TestGetPartnerRecordsByImpForSeatNonBidForFloors(t *testing.T) { FloorValue: 0, FloorCurrency: models.USD, }, + OriginalBidCPM: 10, }, }, }, @@ -1593,6 +1901,7 @@ func TestGetPartnerRecordsByImpForSeatNonBidForFloors(t *testing.T) { FloorValue: 10, FloorCurrency: models.USD, }, + OriginalBidCPM: 10, }, }, }, @@ -1651,8 +1960,9 @@ func TestGetPartnerRecordsByImpForSeatNonBidForFloors(t *testing.T) { Ext: openrtb_ext.ExtNonBid{ Prebid: openrtb_ext.ExtNonBidPrebid{ Bid: openrtb_ext.ExtNonBidPrebidBid{ - Price: 10, - ID: "bid-id-1", + Price: 10, + ID: "bid-id-1", + OriginalBidCPM: 10, }, }, }, @@ -1711,8 +2021,9 @@ func TestGetPartnerRecordsByImpForSeatNonBidForFloors(t *testing.T) { Ext: openrtb_ext.ExtNonBid{ Prebid: openrtb_ext.ExtNonBidPrebid{ Bid: openrtb_ext.ExtNonBidPrebidBid{ - Price: 10, - ID: "bid-id-1", + Price: 10, + ID: "bid-id-1", + OriginalBidCPM: 10, }, }, }, @@ -1782,6 +2093,7 @@ func TestGetPartnerRecordsByImpForSeatNonBidForFloors(t *testing.T) { FloorValue: 1, FloorCurrency: "JPY", }, + OriginalBidCPM: 10, }, }, }, @@ -1988,6 +2300,7 @@ func TestGetPartnerRecordsByImpForBidIDCollisions(t *testing.T) { BidId: "uuid", }, }, + OriginalBidCPM: 10, }, }, }, @@ -2017,6 +2330,11 @@ func TestGetPartnerRecordsByImpForBidIDCollisions(t *testing.T) { name: "valid bid, but json unmarshal fails", args: args{ ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Cur: []string{}, + }, + }, Response: &openrtb2.BidResponse{ SeatBid: []openrtb2.SeatBid{ { @@ -2046,11 +2364,13 @@ func TestGetPartnerRecordsByImpForBidIDCollisions(t *testing.T) { BidId: "uuid", }, }, + OriginalBidCPM: 10, }, }, }, }, }, + CurrencyConversion: func(from, to string, value float64) (float64, error) { return 10, nil }, }, }, partners: map[string][]PartnerRecord{ @@ -2066,6 +2386,7 @@ func TestGetPartnerRecordsByImpForBidIDCollisions(t *testing.T) { OriginalCur: models.USD, NetECPM: 10, GrossECPM: 10, + OriginalCPM: 10, DealPriority: 0, }, }, @@ -2103,7 +2424,8 @@ func TestGetPartnerRecordsByImpForBidIDCollisions(t *testing.T) { DealTierSatisfied: true, }, }, - Nbr: nbr.LossBidLostToHigherBid.Ptr(), + OriginalBidCPM: 10, + Nbr: nbr.LossBidLostToHigherBid.Ptr(), }, }, }, @@ -2195,9 +2517,10 @@ func TestGetPartnerRecordsByImpForBidIDCollisions(t *testing.T) { Ext: openrtb_ext.ExtNonBid{ Prebid: openrtb_ext.ExtNonBidPrebid{ Bid: openrtb_ext.ExtNonBidPrebidBid{ - Price: 10, - ID: "bid-id-1", - BidId: "uuid", + Price: 10, + ID: "bid-id-1", + BidId: "uuid", + OriginalBidCPM: 10, }, }, }, @@ -2263,6 +2586,7 @@ func TestGetPartnerRecordsByImpForBidIDCollisions(t *testing.T) { BidId: "uuid", }, }, + OriginalBidCPM: 10, }, }, }, @@ -2318,6 +2642,9 @@ func TestGetPartnerRecordsByImpForBidExtFailure(t *testing.T) { name: "valid bid, but bid.ext is empty", args: args{ ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, Response: &openrtb2.BidResponse{ SeatBid: []openrtb2.SeatBid{ { @@ -2352,6 +2679,7 @@ func TestGetPartnerRecordsByImpForBidExtFailure(t *testing.T) { }, }, }, + CurrencyConversion: func(from, to string, value float64) (float64, error) { return 10, nil }, }, }, partners: map[string][]PartnerRecord{ @@ -2367,6 +2695,7 @@ func TestGetPartnerRecordsByImpForBidExtFailure(t *testing.T) { OriginalCur: models.USD, NetECPM: 10, GrossECPM: 10, + OriginalCPM: 10, DealPriority: 0, }, }, @@ -2376,6 +2705,9 @@ func TestGetPartnerRecordsByImpForBidExtFailure(t *testing.T) { name: "dropped bid, bidExt unmarshal fails", args: args{ ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, Response: &openrtb2.BidResponse{ SeatBid: []openrtb2.SeatBid{ { @@ -2410,6 +2742,7 @@ func TestGetPartnerRecordsByImpForBidExtFailure(t *testing.T) { }, }, }, + CurrencyConversion: func(from, to string, value float64) (float64, error) { return 10, nil }, }, }, partners: map[string][]PartnerRecord{ @@ -2425,6 +2758,7 @@ func TestGetPartnerRecordsByImpForBidExtFailure(t *testing.T) { OriginalCur: models.USD, NetECPM: 10, GrossECPM: 10, + OriginalCPM: 10, DealPriority: 0, Nbr: nil, }, @@ -3246,6 +3580,9 @@ func TestGetPartnerRecordsByImpForMarketPlaceBidders(t *testing.T) { name: "overwrite marketplace bid details", args: args{ ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, Response: &openrtb2.BidResponse{ SeatBid: []openrtb2.SeatBid{ { @@ -3294,6 +3631,7 @@ func TestGetPartnerRecordsByImpForMarketPlaceBidders(t *testing.T) { }, }, }, + CurrencyConversion: func(from, to string, value float64) (float64, error) { return value, nil }, }, }, partners: map[string][]PartnerRecord{ @@ -3309,6 +3647,7 @@ func TestGetPartnerRecordsByImpForMarketPlaceBidders(t *testing.T) { OriginalCur: models.USD, GrossECPM: 1, NetECPM: 1, + OriginalCPM: 1, KGPV: "apnx_kgpv", KGPSV: "apnx_kgpv", }, @@ -3323,6 +3662,7 @@ func TestGetPartnerRecordsByImpForMarketPlaceBidders(t *testing.T) { OriginalCur: models.USD, GrossECPM: 2, NetECPM: 2, + OriginalCPM: 2, KGPV: "pubm_kgpv", KGPSV: "pubm_kgpv", }, @@ -3337,6 +3677,7 @@ func TestGetPartnerRecordsByImpForMarketPlaceBidders(t *testing.T) { OriginalCur: models.USD, GrossECPM: 3, NetECPM: 3, + OriginalCPM: 3, KGPV: "pubm_kgpv", KGPSV: "pubm_kgpv", }, @@ -4600,129 +4941,129 @@ func TestSlotRecordsInGetLogAuctionObjectAsURL(t *testing.T) { args args want want }{ - // { - // name: "req.Imp not mapped in ImpBidCtx", - // args: args{ - // ao: analytics.AuctionObject{ - // RequestWrapper: &openrtb_ext.RequestWrapper{ - // BidRequest: &openrtb2.BidRequest{ - // Imp: []openrtb2.Imp{ - // { - // ID: "imp1", - // TagID: "tagid", - // }, - // }, - // }, - // }, - // Response: &openrtb2.BidResponse{}, - // }, - // rCtx: &models.RequestCtx{ - // Endpoint: models.EndpointV25, - // PubID: 5890, - // }, - // logInfo: false, - // forRespExt: true, - // }, - // want: want{ - // logger: ow.cfg.Endpoint + `?json={"pubid":5890,"pid":"0","pdvid":"0","sl":1,"dvc":{},"ft":0,"it":"sdk"}&pubid=5890`, - // header: http.Header{ - // models.USER_AGENT_HEADER: []string{""}, - // models.IP_HEADER: []string{""}, - // }, - // }, - // }, - // { - // name: "multi imps request", - // args: args{ - // ao: analytics.AuctionObject{ - // RequestWrapper: &openrtb_ext.RequestWrapper{ - // BidRequest: &openrtb2.BidRequest{ - // Imp: []openrtb2.Imp{ - // { - // ID: "imp_1", - // TagID: "tagid_1", - // }, - // { - // ID: "imp_2", - // TagID: "tagid_2", - // }, - // }, - // }, - // }, - // Response: &openrtb2.BidResponse{}, - // }, - // rCtx: &models.RequestCtx{ - // PubID: 5890, - // Endpoint: models.EndpointV25, - // ImpBidCtx: map[string]models.ImpCtx{ - // "imp_1": { - // SlotName: "imp_1_tagid_1", - // AdUnitName: "tagid_1", - // }, - // "imp_2": { - // AdUnitName: "tagid_2", - // SlotName: "imp_2_tagid_2", - // }, - // }, - // }, - // logInfo: false, - // forRespExt: true, - // }, - // want: want{ - // logger: ow.cfg.Endpoint + `?json={"pubid":5890,"pid":"0","pdvid":"0","sl":1,"s":[{"sid":"sid","sn":"imp_1_tagid_1","au":"tagid_1","ps":[]},{"sid":"sid","sn":"imp_2_tagid_2","au":"tagid_2","ps":[]}],"dvc":{},"ft":0,"it":"sdk"}&pubid=5890`, - // header: http.Header{ - // models.USER_AGENT_HEADER: []string{""}, - // models.IP_HEADER: []string{""}, - // }, - // }, - // }, - // { - // name: "multi imps request and one request has incomingslots", - // args: args{ - // ao: analytics.AuctionObject{ - // RequestWrapper: &openrtb_ext.RequestWrapper{ - // BidRequest: &openrtb2.BidRequest{ - // Imp: []openrtb2.Imp{ - // { - // ID: "imp_1", - // TagID: "tagid_1", - // }, - // { - // ID: "imp_2", - // TagID: "tagid_2", - // }, - // }, - // }, - // }, - // Response: &openrtb2.BidResponse{}, - // }, - // rCtx: &models.RequestCtx{ - // PubID: 5890, - // Endpoint: models.EndpointV25, - // ImpBidCtx: map[string]models.ImpCtx{ - // "imp_1": { - // IncomingSlots: []string{"0x0v", "100x200"}, - // IsRewardInventory: ptrutil.ToPtr(int8(1)), - // SlotName: "imp_1_tagid_1", - // AdUnitName: "tagid_1", - // }, - // "imp_2": { - // AdUnitName: "tagid_2", - // SlotName: "imp_2_tagid_2", - // }, - // }, - // }, - // logInfo: false, - // forRespExt: true, - // }, - // want: want{ - // logger: ow.cfg.Endpoint + `?json={"pubid":5890,"pid":"0","pdvid":"0","sl":1,"s":[{"sid":"sid","sn":"imp_1_tagid_1","sz":["0x0v","100x200"],"au":"tagid_1","ps":[],"rwrd":1},{"sid":"sid","sn":"imp_2_tagid_2","au":"tagid_2","ps":[]}],"dvc":{},"ft":0,"it":"sdk"}&pubid=5890`, - // header: http.Header{ - // models.USER_AGENT_HEADER: []string{""}, - // models.IP_HEADER: []string{""}, - // }, - // }, - // }, + { + name: "req.Imp not mapped in ImpBidCtx", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + { + ID: "imp1", + TagID: "tagid", + }, + }, + }, + }, + Response: &openrtb2.BidResponse{}, + }, + rCtx: &models.RequestCtx{ + Endpoint: models.EndpointV25, + PubID: 5890, + }, + logInfo: false, + forRespExt: true, + }, + want: want{ + logger: ow.cfg.Endpoint + `?json={"pubid":5890,"pid":"0","pdvid":"0","sl":1,"dvc":{},"ft":0,"it":"sdk"}&pubid=5890`, + header: http.Header{ + models.USER_AGENT_HEADER: []string{""}, + models.IP_HEADER: []string{""}, + }, + }, + }, + { + name: "multi imps request", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + { + ID: "imp_1", + TagID: "tagid_1", + }, + { + ID: "imp_2", + TagID: "tagid_2", + }, + }, + }, + }, + Response: &openrtb2.BidResponse{}, + }, + rCtx: &models.RequestCtx{ + PubID: 5890, + Endpoint: models.EndpointV25, + ImpBidCtx: map[string]models.ImpCtx{ + "imp_1": { + SlotName: "imp_1_tagid_1", + AdUnitName: "tagid_1", + }, + "imp_2": { + AdUnitName: "tagid_2", + SlotName: "imp_2_tagid_2", + }, + }, + }, + logInfo: false, + forRespExt: true, + }, + want: want{ + logger: ow.cfg.Endpoint + `?json={"pubid":5890,"pid":"0","pdvid":"0","sl":1,"s":[{"sid":"sid","sn":"imp_1_tagid_1","au":"tagid_1","ps":[]},{"sid":"sid","sn":"imp_2_tagid_2","au":"tagid_2","ps":[]}],"dvc":{},"ft":0,"it":"sdk"}&pubid=5890`, + header: http.Header{ + models.USER_AGENT_HEADER: []string{""}, + models.IP_HEADER: []string{""}, + }, + }, + }, + { + name: "multi imps request and one request has incomingslots", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + { + ID: "imp_1", + TagID: "tagid_1", + }, + { + ID: "imp_2", + TagID: "tagid_2", + }, + }, + }, + }, + Response: &openrtb2.BidResponse{}, + }, + rCtx: &models.RequestCtx{ + PubID: 5890, + Endpoint: models.EndpointV25, + ImpBidCtx: map[string]models.ImpCtx{ + "imp_1": { + IncomingSlots: []string{"0x0v", "100x200"}, + IsRewardInventory: ptrutil.ToPtr(int8(1)), + SlotName: "imp_1_tagid_1", + AdUnitName: "tagid_1", + }, + "imp_2": { + AdUnitName: "tagid_2", + SlotName: "imp_2_tagid_2", + }, + }, + }, + logInfo: false, + forRespExt: true, + }, + want: want{ + logger: ow.cfg.Endpoint + `?json={"pubid":5890,"pid":"0","pdvid":"0","sl":1,"s":[{"sid":"sid","sn":"imp_1_tagid_1","sz":["0x0v","100x200"],"au":"tagid_1","ps":[],"rwrd":1},{"sid":"sid","sn":"imp_2_tagid_2","au":"tagid_2","ps":[]}],"dvc":{},"ft":0,"it":"sdk"}&pubid=5890`, + header: http.Header{ + models.USER_AGENT_HEADER: []string{""}, + models.IP_HEADER: []string{""}, + }, + }, + }, { name: "multi imps request and one imp has partner record", args: args{ @@ -4943,3 +5284,97 @@ func Test_getFloorValueFromUpdatedRequest(t *testing.T) { }) } } + +func TestGetBidPriceAfterCurrencyConversion(t *testing.T) { + type args struct { + price float64 + requestCurrencies []string + responseCurrency string + currencyConverter func(fromCurrency string, toCurrency string, value float64) (float64, error) + } + tests := []struct { + name string + args args + want float64 + }{ + { + name: "Single request currency - successful conversion", + args: args{ + price: 100.0, + requestCurrencies: []string{"EUR"}, + responseCurrency: "USD", + currencyConverter: func(fromCurrency string, toCurrency string, value float64) (float64, error) { + if fromCurrency == "USD" && toCurrency == "EUR" { + return 85.0, nil // Assuming conversion rate USD to EUR is 0.85 + } + return 0, fmt.Errorf("unsupported conversion") + }, + }, + want: 85.0, + }, + { + name: "Multiple request currencies - first successful conversion", + args: args{ + price: 100.0, + requestCurrencies: []string{"EUR", "GBP"}, + responseCurrency: "USD", + currencyConverter: func(fromCurrency string, toCurrency string, value float64) (float64, error) { + if fromCurrency == "USD" && toCurrency == "EUR" { + return 85.0, nil // Successful conversion to EUR + } + return 0, fmt.Errorf("unsupported conversion") + }, + }, + want: 85.0, + }, + { + name: "Multiple request currencies - second successful conversion", + args: args{ + price: 100.0, + requestCurrencies: []string{"JPY", "GBP"}, + responseCurrency: "USD", + currencyConverter: func(fromCurrency string, toCurrency string, value float64) (float64, error) { + if fromCurrency == "USD" && toCurrency == "GBP" { + return 75.0, nil // Successful conversion to GBP + } + return 0, fmt.Errorf("unsupported conversion") + }, + }, + want: 75.0, + }, + { + name: "No request currencies provided - default to USD", + args: args{ + price: 100.0, + requestCurrencies: []string{}, + responseCurrency: "USD", + currencyConverter: func(fromCurrency string, toCurrency string, value float64) (float64, error) { + if fromCurrency == "USD" && toCurrency == "USD" { + return 100.0, nil // No conversion needed + } + return 0, fmt.Errorf("unsupported conversion") + }, + }, + want: 100.0, + }, + { + name: "Conversion fails for all currencies", + args: args{ + price: 100.0, + requestCurrencies: []string{"JPY", "CNY"}, + responseCurrency: "USD", + currencyConverter: func(fromCurrency string, toCurrency string, value float64) (float64, error) { + return 0, fmt.Errorf("conversion failed") + }, + }, + want: 0.0, // Default to 0 on failure + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := GetBidPriceAfterCurrencyConversion(tt.args.price, tt.args.requestCurrencies, tt.args.responseCurrency, tt.args.currencyConverter) + assert.Equal(t, tt.want, got, "mismatched price") + }) + } +} diff --git a/go.mod b/go.mod index 5badc4d5deb..fdf70090dc8 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/PubMatic-OpenWrap/prebid-server/v2 go 1.20 -replace git.pubmatic.com/vastunwrap => git.pubmatic.com/PubMatic/vastunwrap v0.0.0-20240319050712-0b288cbb5a5d +replace git.pubmatic.com/vastunwrap => git.pubmatic.com/PubMatic/vastunwrap v0.0.0-20240827084017-0e392d3beb8b require ( github.com/DATA-DOG/go-sqlmock v1.5.0 @@ -91,7 +91,6 @@ require ( github.com/yudai/pp v2.0.1+incompatible // indirect golang.org/x/crypto v0.21.0 // indirect golang.org/x/sys v0.18.0 // indirect - github.com/yudai/pp v2.0.1+incompatible // indirect google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index 6d50b0b288f..4451dc12c04 100644 --- a/go.sum +++ b/go.sum @@ -53,8 +53,8 @@ git.pubmatic.com/PubMatic/go-common v0.0.0-20240313090142-97ff3d63b7c3 h1:Ea8zwi git.pubmatic.com/PubMatic/go-common v0.0.0-20240313090142-97ff3d63b7c3/go.mod h1:c/I6IcDn4Mtq4mmw8wGJN3v0o10nIMX7VTuQnsalUw0= git.pubmatic.com/PubMatic/go-netacuity-client v0.0.0-20240104092757-5d6f15e25fe3 h1:zQUpPJOjTBGu2fIydrfRWphH7EWLlBE/Qgn64BSoccI= git.pubmatic.com/PubMatic/go-netacuity-client v0.0.0-20240104092757-5d6f15e25fe3/go.mod h1:w733mqJnHt0hLR9mIFMzyDR0D94qzc7mFHsuE0tFQho= -git.pubmatic.com/PubMatic/vastunwrap v0.0.0-20240319050712-0b288cbb5a5d h1:BgLUpJQ9Z89eDGz//voK74G/8FgjgVg2PWVbjgCJ4+A= -git.pubmatic.com/PubMatic/vastunwrap v0.0.0-20240319050712-0b288cbb5a5d/go.mod h1:kcoJf7k+xug8X8fLWmsiKhPnYP+k7RZkfUoUo5QF+KA= +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= diff --git a/hooks/hookexecution/executor.go b/hooks/hookexecution/executor.go index 0ba466acc8d..2249fd29d42 100644 --- a/hooks/hookexecution/executor.go +++ b/hooks/hookexecution/executor.go @@ -260,10 +260,10 @@ func (e *hookExecutor) ExecuteRawBidderResponseStage(response *adapters.BidderRe stageName := hooks.StageRawBidderResponse.String() executionCtx := e.newContext(stageName) - payload := hookstage.RawBidderResponsePayload{Bids: response.Bids, Bidder: bidder} + payload := hookstage.RawBidderResponsePayload{BidderResponse: response, Bidder: bidder} outcome, payload, contexts, reject := executeStage(executionCtx, plan, payload, handler, e.metricEngine) - response.Bids = payload.Bids + response = payload.BidderResponse outcome.Entity = entity(bidder) outcome.Stage = stageName diff --git a/hooks/hookexecution/mocks_test.go b/hooks/hookexecution/mocks_test.go index a52fdbe12a7..968b1a7540b 100644 --- a/hooks/hookexecution/mocks_test.go +++ b/hooks/hookexecution/mocks_test.go @@ -154,7 +154,7 @@ func (e mockTimeoutHook) HandleRawBidderResponseHook(_ context.Context, _ hookst time.Sleep(20 * time.Millisecond) c := hookstage.ChangeSet[hookstage.RawBidderResponsePayload]{} c.AddMutation(func(payload hookstage.RawBidderResponsePayload) (hookstage.RawBidderResponsePayload, error) { - payload.Bids[0].BidMeta = &openrtb_ext.ExtBidPrebidMeta{AdapterCode: "new-code"} + payload.BidderResponse.Bids[0].BidMeta = &openrtb_ext.ExtBidPrebidMeta{AdapterCode: "new-code"} return payload, nil }, hookstage.MutationUpdate, "bidderResponse", "bidMeta.AdapterCode") @@ -371,7 +371,7 @@ func (e mockUpdateBidderResponseHook) HandleRawBidderResponseHook(_ context.Cont c := hookstage.ChangeSet[hookstage.RawBidderResponsePayload]{} c.AddMutation( func(payload hookstage.RawBidderResponsePayload) (hookstage.RawBidderResponsePayload, error) { - payload.Bids[0].DealPriority = 10 + payload.BidderResponse.Bids[0].DealPriority = 10 return payload, nil }, hookstage.MutationUpdate, "bidderResponse", "bid.deal-priority", ) diff --git a/hooks/hookstage/rawbidderresponse.go b/hooks/hookstage/rawbidderresponse.go index 7d08a7d2e02..1f6aaaa64ef 100644 --- a/hooks/hookstage/rawbidderresponse.go +++ b/hooks/hookstage/rawbidderresponse.go @@ -25,6 +25,6 @@ type RawBidderResponse interface { // objects representing bids returned by a particular bidder. // Hooks are allowed to modify bids using mutations. type RawBidderResponsePayload struct { - Bids []*adapters.TypedBid - Bidder string + BidderResponse *adapters.BidderResponse + Bidder string } diff --git a/hooks/hookstage/rawbidderresponse_mutations.go b/hooks/hookstage/rawbidderresponse_mutations.go index efab874fa15..c5022c31218 100644 --- a/hooks/hookstage/rawbidderresponse_mutations.go +++ b/hooks/hookstage/rawbidderresponse_mutations.go @@ -33,7 +33,7 @@ func (c ChangeSetBids[T]) Update(bids []*adapters.TypedBid) { c.changeSetRawBidderResponse.changeSet.AddMutation(func(p T) (T, error) { bidderPayload, err := c.changeSetRawBidderResponse.castPayload(p) if err == nil { - bidderPayload.Bids = bids + bidderPayload.BidderResponse.Bids = bids } if payload, ok := any(bidderPayload).(T); ok { return payload, nil diff --git a/modules/prebid/ortb2blocking/hook_raw_bidder_response.go b/modules/prebid/ortb2blocking/hook_raw_bidder_response.go index 215de260b09..e1a4d9c60dd 100644 --- a/modules/prebid/ortb2blocking/hook_raw_bidder_response.go +++ b/modules/prebid/ortb2blocking/hook_raw_bidder_response.go @@ -34,7 +34,7 @@ func handleRawBidderResponseHook( // allowedBids will store all bids that have passed the attribute check allowedBids := make([]*adapters.TypedBid, 0) - for _, bid := range payload.Bids { + for _, bid := range payload.BidderResponse.Bids { failedChecksData := make(map[string]interface{}) bidMediaTypes := mediaTypesFromBid(bid) @@ -77,7 +77,7 @@ func handleRawBidderResponseHook( } changeSet := hookstage.ChangeSet[hookstage.RawBidderResponsePayload]{} - if len(payload.Bids) != len(allowedBids) { + if len(payload.BidderResponse.Bids) != len(allowedBids) { changeSet.RawBidderResponse().Bids().Update(allowedBids) result.ChangeSet = changeSet } diff --git a/modules/prebid/ortb2blocking/module_test.go b/modules/prebid/ortb2blocking/module_test.go index 8178cba15fe..053fc9bfc75 100644 --- a/modules/prebid/ortb2blocking/module_test.go +++ b/modules/prebid/ortb2blocking/module_test.go @@ -614,9 +614,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }{ { description: "Payload not changed when empty account config and empty module contexts are provided. Analytic tags have successful records", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ADomain: []string{"foo"}, ImpID: impID1}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ADomain: []string{"foo"}, ImpID: impID1}, + }, }, }}, expectedBids: []*adapters.TypedBid{ @@ -643,9 +645,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Catch error if wrong data has been passed from previous hook. Payload not changed", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ADomain: []string{"foo"}, ImpID: impID1}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ADomain: []string{"foo"}, ImpID: impID1}, + }, }, }}, expectedBids: []*adapters.TypedBid{ @@ -658,12 +662,14 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid blocked by badv attribute check. Payload updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, + }, }, }}, config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": true}}}`), @@ -701,12 +707,14 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid not blocked because blocking conditions for current bidder do not exist. Payload not updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, + }, }, }}, config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": true}}}`), @@ -742,12 +750,14 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid not blocked because enforce blocking is disabled by account config. Payload not updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, + }, }, }}, config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": false}}}`), @@ -783,12 +793,14 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid not blocked because enforce blocking overridden for given bidder. Payload not updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, + }, }, }}, config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": true, "action_overrides": {"enforce_blocks": [{"conditions": {"bidders": ["appnexus"]}, "override": false}]}}}}`), @@ -824,12 +836,14 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid blocked by badv attribute check (block unknown attributes). Payload updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, + }, }, }}, config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": true, "block_unknown_adomain": true}}}`), @@ -867,12 +881,14 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid not blocked because block unknown overridden for given bidder. Payload not updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", ImpID: impID2}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", ImpID: impID2}, + }, }, }}, config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": true, "block_unknown_adomain": true, "action_overrides": {"block_unknown_adomain": [{"conditions": {"bidders": ["appnexus"]}, "override": false}]}}}}`), @@ -908,12 +924,14 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid not blocked due to deal exception. Payload not updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1, DealID: "acceptDealID"}, - }, - { - Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1, DealID: "acceptDealID"}, + }, + { + Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, + }, }, }}, config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": true, "action_overrides": {"allowed_adomain_for_deals": [{"conditions": {"deal_ids": ["acceptDealID"]}, "override": ["forbidden_domain"]}]}}}}`), @@ -949,9 +967,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if there is an issue processing enforce blocks overrides for badv attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}}, + }, }, }}, config: json.RawMessage(`{"attributes": {"badv": {"enforce_blocks": true, "action_overrides": {"enforce_blocks": [{"conditions": {}}]}}}}`), @@ -974,9 +994,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if there is an issue processing block unknown domains overrides for badv attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}}, + }, }, }}, config: json.RawMessage(`{"attributes": {"badv": {"enforce_blocks": true, "action_overrides": {"block_unknown_adomain": [{"conditions": {}}]}}}}`), @@ -999,9 +1021,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if deal_ids not defined in config override conditions for badv attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, DealID: "acceptDealID"}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, DealID: "acceptDealID"}, + }, }, }}, config: json.RawMessage(`{"attributes": {"badv": {"enforce_blocks": true, "action_overrides": {"allowed_adomain_for_deals": [{"conditions": {}}]}}}}`), @@ -1024,12 +1048,14 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid blocked by bcat attribute check. Payload updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", Cat: []string{"moto"}, ImpID: impID2}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", Cat: []string{"moto"}, ImpID: impID2}, + }, }, }}, config: json.RawMessage(`{"attributes":{"bcat":{"enforce_blocks": true}}}`), @@ -1067,9 +1093,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if there is an issue processing enforce blocks overrides for bcat attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1}, + }, }, }}, config: json.RawMessage(`{"attributes": {"bcat": {"enforce_blocks": true, "action_overrides": {"enforce_blocks": [{"conditions": {}}]}}}}`), @@ -1092,9 +1120,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if there is an issue processing block unknown domains overrides for bcat attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1}, + }, }, }}, config: json.RawMessage(`{"attributes": {"bcat": {"enforce_blocks": true, "action_overrides": {"block_unknown_adv_cat": [{"conditions": {}}]}}}}`), @@ -1117,9 +1147,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if deal_ids not defined in config override conditions for bcat attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1, DealID: "acceptDealID"}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1, DealID: "acceptDealID"}, + }, }, }}, config: json.RawMessage(`{"attributes": {"bcat": {"enforce_blocks": true, "action_overrides": {"allowed_adv_cat_for_deals": [{"conditions": {}}]}}}}`), @@ -1142,12 +1174,14 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid blocked by cattax attribute check. Payload updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", CatTax: 1, ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", CatTax: 2, ImpID: impID2}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", CatTax: 1, ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", CatTax: 2, ImpID: impID2}, + }, }, }}, config: json.RawMessage(`{"attributes":{"bcat":{"enforce_blocks": true}}}`), @@ -1185,12 +1219,14 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid blocked by cattax attribute check (the default value used if no blocking attribute passed). Payload updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", CatTax: 1, ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", CatTax: 2, ImpID: impID2}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", CatTax: 1, ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", CatTax: 2, ImpID: impID2}, + }, }, }}, config: json.RawMessage(`{"attributes":{"bcat":{"enforce_blocks": true}}}`), @@ -1227,12 +1263,14 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid blocked by bapp attribute check. Payload updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Bundle: "forbidden_bundle", ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", Bundle: "allowed_bundle", ImpID: impID2}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Bundle: "forbidden_bundle", ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", Bundle: "allowed_bundle", ImpID: impID2}, + }, }, }}, config: json.RawMessage(`{"attributes":{"bapp":{"enforce_blocks": true}}}`), @@ -1270,9 +1308,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if there is an issue processing enforce blocks overrides for bapp attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Bundle: "forbidden_bundle", ImpID: impID1}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Bundle: "forbidden_bundle", ImpID: impID1}, + }, }, }}, config: json.RawMessage(`{"attributes": {"bapp": {"enforce_blocks": true, "action_overrides": {"enforce_blocks": [{"conditions": {}}]}}}}`), @@ -1295,9 +1335,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if deal_ids not defined in config override conditions for bapp attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Bundle: "forbidden_bundle", ImpID: impID1, DealID: "acceptDealID"}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Bundle: "forbidden_bundle", ImpID: impID1, DealID: "acceptDealID"}, + }, }, }}, config: json.RawMessage(`{"attributes": {"bapp": {"enforce_blocks": true, "action_overrides": {"allowed_app_for_deals": [{"conditions": {}}]}}}}`), @@ -1320,12 +1362,14 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid blocked by battr attribute check. Payload updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Attr: []adcom1.CreativeAttribute{1}, ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", Attr: []adcom1.CreativeAttribute{2}, ImpID: impID2}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Attr: []adcom1.CreativeAttribute{1}, ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", Attr: []adcom1.CreativeAttribute{2}, ImpID: impID2}, + }, }, }}, config: json.RawMessage(`{"attributes":{"battr":{"enforce_blocks": true}}}`), @@ -1363,9 +1407,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if there is an issue processing enforce blocks overrides for battr attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Attr: []adcom1.CreativeAttribute{1}, ImpID: impID1}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Attr: []adcom1.CreativeAttribute{1}, ImpID: impID1}, + }, }, }}, config: json.RawMessage(`{"attributes": {"battr": {"enforce_blocks": true, "action_overrides": {"enforce_blocks": [{"conditions": {}}]}}}}`), @@ -1388,9 +1434,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if deal_ids not defined in config override conditions for battr attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Attr: []adcom1.CreativeAttribute{1}, ImpID: impID1, DealID: "acceptDealID"}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Attr: []adcom1.CreativeAttribute{1}, ImpID: impID1, DealID: "acceptDealID"}, + }, }, }}, config: json.RawMessage(`{"attributes": {"battr": {"enforce_blocks": true, "action_overrides": {"allowed_banner_attr_for_deals": [{"conditions": {}}]}}}}`), @@ -1413,27 +1461,29 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid blocked by multiple attribute check. Payload updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ - ID: "1", - ADomain: []string{"forbidden_domain"}, - Cat: []string{"fishing"}, - CatTax: 1, - Bundle: "forbidden_bundle", - Attr: []adcom1.CreativeAttribute{1}, - ImpID: impID1, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "1", + ADomain: []string{"forbidden_domain"}, + Cat: []string{"fishing"}, + CatTax: 1, + Bundle: "forbidden_bundle", + Attr: []adcom1.CreativeAttribute{1}, + ImpID: impID1, + }, }, - }, - { - Bid: &openrtb2.Bid{ - ID: "2", - ADomain: []string{"allowed_domain"}, - Cat: []string{"moto"}, - CatTax: 2, - Bundle: "allowed_bundle", - Attr: []adcom1.CreativeAttribute{2}, - ImpID: impID2, + { + Bid: &openrtb2.Bid{ + ID: "2", + ADomain: []string{"allowed_domain"}, + Cat: []string{"moto"}, + CatTax: 2, + Bundle: "allowed_bundle", + Attr: []adcom1.CreativeAttribute{2}, + ImpID: impID2, + }, }, }, }}, @@ -1514,7 +1564,7 @@ func TestHandleRawBidderResponseHook(t *testing.T) { assert.NoError(t, err) test.payload = newPayload } - assert.Equal(t, test.expectedBids, test.payload.Bids, "Invalid Bids returned after executing RawBidderResponse hook.") + assert.Equal(t, test.expectedBids, test.payload.BidderResponse.Bids, "Invalid Bids returned after executing RawBidderResponse hook.") // reset ChangeSet not to break hookResult assertion, we validated ChangeSet separately hookResult.ChangeSet = hookstage.ChangeSet[hookstage.RawBidderResponsePayload]{} diff --git a/modules/pubmatic/openwrap/config/config.go b/modules/pubmatic/openwrap/config/config.go index 0a634af2f62..112a50a2770 100755 --- a/modules/pubmatic/openwrap/config/config.go +++ b/modules/pubmatic/openwrap/config/config.go @@ -101,7 +101,6 @@ type PixelView struct { type FeatureToggle struct { VASTUnwrapPercent int - VASTUnwrapStatsPercent int AnalyticsThrottlingPercentage string } diff --git a/modules/pubmatic/openwrap/hook_raw_bidder_response.go b/modules/pubmatic/openwrap/hook_raw_bidder_response.go index 8bdc4f06b00..a97f80a5af4 100644 --- a/modules/pubmatic/openwrap/hook_raw_bidder_response.go +++ b/modules/pubmatic/openwrap/hook_raw_bidder_response.go @@ -2,14 +2,20 @@ package openwrap import ( "fmt" - "sync" "github.com/prebid/prebid-server/v2/adapters" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/nbr" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/prebid/prebid-server/v2/hooks/hookstage" ) +type BidUnwrapInfo struct { + bid *adapters.TypedBid + unwrapStatus string +} + func (m OpenWrap) handleRawBidderResponseHook( miCtx hookstage.ModuleInvocationContext, payload hookstage.RawBidderResponsePayload, @@ -20,41 +26,63 @@ func (m OpenWrap) handleRawBidderResponseHook( return result, nil } - if vastRequestContext.VastUnwrapEnabled { - // Do Unwrap and Update Adm - wg := new(sync.WaitGroup) - for _, bid := range payload.Bids { - if string(bid.BidType) == models.MediaTypeVideo { - wg.Add(1) - go func(bid *adapters.TypedBid) { - defer wg.Done() - m.unwrap.Unwrap(miCtx.AccountID, payload.Bidder, bid, vastRequestContext.UA, vastRequestContext.IP, vastRequestContext.VastUnwrapStatsEnabled) - }(bid) - } - } - wg.Wait() - changeSet := hookstage.ChangeSet[hookstage.RawBidderResponsePayload]{} - changeSet.RawBidderResponse().Bids().Update(payload.Bids) - result.ChangeSet = changeSet - } else { - vastRequestContext.VastUnwrapStatsEnabled = GetRandomNumberIn1To100() <= m.cfg.Features.VASTUnwrapStatsPercent - if vastRequestContext.VastUnwrapStatsEnabled { - // Do Unwrap and Collect stats only - for _, bid := range payload.Bids { - if string(bid.BidType) == models.MediaTypeVideo { - go func(bid *adapters.TypedBid) { - m.unwrap.Unwrap(miCtx.AccountID, payload.Bidder, bid, vastRequestContext.UA, vastRequestContext.IP, vastRequestContext.VastUnwrapStatsEnabled) - }(bid) - } - } + if !vastRequestContext.VastUnwrapEnabled { + return result, nil + } + + seatNonBid := openrtb_ext.NonBidCollection{} + unwrappedBids := make([]*adapters.TypedBid, 0, len(payload.BidderResponse.Bids)) + unwrappedBidsChan := make(chan BidUnwrapInfo, len(payload.BidderResponse.Bids)) + defer close(unwrappedBidsChan) + + unwrappedBidsCnt, unwrappedSuccessBidCnt := 0, 0 + totalBidCnt := len(payload.BidderResponse.Bids) + // send bids for unwrap + for _, bid := range payload.BidderResponse.Bids { + if !isEligibleForUnwrap(bid) { + unwrappedBids = append(unwrappedBids, bid) + continue } + unwrappedBidsCnt++ + go func(bid adapters.TypedBid) { + unwrapStatus := m.unwrap.Unwrap(&bid, miCtx.AccountID, payload.Bidder, vastRequestContext.UA, vastRequestContext.IP) + unwrappedBidsChan <- BidUnwrapInfo{&bid, unwrapStatus} + }(*bid) } - if vastRequestContext.VastUnwrapEnabled || vastRequestContext.VastUnwrapStatsEnabled { - result.DebugMessages = append(result.DebugMessages, - fmt.Sprintf("For pubid:[%d] VastUnwrapEnabled: [%v] VastUnwrapStatsEnabled:[%v] ", - vastRequestContext.PubID, vastRequestContext.VastUnwrapEnabled, vastRequestContext.VastUnwrapStatsEnabled)) + // collect bids after unwrap + for i := 0; i < unwrappedBidsCnt; i++ { + unwrappedBid := <-unwrappedBidsChan + if !rejectBid(unwrappedBid.unwrapStatus) { + unwrappedSuccessBidCnt++ + unwrappedBids = append(unwrappedBids, unwrappedBid.bid) + continue + } + seatNonBid.AddBid(openrtb_ext.NewNonBid(openrtb_ext.NonBidParams{ + Bid: unwrappedBid.bid.Bid, + NonBidReason: int(nbr.LossBidLostInVastUnwrap), + DealPriority: unwrappedBid.bid.DealPriority, + BidMeta: unwrappedBid.bid.BidMeta, + BidType: unwrappedBid.bid.BidType, + BidVideo: unwrappedBid.bid.BidVideo, + OriginalBidCur: payload.BidderResponse.Currency, + }), payload.Bidder, + ) } + changeSet := hookstage.ChangeSet[hookstage.RawBidderResponsePayload]{} + changeSet.RawBidderResponse().Bids().Update(unwrappedBids) + result.ChangeSet = changeSet + result.SeatNonBid = seatNonBid + result.DebugMessages = append(result.DebugMessages, + fmt.Sprintf("For pubid:[%d] VastUnwrapEnabled: [%v] Total Input Bids: [%d] Total Bids sent for unwrapping: [%d] Total Unwrap Success: [%d]", vastRequestContext.PubID, vastRequestContext.VastUnwrapEnabled, totalBidCnt, unwrappedBidsCnt, unwrappedSuccessBidCnt)) return result, nil } + +func isEligibleForUnwrap(bid *adapters.TypedBid) bool { + return bid != nil && bid.BidType == openrtb_ext.BidTypeVideo && bid.Bid != nil && bid.Bid.AdM != "" +} + +func rejectBid(bidUnwrapStatus string) bool { + return bidUnwrapStatus == models.UnwrapEmptyVASTStatus || bidUnwrapStatus == models.UnwrapInvalidVASTStatus +} diff --git a/modules/pubmatic/openwrap/hook_raw_bidder_response_test.go b/modules/pubmatic/openwrap/hook_raw_bidder_response_test.go index 07130c3aed3..421dd4f6285 100644 --- a/modules/pubmatic/openwrap/hook_raw_bidder_response_test.go +++ b/modules/pubmatic/openwrap/hook_raw_bidder_response_test.go @@ -3,7 +3,6 @@ package openwrap import ( "fmt" "net/http" - "testing" unWrapCfg "git.pubmatic.com/vastunwrap/config" @@ -14,7 +13,9 @@ import ( "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/config" mock_metrics "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics/mock" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/nbr" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/unwrap" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -23,7 +24,6 @@ var invalidVastXMLAdM = "PubMaticAcudeo CompatibleVAST 2.0 Instream Test 1VAST 2.0 Instream Test 1https://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&er=[ERRORCODE]https://track.dsptracker.com?p=1234&er=[ERRORCODE]https://aktrack.pubmatic.com/AdServer/AdDisplayTrackerServlet?operId=1&pubId=64195&siteId=47105&adId=1405154&adType=13&adServerId=243&kefact=1.000000&kaxefact=1.000000&kadNetFrequecy=0&kadwidth=0&kadheight=0&kadsizeid=97&kltstamp=1536933242&indirectAdId=0&adServerOptimizerId=2&ranreq=0.05969169352174375&kpbmtpfact=11.000000&dcId=1&tldId=0&passback=0&svr=ktk57&ekefact=er2bW2sDAwCra06ACbsIQySn5nqBtYsTl8fy5lupAexh37D_&ekaxefact=er2bW4EDAwB_LQpJJ23Fq0DcNC-NSAFXdpSQC8XBk_S33_Fa&ekpbmtpfact=er2bW5MDAwDJHdBnLBt5IrRuh7x0oqp_tjIALv_VvSQDAl6R&crID=m:1_x:3_y:3_p:11_va:3&lpu=ae.com&ucrid=678722001014421372&campaignId=16774&creativeId=0&pctr=0.000000&wDSPByrId=511&wDspId=27&wbId=0&wrId=0&wAdvID=3170&isRTB=1&rtbId=EBCA079F-8D7C-45B8-B733-92951F670AA1&imprId=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&oid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&pageURL=http%253A%252F%252Fowsdk-stagingams.pubmatic.com%253A8443%252Fvast-validator%252F%2523&sec=1&pmc=1https://DspImpressionTracker.com/https://mytracking.com/linear/closehttps://mytracking.com/linear/skiphttps://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&e=1https://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&e=2https://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&e=3https://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&e=4https://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&e=5https://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&e=600:00:04https://www.automationtester.inhttps://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&e=99https://stagingams.pubmatic.com:8443/openwrap/media/pubmatic.mp4https://stagingams.pubmatic.com:8443/openwrap/media/pubmatic.mp4https://stagingams.pubmatic.com:8443/openwrap/media/mp4-sample-3.mp4" func TestHandleRawBidderResponseHook(t *testing.T) { - ctrl := gomock.NewController(t) defer ctrl.Finish() mockMetricsEngine := mock_metrics.NewMockMetricsEngine(ctrl) @@ -33,27 +33,25 @@ func TestHandleRawBidderResponseHook(t *testing.T) { payload hookstage.RawBidderResponsePayload moduleInvocationCtx hookstage.ModuleInvocationContext isAdmUpdated bool - randomNumber int } tests := []struct { - name string - args args - wantResult hookstage.HookResult[hookstage.RawBidderResponsePayload] - setup func() - wantErr bool - mockHandler http.HandlerFunc + name string + args args + wantResult hookstage.HookResult[hookstage.RawBidderResponsePayload] + setup func() + wantSeatNonBid openrtb_ext.NonBidCollection + mockHandler http.HandlerFunc + wantBids []*adapters.TypedBid }{ - { - name: "Empty Request Context", + name: "Empty_Request_Context", args: args{ module: OpenWrap{}, }, wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{DebugMessages: []string{"error: request-ctx not found in handleRawBidderResponseHook()"}}, - wantErr: false, }, { - name: "Set Vast Unwrapper to false in request context with type video", + name: "VASTUnwrap_Disabled_Video_Bids", args: args{ module: OpenWrap{ cfg: config.Config{VastUnwrapCfg: unWrapCfg.VastUnWrapCfg{ @@ -64,32 +62,31 @@ func TestHandleRawBidderResponseHook(t *testing.T) { metricEngine: mockMetricsEngine, }, payload: hookstage.RawBidderResponsePayload{ - Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ - ID: "Bid-123", - ImpID: fmt.Sprintf("div-adunit-%d", 123), - Price: 2.1, - AdM: "
This is an Ad
", - CrID: "Cr-234", - W: 100, - H: 50, - }, - BidType: "video", - }}}, + BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: "
This is an Ad
", + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }}}}, moduleInvocationCtx: hookstage.ModuleInvocationContext{ModuleContext: hookstage.ModuleContext{models.RequestContext: models.RequestCtx{VastUnwrapEnabled: false}}}, - randomNumber: 1, }, wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, - wantErr: false, }, { - name: "Set Vast Unwrapper to false in request context with type video, stats enabled true", + name: "VASTUnwrap_Enabled_Single_Video_Bid_Invalid_Vast_xml", args: args{ module: OpenWrap{ cfg: config.Config{ Features: config.FeatureToggle{ - VASTUnwrapStatsPercent: 2, + VASTUnwrapPercent: 50, }, VastUnwrapCfg: unWrapCfg.VastUnWrapCfg{ MaxWrapperSupport: 5, @@ -99,30 +96,93 @@ func TestHandleRawBidderResponseHook(t *testing.T) { metricEngine: mockMetricsEngine, }, payload: hookstage.RawBidderResponsePayload{ - Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ - ID: "Bid-123", - ImpID: fmt.Sprintf("div-adunit-%d", 123), - Price: 2.1, - AdM: vastXMLAdM, - CrID: "Cr-234", - W: 100, - H: 50, + BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: invalidVastXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", }, - BidType: "video", - }}, + }, + }, Bidder: "pubmatic", }, - moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{models.RequestContext: models.RequestCtx{VastUnwrapEnabled: false}}}, - randomNumber: 1, + moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{models.RequestContext: models.RequestCtx{VastUnwrapEnabled: true}}}, + }, + mockHandler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Add("unwrap-status", "1") + w.WriteHeader(http.StatusNoContent) + }), + wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, + setup: func() { + mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", "1") + mockMetricsEngine.EXPECT().RecordUnwrapRequestTime("5890", "pubmatic", gomock.Any()) + }, + wantBids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: invalidVastXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }, }, + }, + { + name: "VASTUnwrap_Enabled_Single_Video_Bid", mockHandler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Header().Add("unwrap-status", "0") w.Header().Add("unwrap-count", "1") w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(vastXMLAdM)) + _, _ = w.Write([]byte(inlineXMLAdM)) }), + args: args{ + module: OpenWrap{ + cfg: config.Config{ + Features: config.FeatureToggle{ + VASTUnwrapPercent: 50, + }, + VastUnwrapCfg: unWrapCfg.VastUnWrapCfg{ + MaxWrapperSupport: 5, + StatConfig: unWrapCfg.StatConfig{Endpoint: "http://10.172.141.13:8080", PublishInterval: 1}, + APPConfig: unWrapCfg.AppConfig{UnwrapDefaultTimeout: 1500}, + }}, + metricEngine: mockMetricsEngine, + }, + payload: hookstage.RawBidderResponsePayload{ + BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: vastXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }, + }, + }, + Bidder: "pubmatic", + }, + moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{models.RequestContext: models.RequestCtx{VastUnwrapEnabled: true}}}, + isAdmUpdated: true, + }, wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, setup: func() { mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", "0") @@ -130,16 +190,28 @@ func TestHandleRawBidderResponseHook(t *testing.T) { mockMetricsEngine.EXPECT().RecordUnwrapRequestTime("5890", "pubmatic", gomock.Any()) mockMetricsEngine.EXPECT().RecordUnwrapRespTime("5890", "1", gomock.Any()) }, - wantErr: false, + wantBids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: inlineXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }, + }, }, { - name: "Set Vast Unwrapper to true in request context with invalid vast xml", + name: "VASTUnwrap_Enabled_Multiple_Video_Bids", args: args{ module: OpenWrap{ cfg: config.Config{ Features: config.FeatureToggle{ - VASTUnwrapStatsPercent: 2, - VASTUnwrapPercent: 50, + VASTUnwrapPercent: 100, }, VastUnwrapCfg: unWrapCfg.VastUnWrapCfg{ MaxWrapperSupport: 5, @@ -149,50 +221,173 @@ func TestHandleRawBidderResponseHook(t *testing.T) { metricEngine: mockMetricsEngine, }, payload: hookstage.RawBidderResponsePayload{ - Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ - ID: "Bid-123", - ImpID: fmt.Sprintf("div-adunit-%d", 123), - Price: 2.1, - AdM: invalidVastXMLAdM, - CrID: "Cr-234", - W: 100, - H: 50, + BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: vastXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", }, - BidType: "video", - }, + { + Bid: &openrtb2.Bid{ + ID: "Bid-456", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: vastXMLAdM, + CrID: "Cr-789", + W: 100, + H: 50, + }, + BidType: "video", + }}, }, Bidder: "pubmatic", }, moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{models.RequestContext: models.RequestCtx{VastUnwrapEnabled: true}}}, + isAdmUpdated: true, }, mockHandler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Add("unwrap-status", "1") - w.WriteHeader(http.StatusNoContent) + w.Header().Add("unwrap-status", "0") + w.Header().Add("unwrap-count", "1") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(inlineXMLAdM)) }), wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, setup: func() { - mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", "1") - mockMetricsEngine.EXPECT().RecordUnwrapRequestTime("5890", "pubmatic", gomock.Any()) + mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", "0").Times(2) + mockMetricsEngine.EXPECT().RecordUnwrapWrapperCount("5890", "pubmatic", "1").Times(2) + mockMetricsEngine.EXPECT().RecordUnwrapRequestTime("5890", "pubmatic", gomock.Any()).Times(2) + mockMetricsEngine.EXPECT().RecordUnwrapRespTime("5890", "1", gomock.Any()).Times(2) + }, + wantBids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-456", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: inlineXMLAdM, + CrID: "Cr-789", + W: 100, + H: 50, + }, + BidType: "video", + }, + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: inlineXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }, }, - wantErr: true, }, - { - name: "Set Vast Unwrapper to true in request context with type video", + name: "VASTUnwrap_Enabled_Video_and_Banner_Bids", + args: args{ + module: OpenWrap{ + cfg: config.Config{ + Features: config.FeatureToggle{ + VASTUnwrapPercent: 50, + }, + VastUnwrapCfg: unWrapCfg.VastUnWrapCfg{ + MaxWrapperSupport: 5, + StatConfig: unWrapCfg.StatConfig{Endpoint: "http://10.172.141.13:8080", PublishInterval: 1}, + APPConfig: unWrapCfg.AppConfig{UnwrapDefaultTimeout: 1500}, + }}, + metricEngine: mockMetricsEngine, + }, + payload: hookstage.RawBidderResponsePayload{ + BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: vastXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }, + { + Bid: &openrtb2.Bid{ + ID: "Bid-456", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: "This is banner creative", + CrID: "Cr-789", + W: 100, + H: 50, + }, + BidType: "banner", + }}, + }, + Bidder: "pubmatic", + }, + moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{models.RequestContext: models.RequestCtx{VastUnwrapEnabled: true}}}, + isAdmUpdated: true, + }, mockHandler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Header().Add("unwrap-status", "0") - w.Header().Add("unwrap-count", "1") + w.Header().Add("unwrap-count", "0") w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(inlineXMLAdM)) }), + wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, + setup: func() { + mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", "0") + mockMetricsEngine.EXPECT().RecordUnwrapWrapperCount("5890", "pubmatic", "0") + mockMetricsEngine.EXPECT().RecordUnwrapRequestTime("5890", "pubmatic", gomock.Any()) + mockMetricsEngine.EXPECT().RecordUnwrapRespTime("5890", "0", gomock.Any()) + }, + wantBids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-456", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: "This is banner creative", + CrID: "Cr-789", + W: 100, + H: 50, + }, + BidType: "banner", + }, + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: inlineXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }, + }, + }, + { + name: "VASTUnwrap_Enabled_Video_and_Native_Bids", args: args{ module: OpenWrap{ cfg: config.Config{ Features: config.FeatureToggle{ - VASTUnwrapStatsPercent: 2, - VASTUnwrapPercent: 50, + VASTUnwrapPercent: 50, }, VastUnwrapCfg: unWrapCfg.VastUnWrapCfg{ MaxWrapperSupport: 5, @@ -202,43 +397,85 @@ func TestHandleRawBidderResponseHook(t *testing.T) { metricEngine: mockMetricsEngine, }, payload: hookstage.RawBidderResponsePayload{ - Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ - ID: "Bid-123", - ImpID: fmt.Sprintf("div-adunit-%d", 123), - Price: 2.1, - AdM: vastXMLAdM, - CrID: "Cr-234", - W: 100, - H: 50, + BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: vastXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", }, - BidType: "video", - }, + { + Bid: &openrtb2.Bid{ + ID: "Bid-456", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: "This is native creative", + CrID: "Cr-789", + W: 100, + H: 50, + }, + BidType: "native", + }}, }, Bidder: "pubmatic", }, moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{models.RequestContext: models.RequestCtx{VastUnwrapEnabled: true}}}, - - isAdmUpdated: true, + isAdmUpdated: true, }, + mockHandler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Add("unwrap-status", "0") + w.Header().Add("unwrap-count", "0") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(inlineXMLAdM)) + }), wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, setup: func() { - mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", "0").AnyTimes() - mockMetricsEngine.EXPECT().RecordUnwrapWrapperCount("5890", "pubmatic", "1").AnyTimes() - mockMetricsEngine.EXPECT().RecordUnwrapRequestTime("5890", "pubmatic", gomock.Any()).AnyTimes() - mockMetricsEngine.EXPECT().RecordUnwrapRespTime("5890", "1", gomock.Any()).AnyTimes() + mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", "0") + mockMetricsEngine.EXPECT().RecordUnwrapWrapperCount("5890", "pubmatic", "0") + mockMetricsEngine.EXPECT().RecordUnwrapRequestTime("5890", "pubmatic", gomock.Any()) + mockMetricsEngine.EXPECT().RecordUnwrapRespTime("5890", "0", gomock.Any()) + }, + wantBids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-456", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: "This is native creative", + CrID: "Cr-789", + W: 100, + H: 50, + }, + BidType: "native", + }, + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: inlineXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }, }, - wantErr: false, }, { - name: "Set Vast Unwrapper to true in request context for multiple bids with type video", + name: "VASTUnwrap_Enabled_Single_Video_bid_and_source_owsdk", args: args{ module: OpenWrap{ cfg: config.Config{ Features: config.FeatureToggle{ - VASTUnwrapStatsPercent: 2, - VASTUnwrapPercent: 50, + VASTUnwrapPercent: 50, }, VastUnwrapCfg: unWrapCfg.VastUnWrapCfg{ MaxWrapperSupport: 5, @@ -248,36 +485,26 @@ func TestHandleRawBidderResponseHook(t *testing.T) { metricEngine: mockMetricsEngine, }, payload: hookstage.RawBidderResponsePayload{ - Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ - ID: "Bid-123", - ImpID: fmt.Sprintf("div-adunit-%d", 123), - Price: 2.1, - AdM: vastXMLAdM, - CrID: "Cr-234", - W: 100, - H: 50, + BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: vastXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", }, - BidType: "video", }, - { - Bid: &openrtb2.Bid{ - ID: "Bid-456", - ImpID: fmt.Sprintf("div-adunit-%d", 123), - Price: 2.1, - AdM: vastXMLAdM, - CrID: "Cr-789", - W: 100, - H: 50, - }, - BidType: "video", - }}, + }, Bidder: "pubmatic", }, moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{models.RequestContext: models.RequestCtx{VastUnwrapEnabled: true}}}, isAdmUpdated: true, - randomNumber: 10, }, mockHandler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Header().Add("unwrap-status", "0") @@ -287,22 +514,33 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }), wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, setup: func() { - mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", "0").AnyTimes() - mockMetricsEngine.EXPECT().RecordUnwrapWrapperCount("5890", "pubmatic", "1").AnyTimes() - mockMetricsEngine.EXPECT().RecordUnwrapRequestTime("5890", "pubmatic", gomock.Any()).AnyTimes() - mockMetricsEngine.EXPECT().RecordUnwrapRespTime("5890", "1", gomock.Any()).AnyTimes() + mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", "0") + mockMetricsEngine.EXPECT().RecordUnwrapWrapperCount("5890", "pubmatic", "1") + mockMetricsEngine.EXPECT().RecordUnwrapRequestTime("5890", "pubmatic", gomock.Any()) + mockMetricsEngine.EXPECT().RecordUnwrapRespTime("5890", "1", gomock.Any()) + }, + wantBids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: inlineXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }, }, - wantErr: false, }, - { - name: "Set Vast Unwrapper to true in request context for multiple bids with different type", + name: "VASTUnwrap_Enabled_Native_and_Banner_Bids", args: args{ module: OpenWrap{ cfg: config.Config{ Features: config.FeatureToggle{ - VASTUnwrapStatsPercent: 2, - VASTUnwrapPercent: 50, + VASTUnwrapPercent: 50, }, VastUnwrapCfg: unWrapCfg.VastUnWrapCfg{ MaxWrapperSupport: 5, @@ -312,60 +550,423 @@ func TestHandleRawBidderResponseHook(t *testing.T) { metricEngine: mockMetricsEngine, }, payload: hookstage.RawBidderResponsePayload{ - Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ - ID: "Bid-123", - ImpID: fmt.Sprintf("div-adunit-%d", 123), - Price: 2.1, - AdM: vastXMLAdM, - CrID: "Cr-234", - W: 100, - H: 50, + BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: "This is banner creative", + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "banner", + }, + { + Bid: &openrtb2.Bid{ + ID: "Bid-456", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: "This is native creative", + CrID: "Cr-789", + W: 100, + H: 50, + }, + BidType: "native", + }}, + }, + Bidder: "pubmatic", + }, + moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{models.RequestContext: models.RequestCtx{VastUnwrapEnabled: true}}}, + }, + wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, + wantBids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: "This is banner creative", + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "banner", + }, + { + Bid: &openrtb2.Bid{ + ID: "Bid-456", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: "This is native creative", + CrID: "Cr-789", + W: 100, + H: 50, + }, + BidType: "native", + }, + }, + }, + { + name: "bid_with_InvalidVAST_should_be_discarded_and_should_be_present_in_seatNonBid", + args: args{ + module: OpenWrap{ + cfg: config.Config{ + Features: config.FeatureToggle{ + VASTUnwrapPercent: 50, + }, + VastUnwrapCfg: unWrapCfg.VastUnWrapCfg{ + MaxWrapperSupport: 5, + StatConfig: unWrapCfg.StatConfig{Endpoint: "http://10.172.141.13:8080", PublishInterval: 1}, + APPConfig: unWrapCfg.AppConfig{UnwrapDefaultTimeout: 1500}, + }}, + metricEngine: mockMetricsEngine, + }, + payload: hookstage.RawBidderResponsePayload{ + BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: invalidVastXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", }, - BidType: "video", }, - { - Bid: &openrtb2.Bid{ - ID: "Bid-456", - ImpID: fmt.Sprintf("div-adunit-%d", 123), + }, + Bidder: "pubmatic", + }, + moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{models.RequestContext: models.RequestCtx{VastUnwrapEnabled: true}}}, + }, + mockHandler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Add("unwrap-status", models.UnwrapInvalidVASTStatus) + w.WriteHeader(http.StatusNoContent) + }), + wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, + setup: func() { + mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", models.UnwrapInvalidVASTStatus) + mockMetricsEngine.EXPECT().RecordUnwrapRequestTime("5890", "pubmatic", gomock.Any()) + }, + wantBids: []*adapters.TypedBid{}, + wantSeatNonBid: func() openrtb_ext.NonBidCollection { + seatNonBid := openrtb_ext.NonBidCollection{} + seatNonBid.AddBid(openrtb_ext.NonBid{ + ImpId: fmt.Sprintf("div-adunit-%d", 123), + StatusCode: int(nbr.LossBidLostInVastUnwrap), + Ext: openrtb_ext.ExtNonBid{ + Prebid: openrtb_ext.ExtNonBidPrebid{ + Bid: openrtb_ext.ExtNonBidPrebidBid{ Price: 2.1, - AdM: "This is banner creative", - CrID: "Cr-789", + ID: "Bid-123", W: 100, H: 50, + Type: openrtb_ext.BidTypeVideo, }, - BidType: "banner", + }, + }, + }, "pubmatic") + return seatNonBid + }(), + }, + { + name: "bid_with_EmptyVAST_should_be_discarded_and_should_be_present_in_seatNonBid", + args: args{ + module: OpenWrap{ + cfg: config.Config{ + Features: config.FeatureToggle{ + VASTUnwrapPercent: 50, + }, + VastUnwrapCfg: unWrapCfg.VastUnWrapCfg{ + MaxWrapperSupport: 5, + StatConfig: unWrapCfg.StatConfig{Endpoint: "http://10.172.141.13:8080", PublishInterval: 1}, + APPConfig: unWrapCfg.AppConfig{UnwrapDefaultTimeout: 1500}, }}, + metricEngine: mockMetricsEngine, + }, + payload: hookstage.RawBidderResponsePayload{ + BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: invalidVastXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }, + }, + }, Bidder: "pubmatic", }, moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{models.RequestContext: models.RequestCtx{VastUnwrapEnabled: true}}}, - - isAdmUpdated: true, }, mockHandler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Add("unwrap-status", "0") - w.Header().Add("unwrap-count", "0") - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(inlineXMLAdM)) + w.Header().Add("unwrap-status", models.UnwrapEmptyVASTStatus) + w.WriteHeader(http.StatusNoContent) }), wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, setup: func() { - mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", "0").AnyTimes() - mockMetricsEngine.EXPECT().RecordUnwrapWrapperCount("5890", "pubmatic", "0").AnyTimes() - mockMetricsEngine.EXPECT().RecordUnwrapRequestTime("5890", "pubmatic", gomock.Any()).AnyTimes() - mockMetricsEngine.EXPECT().RecordUnwrapRespTime("5890", "0", gomock.Any()).AnyTimes() + mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", models.UnwrapEmptyVASTStatus) + mockMetricsEngine.EXPECT().RecordUnwrapRequestTime("5890", "pubmatic", gomock.Any()) + }, + wantBids: []*adapters.TypedBid{}, + wantSeatNonBid: func() openrtb_ext.NonBidCollection { + seatNonBid := openrtb_ext.NonBidCollection{} + seatNonBid.AddBid(openrtb_ext.NonBid{ + ImpId: fmt.Sprintf("div-adunit-%d", 123), + StatusCode: int(nbr.LossBidLostInVastUnwrap), + Ext: openrtb_ext.ExtNonBid{ + Prebid: openrtb_ext.ExtNonBidPrebid{ + Bid: openrtb_ext.ExtNonBidPrebidBid{ + Price: 2.1, + ID: "Bid-123", + W: 100, + H: 50, + Type: openrtb_ext.BidTypeVideo, + }, + }, + }, + }, "pubmatic") + return seatNonBid + }(), + }, + { + name: "VASTUnwrap_Disabled_Video_Bids_Valid_XML", + args: args{ + module: OpenWrap{ + cfg: config.Config{ + Features: config.FeatureToggle{}, + VastUnwrapCfg: unWrapCfg.VastUnWrapCfg{ + MaxWrapperSupport: 5, + StatConfig: unWrapCfg.StatConfig{Endpoint: "http://10.172.141.13:8080", PublishInterval: 1}, + APPConfig: unWrapCfg.AppConfig{UnwrapDefaultTimeout: 1500}, + }}, + metricEngine: mockMetricsEngine, + }, + payload: hookstage.RawBidderResponsePayload{ + BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: vastXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }}}, + Bidder: "pubmatic", + }, + moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{models.RequestContext: models.RequestCtx{VastUnwrapEnabled: false}}}, + }, + wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, + wantBids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: vastXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }, + }, + }, + { + name: "VASTUnwrap_Disabled_Video_and_Banner_Bids", + args: args{ + module: OpenWrap{ + cfg: config.Config{ + Features: config.FeatureToggle{}, + VastUnwrapCfg: unWrapCfg.VastUnWrapCfg{ + MaxWrapperSupport: 5, + StatConfig: unWrapCfg.StatConfig{Endpoint: "http://10.172.141.13:8080", PublishInterval: 1}, + APPConfig: unWrapCfg.AppConfig{UnwrapDefaultTimeout: 1500}, + }}, + metricEngine: mockMetricsEngine, + }, + payload: hookstage.RawBidderResponsePayload{ + BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: vastXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }, + { + Bid: &openrtb2.Bid{ + ID: "Bid-456", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: "This is banner creative", + CrID: "Cr-789", + W: 100, + H: 50, + }, + BidType: "banner", + }}, + }, + Bidder: "pubmatic", + }, + moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{models.RequestContext: models.RequestCtx{VastUnwrapEnabled: false}}}, + }, + wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, + wantBids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: vastXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }, + { + Bid: &openrtb2.Bid{ + ID: "Bid-456", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: "This is banner creative", + CrID: "Cr-789", + W: 100, + H: 50, + }, + BidType: "banner", + }, }, - wantErr: false, }, { - name: "Set Vast Unwrapper to true in request context with type video and source owsdk", + name: "VASTUnwrap_Disabled_Banner_Bids", + args: args{ + module: OpenWrap{ + cfg: config.Config{ + Features: config.FeatureToggle{}, + VastUnwrapCfg: unWrapCfg.VastUnWrapCfg{ + MaxWrapperSupport: 5, + StatConfig: unWrapCfg.StatConfig{Endpoint: "http://10.172.141.13:8080", PublishInterval: 1}, + APPConfig: unWrapCfg.AppConfig{UnwrapDefaultTimeout: 1500}, + }}, + metricEngine: mockMetricsEngine, + }, + payload: hookstage.RawBidderResponsePayload{ + BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-456", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: "This is banner creative", + CrID: "Cr-789", + W: 100, + H: 50, + }, + BidType: "banner", + }}, + }, + Bidder: "pubmatic", + }, + moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{models.RequestContext: models.RequestCtx{VastUnwrapEnabled: false}}}, + }, + wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, + wantBids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-456", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: "This is banner creative", + CrID: "Cr-789", + W: 100, + H: 50, + }, + BidType: "banner", + }, + }, + }, + { + name: "VASTUnwrap_Enabled_Banner_Bids", + args: args{ + module: OpenWrap{ + cfg: config.Config{ + Features: config.FeatureToggle{}, + VastUnwrapCfg: unWrapCfg.VastUnWrapCfg{ + MaxWrapperSupport: 5, + StatConfig: unWrapCfg.StatConfig{Endpoint: "http://10.172.141.13:8080", PublishInterval: 1}, + APPConfig: unWrapCfg.AppConfig{UnwrapDefaultTimeout: 1500}, + }}, + metricEngine: mockMetricsEngine, + }, + payload: hookstage.RawBidderResponsePayload{ + BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-456", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: "This is banner creative", + CrID: "Cr-789", + W: 100, + H: 50, + }, + BidType: "banner", + }}, + }, + Bidder: "pubmatic", + }, + moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{models.RequestContext: models.RequestCtx{VastUnwrapEnabled: true}}}, + }, + wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, + wantBids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-456", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: "This is banner creative", + CrID: "Cr-789", + W: 100, + H: 50, + }, + BidType: "banner", + }, + }, + }, + { + name: "VASTUnwrap_Enabled_Invalid_Video_and_Banner_Bids", args: args{ module: OpenWrap{ cfg: config.Config{ Features: config.FeatureToggle{ - VASTUnwrapStatsPercent: 2, - VASTUnwrapPercent: 50, + VASTUnwrapPercent: 50, }, VastUnwrapCfg: unWrapCfg.VastUnWrapCfg{ MaxWrapperSupport: 5, @@ -375,40 +976,73 @@ func TestHandleRawBidderResponseHook(t *testing.T) { metricEngine: mockMetricsEngine, }, payload: hookstage.RawBidderResponsePayload{ - Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ - ID: "Bid-123", - ImpID: fmt.Sprintf("div-adunit-%d", 123), - Price: 2.1, - AdM: vastXMLAdM, - CrID: "Cr-234", - W: 100, - H: 50, + BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: invalidVastXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }, + { + Bid: &openrtb2.Bid{ + ID: "Bid-456", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: "This is banner creative", + CrID: "Cr-789", + W: 100, + H: 50, + }, + BidType: "banner", }, - BidType: "video", }, }, Bidder: "pubmatic", }, moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{models.RequestContext: models.RequestCtx{VastUnwrapEnabled: true}}}, - - isAdmUpdated: true, }, mockHandler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Add("unwrap-status", "0") - w.Header().Add("unwrap-count", "1") - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(inlineXMLAdM)) + w.Header().Add("unwrap-status", "1") + w.WriteHeader(http.StatusNoContent) }), wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, setup: func() { - mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", "0").AnyTimes() - mockMetricsEngine.EXPECT().RecordUnwrapWrapperCount("5890", "pubmatic", "1").AnyTimes() - mockMetricsEngine.EXPECT().RecordUnwrapRequestTime("5890", "pubmatic", gomock.Any()).AnyTimes() - mockMetricsEngine.EXPECT().RecordUnwrapRespTime("5890", "1", gomock.Any()).AnyTimes() + mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", "1") + mockMetricsEngine.EXPECT().RecordUnwrapRequestTime("5890", "pubmatic", gomock.Any()) + }, + wantBids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "Bid-456", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: "This is banner creative", + CrID: "Cr-789", + W: 100, + H: 50, + }, + BidType: "banner", + }, + { + Bid: &openrtb2.Bid{ + ID: "Bid-123", + ImpID: fmt.Sprintf("div-adunit-%d", 123), + Price: 2.1, + AdM: invalidVastXMLAdM, + CrID: "Cr-234", + W: 100, + H: 50, + }, + BidType: "video", + }, }, - wantErr: false, }, } for _, tt := range tests { @@ -417,19 +1051,65 @@ func TestHandleRawBidderResponseHook(t *testing.T) { tt.setup() } - GetRandomNumberIn1To100 = func() int { - return tt.args.randomNumber - } - m := tt.args.module m.unwrap = unwrap.NewUnwrap("http://localhost:8001/unwrap", 200, tt.mockHandler, m.metricEngine) - _, err := m.handleRawBidderResponseHook(tt.args.moduleInvocationCtx, tt.args.payload) - if !assert.NoError(t, err, tt.wantErr) { - return - } + hookResult, _ := m.handleRawBidderResponseHook(tt.args.moduleInvocationCtx, tt.args.payload) if tt.args.moduleInvocationCtx.ModuleContext != nil && tt.args.isAdmUpdated { - assert.Equal(t, inlineXMLAdM, tt.args.payload.Bids[0].Bid.AdM, "AdM is not updated correctly after executing RawBidderResponse hook.") + assert.Equal(t, inlineXMLAdM, tt.args.payload.BidderResponse.Bids[0].Bid.AdM, "AdM is not updated correctly after executing RawBidderResponse hook.") + } + for _, mut := range hookResult.ChangeSet.Mutations() { + newPayload, err := mut.Apply(tt.args.payload) + assert.NoError(t, err) + tt.args.payload = newPayload + } + if tt.wantBids != nil { + assert.ElementsMatch(t, tt.wantBids, tt.args.payload.BidderResponse.Bids, "Mismatched response bids") } + + assert.Equal(t, tt.wantSeatNonBid, hookResult.SeatNonBid, "mismatched seatNonBids") + }) + } +} + +func TestIsEligibleForUnwrap(t *testing.T) { + type args struct { + bid *adapters.TypedBid + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "Bid is nil", + args: args{bid: nil}, + want: false, + }, + { + name: "Bid.Bid is nil", + args: args{bid: &adapters.TypedBid{Bid: nil}}, + want: false, + }, + { + name: "AdM is empty", + args: args{bid: &adapters.TypedBid{Bid: &openrtb2.Bid{AdM: ""}}}, + want: false, + }, + { + name: "BidType is not video", + args: args{bid: &adapters.TypedBid{Bid: &openrtb2.Bid{AdM: "some_adm"}, BidType: openrtb_ext.BidTypeBanner}}, + want: false, + }, + { + name: "Bid is eligible for unwrap", + args: args{bid: &adapters.TypedBid{Bid: &openrtb2.Bid{AdM: "some_adm"}, BidType: openrtb_ext.BidTypeVideo}}, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := isEligibleForUnwrap(tt.args.bid) + assert.Equal(t, tt.want, got) }) } } diff --git a/modules/pubmatic/openwrap/models/constants.go b/modules/pubmatic/openwrap/models/constants.go index d55973c7bf0..7b49843fff7 100755 --- a/modules/pubmatic/openwrap/models/constants.go +++ b/modules/pubmatic/openwrap/models/constants.go @@ -374,21 +374,23 @@ const ( Enabled = "1" // VAST Unwrap - RequestContext = "rctx" - UnwrapCount = "unwrap-count" - UnwrapStatus = "unwrap-status" - Timeout = "Timeout" - UnwrapSucessStatus = "0" - UnwrapTimeout = "unwrap-timeout" - MediaTypeVideo = "video" - ProfileId = "profileID" - VersionId = "versionID" - DisplayId = "DisplayID" - XUserIP = "X-Forwarded-For" - XUserAgent = "X-Device-User-Agent" - CreativeID = "unwrap-ucrid" - PubID = "pub_id" - ImpressionID = "imr_id" + RequestContext = "rctx" + UnwrapCount = "unwrap-count" + UnwrapStatus = "unwrap-status" + Timeout = "Timeout" + UnwrapSucessStatus = "0" + UnwrapEmptyVASTStatus = "4" + UnwrapInvalidVASTStatus = "6" + UnwrapTimeout = "unwrap-timeout" + MediaTypeVideo = "video" + ProfileId = "profileID" + VersionId = "versionID" + DisplayId = "DisplayID" + XUserIP = "X-Forwarded-For" + XUserAgent = "X-Device-User-Agent" + CreativeID = "unwrap-ucrid" + PubID = "pub_id" + ImpressionID = "imr_id" //Constants for new SDK reporting ProfileTypeKey = "type" diff --git a/modules/pubmatic/openwrap/models/nbr/codes.go b/modules/pubmatic/openwrap/models/nbr/codes.go index 7eafb8a5d09..fa54481d8d1 100644 --- a/modules/pubmatic/openwrap/models/nbr/codes.go +++ b/modules/pubmatic/openwrap/models/nbr/codes.go @@ -9,6 +9,7 @@ const ( RequestBlockedSlotNotMapped openrtb3.NoBidReason = 503 RequestBlockedPartnerThrottle openrtb3.NoBidReason = 504 RequestBlockedPartnerFiltered openrtb3.NoBidReason = 505 + LossBidLostInVastUnwrap openrtb3.NoBidReason = 506 ) // Openwrap module specific codes diff --git a/modules/pubmatic/openwrap/unwrap/unwrap.go b/modules/pubmatic/openwrap/unwrap/unwrap.go index ef002929116..24c5b24ba38 100644 --- a/modules/pubmatic/openwrap/unwrap/unwrap.go +++ b/modules/pubmatic/openwrap/unwrap/unwrap.go @@ -40,21 +40,17 @@ func NewUnwrap(Endpoint string, DefaultTime int, handler http.HandlerFunc, Metri } -func (uw Unwrap) Unwrap(accountID, bidder string, bid *adapters.TypedBid, userAgent, ip string, isStatsEnabled bool) { +func (uw Unwrap) Unwrap(bid *adapters.TypedBid, accountID, bidder, userAgent, ip string) (unwrapStatus string) { startTime := time.Now() var wrapperCnt int64 - var respStatus string - if bid == nil || bid.Bid == nil || bid.Bid.AdM == "" { - return - } defer func() { if r := recover(); r != nil { glog.Errorf("AdM:[%s] Error:[%v] stacktrace:[%s]", bid.Bid.AdM, r, string(debug.Stack())) } respTime := time.Since(startTime) uw.metricEngine.RecordUnwrapRequestTime(accountID, bidder, respTime) - uw.metricEngine.RecordUnwrapRequestStatus(accountID, bidder, respStatus) - if respStatus == "0" { + uw.metricEngine.RecordUnwrapRequestStatus(accountID, bidder, unwrapStatus) + if unwrapStatus == "0" { uw.metricEngine.RecordUnwrapWrapperCount(accountID, bidder, strconv.Itoa(int(wrapperCnt))) uw.metricEngine.RecordUnwrapRespTime(accountID, strconv.Itoa(int(wrapperCnt)), respTime) } @@ -76,13 +72,14 @@ func (uw Unwrap) Unwrap(accountID, bidder string, bid *adapters.TypedBid, userAg httpReq.Header = headers httpResp := NewCustomRecorder() uw.unwrapRequest(httpResp, httpReq) - respStatus = httpResp.Header().Get(models.UnwrapStatus) + unwrapStatus = httpResp.Header().Get(models.UnwrapStatus) wrapperCnt, _ = strconv.ParseInt(httpResp.Header().Get(models.UnwrapCount), 10, 0) - if !isStatsEnabled && httpResp.Code == http.StatusOK && respStatus == models.UnwrapSucessStatus { + if httpResp.Code == http.StatusOK && unwrapStatus == models.UnwrapSucessStatus { respBody := httpResp.Body.Bytes() bid.Bid.AdM = string(respBody) } - glog.V(models.LogLevelDebug).Infof("[VAST_UNWRAPPER] pubid:[%v] bidder:[%v] impid:[%v] bidid:[%v] status_code:[%v] wrapper_cnt:[%v] httpRespCode= [%v] statsEnabled:[%v]", - accountID, bidder, bid.Bid.ImpID, bid.Bid.ID, respStatus, wrapperCnt, httpResp.Code, isStatsEnabled) + glog.V(models.LogLevelDebug).Infof("[VAST_UNWRAPPER] pubid:[%v] bidder:[%v] impid:[%v] bidid:[%v] status_code:[%v] wrapper_cnt:[%v] httpRespCode= [%v]", + accountID, bidder, bid.Bid.ImpID, bid.Bid.ID, unwrapStatus, wrapperCnt, httpResp.Code) + return unwrapStatus } diff --git a/modules/pubmatic/openwrap/unwrap/unwrap_test.go b/modules/pubmatic/openwrap/unwrap/unwrap_test.go index a7f3e7be050..763ba071c8c 100644 --- a/modules/pubmatic/openwrap/unwrap/unwrap_test.go +++ b/modules/pubmatic/openwrap/unwrap/unwrap_test.go @@ -8,7 +8,6 @@ import ( "github.com/golang/mock/gomock" "github.com/prebid/openrtb/v20/openrtb2" "github.com/prebid/prebid-server/v2/adapters" - metrics "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics" mock_metrics "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics/mock" "github.com/stretchr/testify/assert" ) @@ -18,35 +17,32 @@ var invalidVastXMLAdM = "PubMaticAcudeo CompatibleVAST 2.0 Instream Test 1VAST 2.0 Instream Test 1https://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&er=[ERRORCODE]https://track.dsptracker.com?p=1234&er=[ERRORCODE]https://aktrack.pubmatic.com/AdServer/AdDisplayTrackerServlet?operId=1&pubId=64195&siteId=47105&adId=1405154&adType=13&adServerId=243&kefact=1.000000&kaxefact=1.000000&kadNetFrequecy=0&kadwidth=0&kadheight=0&kadsizeid=97&kltstamp=1536933242&indirectAdId=0&adServerOptimizerId=2&ranreq=0.05969169352174375&kpbmtpfact=11.000000&dcId=1&tldId=0&passback=0&svr=ktk57&ekefact=er2bW2sDAwCra06ACbsIQySn5nqBtYsTl8fy5lupAexh37D_&ekaxefact=er2bW4EDAwB_LQpJJ23Fq0DcNC-NSAFXdpSQC8XBk_S33_Fa&ekpbmtpfact=er2bW5MDAwDJHdBnLBt5IrRuh7x0oqp_tjIALv_VvSQDAl6R&crID=m:1_x:3_y:3_p:11_va:3&lpu=ae.com&ucrid=678722001014421372&campaignId=16774&creativeId=0&pctr=0.000000&wDSPByrId=511&wDspId=27&wbId=0&wrId=0&wAdvID=3170&isRTB=1&rtbId=EBCA079F-8D7C-45B8-B733-92951F670AA1&imprId=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&oid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&pageURL=http%253A%252F%252Fowsdk-stagingams.pubmatic.com%253A8443%252Fvast-validator%252F%2523&sec=1&pmc=1https://DspImpressionTracker.com/https://mytracking.com/linear/closehttps://mytracking.com/linear/skiphttps://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&e=1https://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&e=2https://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&e=3https://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&e=4https://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&e=5https://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&e=600:00:04https://www.automationtester.inhttps://aktrack.pubmatic.com/track?operId=7&p=64195&s=47105&a=1405154&wa=243&ts=1536933242&wc=16774&crId=m:1_x:3_y:3_p:11_va:3&ucrid=678722001014421372&impid=24D9FEDA-C97D-4DF7-B747-BD3CFF5AC7B5&advertiser_id=3170&ecpm=1.000000&e=99https://stagingams.pubmatic.com:8443/openwrap/media/pubmatic.mp4https://stagingams.pubmatic.com:8443/openwrap/media/pubmatic.mp4https://stagingams.pubmatic.com:8443/openwrap/media/mp4-sample-3.mp4" func TestUnwrap_Unwrap(t *testing.T) { - ctrl := gomock.NewController(t) defer ctrl.Finish() mockMetricsEngine := mock_metrics.NewMockMetricsEngine(ctrl) type fields struct { - endpoint string - defaultTime int - metricEngine metrics.MetricsEngine - unwrapRequest http.HandlerFunc + endpoint string } type args struct { - accountID string - bidder string - bid *adapters.TypedBid - userAgent string - ip string - isStatsEnabled bool + accountID string + bidder string + bid *adapters.TypedBid + userAgent string + ip string } tests := []struct { - name string - fields fields - args args - setup func() - mockHandler http.HandlerFunc - expectedAdm string + name string + fields fields + args args + setup func() + mockHandler http.HandlerFunc + expectedAdm string + expectedUnwrapStatus string }{ { - name: "Stats enabled only", + name: "Unwrap enabled with valid adm", + fields: fields{endpoint: "http://localhost:8001/unwrap"}, args: args{ accountID: "5890", bidder: "pubmatic", @@ -55,9 +51,8 @@ func TestUnwrap_Unwrap(t *testing.T) { AdM: vastXMLAdM, }, }, - userAgent: "UA", - ip: "10.12.13.14", - isStatsEnabled: true, + userAgent: "UA", + ip: "10.12.13.14", }, setup: func() { mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", "0") @@ -69,40 +64,39 @@ func TestUnwrap_Unwrap(t *testing.T) { w.Header().Add("unwrap-status", "0") w.Header().Add("unwrap-count", "1") w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(vastXMLAdM)) + _, _ = w.Write([]byte(inlineXMLAdM)) }), - expectedAdm: vastXMLAdM, + expectedAdm: inlineXMLAdM, + expectedUnwrapStatus: "0", }, { - name: "Unwrap enabled with valid adm", + name: "Unwrap enabled with invalid adm", + fields: fields{endpoint: "http://localhost:8001/unwrap"}, args: args{ accountID: "5890", bidder: "pubmatic", bid: &adapters.TypedBid{ Bid: &openrtb2.Bid{ - AdM: vastXMLAdM, + AdM: invalidVastXMLAdM, }, }, - userAgent: "UA", - ip: "10.12.13.14", - isStatsEnabled: false, + userAgent: "UA", + ip: "10.12.13.14", }, setup: func() { - mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", "0") - mockMetricsEngine.EXPECT().RecordUnwrapWrapperCount("5890", "pubmatic", "1") + mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", "1") mockMetricsEngine.EXPECT().RecordUnwrapRequestTime("5890", "pubmatic", gomock.Any()) - mockMetricsEngine.EXPECT().RecordUnwrapRespTime("5890", "1", gomock.Any()) }, mockHandler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Add("unwrap-status", "0") - w.Header().Add("unwrap-count", "1") - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(inlineXMLAdM)) + w.Header().Add("unwrap-status", "1") + w.WriteHeader(http.StatusNoContent) }), - expectedAdm: inlineXMLAdM, + expectedAdm: invalidVastXMLAdM, + expectedUnwrapStatus: "1", }, { - name: "Unwrap enabled with invalid adm", + name: "Error while forming the HTTPRequest for unwrap process", + fields: fields{endpoint: ":"}, args: args{ accountID: "5890", bidder: "pubmatic", @@ -111,33 +105,29 @@ func TestUnwrap_Unwrap(t *testing.T) { AdM: invalidVastXMLAdM, }, }, - userAgent: "UA", - ip: "10.12.13.14", - isStatsEnabled: false, + userAgent: "UA", + ip: "10.12.13.14", }, setup: func() { - mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", "1") + mockMetricsEngine.EXPECT().RecordUnwrapRequestStatus("5890", "pubmatic", "") mockMetricsEngine.EXPECT().RecordUnwrapRequestTime("5890", "pubmatic", gomock.Any()) }, - mockHandler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Add("unwrap-status", "1") - w.WriteHeader(http.StatusNoContent) - }), - expectedAdm: invalidVastXMLAdM, + mockHandler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {}), + expectedAdm: invalidVastXMLAdM, + expectedUnwrapStatus: "", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if tt.setup != nil { tt.setup() } - uw := NewUnwrap("http://localhost:8001/unwrap", 200, tt.mockHandler, mockMetricsEngine) - uw.Unwrap(tt.args.accountID, tt.args.bidder, tt.args.bid, tt.args.userAgent, tt.args.ip, tt.args.isStatsEnabled) - if !tt.args.isStatsEnabled && strings.Compare(tt.args.bid.Bid.AdM, tt.expectedAdm) != 0 { + uw := NewUnwrap(tt.fields.endpoint, 200, tt.mockHandler, mockMetricsEngine) + unwrapStatus := uw.Unwrap(tt.args.bid, tt.args.accountID, tt.args.bidder, tt.args.userAgent, tt.args.ip) + if strings.Compare(tt.args.bid.Bid.AdM, tt.expectedAdm) != 0 { assert.Equal(t, inlineXMLAdM, tt.args.bid.Bid.AdM, "AdM is not updated correctly after unwrap ") - } + assert.Equal(t, tt.expectedUnwrapStatus, unwrapStatus, "mismatched unwrap status") }) } } From 47cef0e072f255b7682542e6539f25520ec7aba5 Mon Sep 17 00:00:00 2001 From: Pubmatic-Supriya-Patil <131644110+Pubmatic-Supriya-Patil@users.noreply.github.com> Date: Wed, 9 Oct 2024 14:55:42 +0530 Subject: [PATCH 3/9] UOE-11098: Pass user.ext.sessionduration and user.ext.impdepth ORTB request to OW partners (#922) --- modules/pubmatic/openwrap/applovinmax.go | 2 +- modules/pubmatic/openwrap/applovinmax_test.go | 32 +++ .../pubmatic/openwrap/beforevalidationhook.go | 2 +- .../openwrap/endpoints/legacy/ctv/constant.go | 12 +- .../legacy/ctv/parser_implementation.go | 66 +++++ .../legacy/ctv/parser_implementation_test.go | 226 +++++++++++++++++- .../endpoints/legacy/ctv/parser_map.go | 11 + .../openwrap/endpoints/legacy/ctv/util.go | 58 ----- .../endpoints/legacy/ctv/util_test.go | 127 ---------- modules/pubmatic/openwrap/util.go | 62 +++++ modules/pubmatic/openwrap/util_test.go | 143 +++++++++++ openrtb_ext/user.go | 4 +- 12 files changed, 551 insertions(+), 194 deletions(-) delete mode 100644 modules/pubmatic/openwrap/endpoints/legacy/ctv/util_test.go diff --git a/modules/pubmatic/openwrap/applovinmax.go b/modules/pubmatic/openwrap/applovinmax.go index 06fa6a8de00..a95ee5edbba 100644 --- a/modules/pubmatic/openwrap/applovinmax.go +++ b/modules/pubmatic/openwrap/applovinmax.go @@ -186,7 +186,7 @@ func updateUser(signalUser *openrtb2.User, maxRequest *openrtb2.BidRequest) { } maxRequest.User.Data = signalUser.Data - maxRequest.User.Ext = setIfKeysExists(signalUser.Ext, maxRequest.User.Ext, "consent", "eids") + maxRequest.User.Ext = setIfKeysExists(signalUser.Ext, maxRequest.User.Ext, "consent", "eids", "sessionduration", "impdepth") } func setIfKeysExists(source []byte, target []byte, keys ...string) []byte { diff --git a/modules/pubmatic/openwrap/applovinmax_test.go b/modules/pubmatic/openwrap/applovinmax_test.go index 6e1e1e78463..fec08e77116 100644 --- a/modules/pubmatic/openwrap/applovinmax_test.go +++ b/modules/pubmatic/openwrap/applovinmax_test.go @@ -439,6 +439,38 @@ func TestUpdateUser(t *testing.T) { }, want: &openrtb2.User{ID: "maxID", Yob: 1999, Gender: "M", Keywords: "k1=v2;k2=v2", Ext: json.RawMessage(`{"consent":"consent_string","eids":[{"source":"amxid","uids":[{"atype":1,"id":"88de601e-3d98-48e7-81d7-00000000"}]},{"source":"adserver.org","uids":[{"id":"1234567","ext":{"rtiPartner":"TDID"}}]}]}`)}, }, + { + name: "signalUserExt has sessionduration and impdepth", + args: args{ + signalUser: &openrtb2.User{ID: "sdkID", Yob: 1999, Gender: "M", Keywords: "k1=v2;k2=v2", Ext: json.RawMessage(`{"sessionduration":40,"impdepth":10}`)}, + maxRequest: &openrtb2.BidRequest{User: &openrtb2.User{ID: "maxID", Yob: 2000, Gender: "F", Keywords: "k52=v43"}}, + }, + want: &openrtb2.User{ID: "maxID", Yob: 1999, Gender: "M", Keywords: "k1=v2;k2=v2", Ext: json.RawMessage(`{"sessionduration":40,"impdepth":10}`)}, + }, + { + name: "signalUserExt has invalid sessionduration and impdepth", + args: args{ + signalUser: &openrtb2.User{ID: "sdkID", Yob: 1999, Gender: "M", Keywords: "k1=v2;k2=v2", Ext: json.RawMessage(`{"sessionduration":"sessionduration","impdepth":"impdepth"}`)}, + maxRequest: &openrtb2.BidRequest{User: &openrtb2.User{ID: "maxID", Yob: 2000, Gender: "F", Keywords: "k52=v43"}}, + }, + want: &openrtb2.User{ID: "maxID", Yob: 1999, Gender: "M", Keywords: "k1=v2;k2=v2", Ext: json.RawMessage(`{"sessionduration":"sessionduration","impdepth":"impdepth"}`)}, + }, + { + name: "signalUserExt has sessionduration", + args: args{ + signalUser: &openrtb2.User{ID: "sdkID", Yob: 1999, Gender: "M", Keywords: "k1=v2;k2=v2", Ext: json.RawMessage(`{"sessionduration":40}`)}, + maxRequest: &openrtb2.BidRequest{User: &openrtb2.User{ID: "maxID", Yob: 2000, Gender: "F", Keywords: "k52=v43"}}, + }, + want: &openrtb2.User{ID: "maxID", Yob: 1999, Gender: "M", Keywords: "k1=v2;k2=v2", Ext: json.RawMessage(`{"sessionduration":40}`)}, + }, + { + name: "signalUserExt has impdepth", + args: args{ + signalUser: &openrtb2.User{ID: "sdkID", Yob: 1999, Gender: "M", Keywords: "k1=v2;k2=v2", Ext: json.RawMessage(`{"impdepth":10}`)}, + maxRequest: &openrtb2.BidRequest{User: &openrtb2.User{ID: "maxID", Yob: 2000, Gender: "F", Keywords: "k52=v43"}}, + }, + want: &openrtb2.User{ID: "maxID", Yob: 1999, Gender: "M", Keywords: "k1=v2;k2=v2", Ext: json.RawMessage(`{"impdepth":10}`)}, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/modules/pubmatic/openwrap/beforevalidationhook.go b/modules/pubmatic/openwrap/beforevalidationhook.go index 243e819d5ee..9a4a70be69e 100644 --- a/modules/pubmatic/openwrap/beforevalidationhook.go +++ b/modules/pubmatic/openwrap/beforevalidationhook.go @@ -763,7 +763,7 @@ func (m *OpenWrap) applyProfileChanges(rctx models.RequestCtx, bidRequest *openr if bidRequest.User.CustomData == "" && rctx.KADUSERCookie != nil { bidRequest.User.CustomData = rctx.KADUSERCookie.Value } - ctv.UpdateUserEidsWithValidValues(bidRequest.User) + UpdateUserExtWithValidValues(bidRequest.User) for i := 0; i < len(bidRequest.WLang); i++ { bidRequest.WLang[i] = getValidLanguage(bidRequest.WLang[i]) diff --git a/modules/pubmatic/openwrap/endpoints/legacy/ctv/constant.go b/modules/pubmatic/openwrap/endpoints/legacy/ctv/constant.go index d3c57c63285..2233911e0c4 100644 --- a/modules/pubmatic/openwrap/endpoints/legacy/ctv/constant.go +++ b/modules/pubmatic/openwrap/endpoints/legacy/ctv/constant.go @@ -287,10 +287,12 @@ const ( ORTBUserGeoUtcOffset = "user.geo.utcoffset" //ORTBUserGeoUtcOffset get api parameter user.geo.utcoffset // User.Ext.Consent level parameters - ORTBUserExtConsent = "user.ext.consent" //ORTBUserExtConsent get api parameter user.ext.consent - ORTBUserExtEIDS = "user.ext.eids" //ORTBUserExtEIDS get api parameter user.ext.eids - ORTBUserData = "user.data" //ORTBUserData get api parameter user.data - ORTBExtEIDS = "eids" //ORTBExtEIDS parameter + ORTBUserExtConsent = "user.ext.consent" //ORTBUserExtConsent get api parameter user.ext.consent + ORTBUserExtEIDS = "user.ext.eids" //ORTBUserExtEIDS get api parameter user.ext.eids + ORTBUserExtSessionDuration = "user.ext.sessionduration" //ORTBUserExtSessionDuration get api parameter user.ext.sessionduration + ORTBUserExtImpDepth = "user.ext.impdepth" //ORTBUserExtImpDepth get api parameter user.ext.impdepth + ORTBUserData = "user.data" //ORTBUserData get api parameter user.data + ORTBExtEIDS = "eids" //ORTBExtEIDS parameter // Regs.Ext.GDPR level parameters ORTBRegsExtGdpr = "regs.ext.gdpr" //ORTBRegsExtGdpr get api parameter regs.ext.gdpr @@ -350,6 +352,8 @@ const ( ORTBUserGeoExt = "user.geo.ext" //ORTBUserGeoExt get api parameter user.geo.ext ORTBUserExtUIDS = "uids" //ORTBUserExtUIDs get api parameter user.ext.eids.uids ORTBUserExtID = "id" //ORTBUserExtID get api parameter user.ext.eids.uids.id + ORTBExtSessionDuration = "sessionduration" //ORTBUserExtSessionDuration get api parameter user.ext.sessionduration + ORTBExtImpDepth = "impdepth" //ORTBUserExtImpDepth get api parameter user.ext.impdepth // ORTB Extension Standard Keys */ //// get api parameter xtension Standard Klevel parameters ORTBExtWrapper = "wrapper" //ORTBExtWrapper get api parameter wrapper diff --git a/modules/pubmatic/openwrap/endpoints/legacy/ctv/parser_implementation.go b/modules/pubmatic/openwrap/endpoints/legacy/ctv/parser_implementation.go index 97184c18f6f..a53295155cb 100644 --- a/modules/pubmatic/openwrap/endpoints/legacy/ctv/parser_implementation.go +++ b/modules/pubmatic/openwrap/endpoints/legacy/ctv/parser_implementation.go @@ -5,8 +5,10 @@ import ( "fmt" "net/http" "net/url" + "strconv" "strings" + "git.pubmatic.com/PubMatic/go-common/logger" "github.com/golang/glog" "github.com/prebid/openrtb/v20/adcom1" "github.com/prebid/openrtb/v20/openrtb2" @@ -4506,6 +4508,70 @@ func (o *OpenRTB) ORTBUserExtEIDS() (err error) { return } +// ORTBUserExtSessionDuration will read and set ortb User.Ext.sessionduration parameter +func (o *OpenRTB) ORTBUserExtSessionDuration() (err error) { + valStr, ok := o.values.GetString(ORTBUserExtSessionDuration) + if !ok || valStr == "" { + return + } + if o.ortb.User == nil { + o.ortb.User = &openrtb2.User{} + } + userExt := map[string]interface{}{} + if o.ortb.User.Ext != nil { + if err = json.Unmarshal(o.ortb.User.Ext, &userExt); err != nil { + return + } + } + + val, err := strconv.ParseUint(valStr, 10, 64) + if err != nil { + logger.Warn("Invalid session duration value '%v': %v", valStr, err) + return nil + } + userExt[ORTBExtSessionDuration] = int64(val) + + data, err := json.Marshal(userExt) + if err != nil { + return + } + + o.ortb.User.Ext = data + return +} + +// ORTBUserExtImpDepth will read and set ortb User.Ext.impdepth parameter +func (o *OpenRTB) ORTBUserExtImpDepth() (err error) { + valStr, ok := o.values.GetString(ORTBUserExtImpDepth) + if !ok || valStr == "" { + return + } + if o.ortb.User == nil { + o.ortb.User = &openrtb2.User{} + } + userExt := map[string]interface{}{} + if o.ortb.User.Ext != nil { + if err = json.Unmarshal(o.ortb.User.Ext, &userExt); err != nil { + return + } + } + + val, err := strconv.ParseUint(valStr, 10, 64) + if err != nil { + logger.Warn("Invalid imp depth value '%v': %v", valStr, err) + return nil + } + userExt[ORTBExtImpDepth] = int64(val) + + data, err := json.Marshal(userExt) + if err != nil { + return + } + + o.ortb.User.Ext = data + return +} + // ORTBUserData will read and set ortb user.data parameter func (o *OpenRTB) ORTBUserData() (err error) { dataValue, ok := o.values.GetString(ORTBUserData) diff --git a/modules/pubmatic/openwrap/endpoints/legacy/ctv/parser_implementation_test.go b/modules/pubmatic/openwrap/endpoints/legacy/ctv/parser_implementation_test.go index 60090727e6e..9791267fa79 100644 --- a/modules/pubmatic/openwrap/endpoints/legacy/ctv/parser_implementation_test.go +++ b/modules/pubmatic/openwrap/endpoints/legacy/ctv/parser_implementation_test.go @@ -316,8 +316,10 @@ func getTestValues() url.Values { ORTBRequestExtPrebidTransparencyContent: {`{"pubmatic": {"include": 1}}`}, // - ORTBUserData: {`[{"name":"publisher.com","ext":{"segtax":4},"segment":[{"id":"1"}]}]`}, - ORTBUserExtEIDS: {`[{"source":"bvod.connect","uids":[{"id":"OztamSession-123456","atype":501},{"id":"7D92078A-8246-4BA4-AE5B-76104861E7DC","atype":2,"ext":{"seq":1,"demgid":"1234"}},{"id":"8D92078A-8246-4BA4-AE5B-76104861E7DC","atype":2,"ext":{"seq":2,"demgid":"2345"}}]}]`}, + ORTBUserData: {`[{"name":"publisher.com","ext":{"segtax":4},"segment":[{"id":"1"}]}]`}, + ORTBUserExtEIDS: {`[{"source":"bvod.connect","uids":[{"id":"OztamSession-123456","atype":501},{"id":"7D92078A-8246-4BA4-AE5B-76104861E7DC","atype":2,"ext":{"seq":1,"demgid":"1234"}},{"id":"8D92078A-8246-4BA4-AE5B-76104861E7DC","atype":2,"ext":{"seq":2,"demgid":"2345"}}]}]`}, + ORTBUserExtSessionDuration: {"40"}, + ORTBUserExtImpDepth: {"10"}, ORTBDeviceExtIfaType: {"ORTBDeviceExtIfaType"}, ORTBDeviceExtSessionID: {"ORTBDeviceExtSessionID"}, @@ -734,6 +736,8 @@ func TestParseORTBRequest(t *testing.T) { ], "ext": { "consent": "BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA", + "sessionduration": 40, + "impdepth": 10, "eids": [ { "source": "bvod.connect", @@ -2444,3 +2448,221 @@ func TestORTBDeviceExtIfaType(t *testing.T) { }) } } + +func TestOpenRTB_ORTBUserExtSessionDuration(t *testing.T) { + type fields struct { + values URLValues + } + tests := []struct { + name string + fields fields + user *openrtb2.User + wantResult *openrtb2.User + wantErr error + }{ + { + name: "Nil_User_and_Ext", + fields: fields{ + values: URLValues{ + Values: url.Values{ + ORTBUserExtSessionDuration: []string{"3600"}, + }, + }, + }, + wantResult: &openrtb2.User{ + Ext: json.RawMessage(`{"sessionduration":3600}`), + }, + wantErr: nil, + }, + { + name: "Valid_sessionduration", + fields: fields{ + values: URLValues{ + Values: url.Values{ + ORTBUserExtSessionDuration: []string{"3600"}, + }, + }, + }, + user: &openrtb2.User{}, + wantResult: &openrtb2.User{ + Ext: json.RawMessage(`{"sessionduration":3600}`), + }, + wantErr: nil, + }, + { + name: "Zero_sessionduration", + fields: fields{ + values: URLValues{ + Values: url.Values{ + ORTBUserExtSessionDuration: {"0"}, + }, + }, + }, + user: &openrtb2.User{}, + wantResult: &openrtb2.User{ + Ext: json.RawMessage(`{"sessionduration":0}`), + }, + wantErr: nil, + }, + { + name: "Negative_sessionduration", + fields: fields{ + values: URLValues{ + Values: url.Values{ + ORTBUserExtSessionDuration: {"-10"}, + }, + }, + }, + user: &openrtb2.User{}, + wantResult: &openrtb2.User{Ext: nil}, + wantErr: nil, + }, + { + name: "Empty_sessionduration", + fields: fields{ + values: URLValues{ + Values: url.Values{ + ORTBUserExtSessionDuration: {""}, + }, + }, + }, + user: &openrtb2.User{}, + wantResult: &openrtb2.User{ + Ext: nil, + }, + wantErr: nil, + }, + { + name: "Missing_sessionduration", + fields: fields{ + values: URLValues{ + Values: url.Values{}, + }, + }, + user: &openrtb2.User{}, + wantResult: &openrtb2.User{ + Ext: nil, + }, + wantErr: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + o := &OpenRTB{ + values: tt.fields.values, + ortb: &openrtb2.BidRequest{ID: "request-ID", User: tt.user}, + } + err := o.ORTBUserExtSessionDuration() + assert.Equal(t, tt.wantErr, err) + assert.Equal(t, tt.wantResult, o.ortb.User) + }) + } +} + +func TestOpenRTB_ORTBUserExtImpDepth(t *testing.T) { + type fields struct { + values URLValues + } + tests := []struct { + name string + fields fields + user *openrtb2.User + wantResult *openrtb2.User + wantErr error + }{ + { + name: "Nil_User_and_Ext", + fields: fields{ + values: URLValues{ + Values: url.Values{ + ORTBUserExtImpDepth: []string{"2"}, + }, + }, + }, + wantResult: &openrtb2.User{ + Ext: json.RawMessage(`{"impdepth":2}`), + }, + wantErr: nil, + }, + { + name: "Valid_impdepth", + fields: fields{ + values: URLValues{ + Values: url.Values{ + ORTBUserExtImpDepth: []string{"2"}, + }, + }, + }, + user: &openrtb2.User{}, + wantResult: &openrtb2.User{ + Ext: json.RawMessage(`{"impdepth":2}`), + }, + wantErr: nil, + }, + { + name: "Zero_impdepth", + fields: fields{ + values: URLValues{ + Values: url.Values{ + ORTBUserExtImpDepth: {"0"}, + }, + }, + }, + user: &openrtb2.User{}, + wantResult: &openrtb2.User{ + Ext: json.RawMessage(`{"impdepth":0}`), + }, + wantErr: nil, + }, + { + name: "Negative_impdepth", + fields: fields{ + values: URLValues{ + Values: url.Values{ + ORTBUserExtImpDepth: {"-10"}, + }, + }, + }, + user: &openrtb2.User{}, + wantResult: &openrtb2.User{Ext: nil}, + wantErr: nil, + }, + { + name: "Empty_impdepth", + fields: fields{ + values: URLValues{ + Values: url.Values{ + ORTBUserExtImpDepth: {""}, + }, + }, + }, + user: &openrtb2.User{}, + wantResult: &openrtb2.User{ + Ext: nil, + }, + wantErr: nil, + }, + { + name: "Missing_impdepth", + fields: fields{ + values: URLValues{}, + }, + user: &openrtb2.User{}, + wantResult: &openrtb2.User{ + Ext: nil, + }, + wantErr: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + o := &OpenRTB{ + values: tt.fields.values, + ortb: &openrtb2.BidRequest{ID: "request-ID", User: tt.user}, + } + err := o.ORTBUserExtImpDepth() + assert.Equal(t, tt.wantErr, err) + assert.Equal(t, tt.wantResult, o.ortb.User) + }) + } +} diff --git a/modules/pubmatic/openwrap/endpoints/legacy/ctv/parser_map.go b/modules/pubmatic/openwrap/endpoints/legacy/ctv/parser_map.go index d536c439460..a774822b529 100644 --- a/modules/pubmatic/openwrap/endpoints/legacy/ctv/parser_map.go +++ b/modules/pubmatic/openwrap/endpoints/legacy/ctv/parser_map.go @@ -255,6 +255,10 @@ var ortbMapper = &ParserMap{ ORTBUserExtConsent: Parser.ORTBUserExtConsent, //User.Ext.EIDS ORTBUserExtEIDS: Parser.ORTBUserExtEIDS, + //User.Ext.SessionDuration + ORTBUserExtSessionDuration: Parser.ORTBUserExtSessionDuration, + //User.Ext.ImpDepth + ORTBUserExtImpDepth: Parser.ORTBUserExtImpDepth, //User.Data ORTBUserData: Parser.ORTBUserData, @@ -603,6 +607,13 @@ type Parser interface { //User.Ext.EIDS ORTBUserExtEIDS() error + + //User.Ext.SessionDuration + ORTBUserExtSessionDuration() error + + //User.Ext.ImpDepth + ORTBUserExtImpDepth() error + //User.Data ORTBUserData() error diff --git a/modules/pubmatic/openwrap/endpoints/legacy/ctv/util.go b/modules/pubmatic/openwrap/endpoints/legacy/ctv/util.go index 0e734870b58..b17ff67171c 100644 --- a/modules/pubmatic/openwrap/endpoints/legacy/ctv/util.go +++ b/modules/pubmatic/openwrap/endpoints/legacy/ctv/util.go @@ -1,16 +1,9 @@ package ctv import ( - "encoding/json" "net/url" - "regexp" - - "github.com/prebid/openrtb/v20/openrtb2" - "github.com/prebid/prebid-server/v2/openrtb_ext" ) -var uidRegexp = regexp.MustCompile(`^(UID2|ID5|BGID|euid|PAIRID|IDL|connectid|firstid|utiq):`) - func GetPubIdFromQueryParams(params url.Values) string { pubId := params.Get(ORTBSitePublisherID) if len(pubId) == 0 { @@ -18,54 +11,3 @@ func GetPubIdFromQueryParams(params url.Values) string { } return pubId } - -func ValidateEIDs(eids []openrtb2.EID) []openrtb2.EID { - validEIDs := make([]openrtb2.EID, 0, len(eids)) - for _, eid := range eids { - validUIDs := make([]openrtb2.UID, 0, len(eid.UIDs)) - for _, uid := range eid.UIDs { - uid.ID = uidRegexp.ReplaceAllString(uid.ID, "") - if uid.ID != "" { - validUIDs = append(validUIDs, uid) - } - } - if len(validUIDs) > 0 { - eid.UIDs = validUIDs - validEIDs = append(validEIDs, eid) - } - } - return validEIDs -} - -func UpdateUserEidsWithValidValues(user *openrtb2.User) { - if user == nil { - return - } - - if user.Ext != nil { - var userExt openrtb_ext.ExtUser - err := json.Unmarshal(user.Ext, &userExt) - if err != nil { - return - } - - eids := ValidateEIDs(userExt.Eids) - userExt.Eids = nil - if len(eids) > 0 { - userExt.Eids = eids - } - - userExtjson, err := json.Marshal(userExt) - if err == nil { - user.Ext = userExtjson - } - } - - if len(user.EIDs) > 0 { - eids := ValidateEIDs(user.EIDs) - user.EIDs = nil - if len(eids) > 0 { - user.EIDs = eids - } - } -} diff --git a/modules/pubmatic/openwrap/endpoints/legacy/ctv/util_test.go b/modules/pubmatic/openwrap/endpoints/legacy/ctv/util_test.go deleted file mode 100644 index 177ab5c6372..00000000000 --- a/modules/pubmatic/openwrap/endpoints/legacy/ctv/util_test.go +++ /dev/null @@ -1,127 +0,0 @@ -package ctv - -import ( - "encoding/json" - "testing" - - "github.com/prebid/openrtb/v20/openrtb2" - "github.com/stretchr/testify/assert" -) - -func TestUpdateUserEidsWithValidValues(t *testing.T) { - type args struct { - user *openrtb2.User - } - tests := []struct { - name string - args args - want *openrtb2.User - }{ - { - name: "test_valid_user_eids", - args: args{ - user: &openrtb2.User{ - EIDs: []openrtb2.EID{ - { - Source: "uidapi.com", - UIDs: []openrtb2.UID{ - { - ID: "UID2:testUID", - }, - }, - }, - }, - }, - }, - want: &openrtb2.User{ - EIDs: []openrtb2.EID{ - { - Source: "uidapi.com", - UIDs: []openrtb2.UID{ - { - ID: "testUID", - }, - }, - }, - }, - }, - }, - { - name: "test_user_eids_and_user_ext_eids", - args: args{ - user: &openrtb2.User{ - Ext: json.RawMessage(`{"eids":[{"source":"uidapi.com","uids":[{"id":"UID2:testUID"},{"id":"testUID2"}]},{"source":"euid.eu","uids":[{"id":"testeuid"}]},{"source":"liveramp.com","uids":[{"id":""}]}]}`), - EIDs: []openrtb2.EID{ - { - Source: "uidapi.com", - UIDs: []openrtb2.UID{ - { - ID: "UID2:testUID", - }, - }, - }, - }, - }, - }, - want: &openrtb2.User{ - Ext: json.RawMessage(`{"eids":[{"source":"uidapi.com","uids":[{"id":"testUID"},{"id":"testUID2"}]},{"source":"euid.eu","uids":[{"id":"testeuid"}]}]}`), - EIDs: []openrtb2.EID{ - { - Source: "uidapi.com", - UIDs: []openrtb2.UID{ - { - ID: "testUID", - }, - }, - }, - }, - }, - }, - { - name: "test_user_ext_eids", - args: args{ - user: &openrtb2.User{ - Ext: json.RawMessage(`{"eids":[{"source":"uidapi.com","uids":[{"id":"UID2:testUID"},{"id":"testUID2"}]},{"source":"euid.eu","uids":[{"id":"testeuid"}]},{"source":"liveramp.com","uids":[{"id":""}]}]}`), - }, - }, - want: &openrtb2.User{ - Ext: json.RawMessage(`{"eids":[{"source":"uidapi.com","uids":[{"id":"testUID"},{"id":"testUID2"}]},{"source":"euid.eu","uids":[{"id":"testeuid"}]}]}`), - }, - }, - { - name: "test_user_ext_eids_invalid", - args: args{ - user: &openrtb2.User{ - Ext: json.RawMessage(`{"eids":[{"source":"uidapi.com","uids":[{"id":"UID2:"},{"id":""}]},{"source":"euid.eu","uids":[{"id":"euid:"}]},{"source":"liveramp.com","uids":[{"id":""}]}]}`), - }, - }, - want: &openrtb2.User{ - Ext: json.RawMessage(`{}`), - }, - }, - { - name: "test_valid_user_eids_invalid", - args: args{ - user: &openrtb2.User{ - EIDs: []openrtb2.EID{ - { - Source: "uidapi.com", - UIDs: []openrtb2.UID{ - { - ID: "UID2:", - }, - }, - }, - }, - }, - }, - want: &openrtb2.User{}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - UpdateUserEidsWithValidValues(tt.args.user) - assert.Equal(t, tt.want, tt.args.user) - }) - } -} diff --git a/modules/pubmatic/openwrap/util.go b/modules/pubmatic/openwrap/util.go index d0976403770..ea348f9ad98 100644 --- a/modules/pubmatic/openwrap/util.go +++ b/modules/pubmatic/openwrap/util.go @@ -11,6 +11,7 @@ import ( "strings" "github.com/buger/jsonparser" + "github.com/golang/glog" "github.com/prebid/openrtb/v20/adcom1" "github.com/prebid/openrtb/v20/openrtb2" "github.com/prebid/openrtb/v20/openrtb3" @@ -23,6 +24,8 @@ import ( "github.com/prebid/prebid-server/v2/openrtb_ext" ) +var uidRegexp = regexp.MustCompile(`^(UID2|ID5|BGID|euid|PAIRID|IDL|connectid|firstid|utiq):`) + var ( widthRegEx *regexp.Regexp heightRegEx *regexp.Regexp @@ -486,3 +489,62 @@ func getStringValueFromRequest(request []byte, key []string) (string, bool, erro } return string(val), true, nil } + +func ValidateEIDs(eids []openrtb2.EID) []openrtb2.EID { + validEIDs := make([]openrtb2.EID, 0, len(eids)) + for _, eid := range eids { + validUIDs := make([]openrtb2.UID, 0, len(eid.UIDs)) + for _, uid := range eid.UIDs { + uid.ID = uidRegexp.ReplaceAllString(uid.ID, "") + if uid.ID != "" { + validUIDs = append(validUIDs, uid) + } + } + if len(validUIDs) > 0 { + eid.UIDs = validUIDs + validEIDs = append(validEIDs, eid) + } + } + return validEIDs +} + +func UpdateUserExtWithValidValues(user *openrtb2.User) { + if user == nil { + return + } + + if user.Ext != nil { + var userExt openrtb_ext.ExtUser + err := json.Unmarshal(user.Ext, &userExt) + if err != nil { + return + } + if userExt.SessionDuration < 0 { + glog.Warningf("Invalid sessionduration value: %v. Only positive values are allowed.", userExt.SessionDuration) + userExt.SessionDuration = 0 + } + + if userExt.ImpDepth < 0 { + glog.Warningf("Invalid impdepth value: %v. Only positive values are allowed.", userExt.ImpDepth) + userExt.ImpDepth = 0 + } + eids := ValidateEIDs(userExt.Eids) + userExt.Eids = nil + if len(eids) > 0 { + userExt.Eids = eids + } + + userExtjson, err := json.Marshal(userExt) + if err == nil { + user.Ext = userExtjson + } + } + + if len(user.EIDs) > 0 { + eids := ValidateEIDs(user.EIDs) + user.EIDs = nil + if len(eids) > 0 { + user.EIDs = eids + } + } +} diff --git a/modules/pubmatic/openwrap/util_test.go b/modules/pubmatic/openwrap/util_test.go index 89c487d7ccb..61407b9403e 100644 --- a/modules/pubmatic/openwrap/util_test.go +++ b/modules/pubmatic/openwrap/util_test.go @@ -1782,3 +1782,146 @@ func TestGetStringValueFromRequest(t *testing.T) { assert.Equal(t, test.expectedError, err, "getStringValueFromRequest should return expected error for test case: %s", test.description) } } + +func TestUpdateUserExtWithValidValues(t *testing.T) { + type args struct { + user *openrtb2.User + } + tests := []struct { + name string + args args + want *openrtb2.User + }{ + { + name: "test_valid_user_eids", + args: args{ + user: &openrtb2.User{ + EIDs: []openrtb2.EID{ + { + Source: "uidapi.com", + UIDs: []openrtb2.UID{ + { + ID: "UID2:testUID", + }, + }, + }, + }, + }, + }, + want: &openrtb2.User{ + EIDs: []openrtb2.EID{ + { + Source: "uidapi.com", + UIDs: []openrtb2.UID{ + { + ID: "testUID", + }, + }, + }, + }, + }, + }, + { + name: "test_user_eids_and_user_ext_eids", + args: args{ + user: &openrtb2.User{ + Ext: json.RawMessage(`{"eids":[{"source":"uidapi.com","uids":[{"id":"UID2:testUID"},{"id":"testUID2"}]},{"source":"euid.eu","uids":[{"id":"testeuid"}]},{"source":"liveramp.com","uids":[{"id":""}]}]}`), + EIDs: []openrtb2.EID{ + { + Source: "uidapi.com", + UIDs: []openrtb2.UID{ + { + ID: "UID2:testUID", + }, + }, + }, + }, + }, + }, + want: &openrtb2.User{ + Ext: json.RawMessage(`{"eids":[{"source":"uidapi.com","uids":[{"id":"testUID"},{"id":"testUID2"}]},{"source":"euid.eu","uids":[{"id":"testeuid"}]}]}`), + EIDs: []openrtb2.EID{ + { + Source: "uidapi.com", + UIDs: []openrtb2.UID{ + { + ID: "testUID", + }, + }, + }, + }, + }, + }, + { + name: "test_user_ext_eids", + args: args{ + user: &openrtb2.User{ + Ext: json.RawMessage(`{"eids":[{"source":"uidapi.com","uids":[{"id":"UID2:testUID"},{"id":"testUID2"}]},{"source":"euid.eu","uids":[{"id":"testeuid"}]},{"source":"liveramp.com","uids":[{"id":""}]}]}`), + }, + }, + want: &openrtb2.User{ + Ext: json.RawMessage(`{"eids":[{"source":"uidapi.com","uids":[{"id":"testUID"},{"id":"testUID2"}]},{"source":"euid.eu","uids":[{"id":"testeuid"}]}]}`), + }, + }, + { + name: "test_user_ext_eids_invalid", + args: args{ + user: &openrtb2.User{ + Ext: json.RawMessage(`{"eids":[{"source":"uidapi.com","uids":[{"id":"UID2:"},{"id":""}]},{"source":"euid.eu","uids":[{"id":"euid:"}]},{"source":"liveramp.com","uids":[{"id":""}]}]}`), + }, + }, + want: &openrtb2.User{ + Ext: json.RawMessage(`{}`), + }, + }, + { + name: "test_valid_user_eids_invalid", + args: args{ + user: &openrtb2.User{ + EIDs: []openrtb2.EID{ + { + Source: "uidapi.com", + UIDs: []openrtb2.UID{ + { + ID: "UID2:", + }, + }, + }, + }, + }, + }, + want: &openrtb2.User{}, + }, + { + name: "test_valid_user_ext_sessionduration_impdepth", + args: args{ + user: &openrtb2.User{ + Ext: json.RawMessage(`{"sessionduration":40,"impdepth":10}`), + }, + }, + want: &openrtb2.User{ + Ext: json.RawMessage(`{"sessionduration":40,"impdepth":10}`), + }, + }, + { + name: "test_invalid_user_ext_sessionduration_impdepth", + args: args{ + user: &openrtb2.User{ + Ext: json.RawMessage(`{ + "sessionduration": -20, + "impdepth": -10 + }`), + }, + }, + want: &openrtb2.User{ + Ext: json.RawMessage(`{}`), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + UpdateUserExtWithValidValues(tt.args.user) + assert.Equal(t, tt.want, tt.args.user) + }) + } +} diff --git a/openrtb_ext/user.go b/openrtb_ext/user.go index cd686a6ee75..85a0a5d047e 100644 --- a/openrtb_ext/user.go +++ b/openrtb_ext/user.go @@ -22,7 +22,9 @@ type ExtUser struct { Eids []openrtb2.EID `json:"eids,omitempty"` - Data json.RawMessage `json:"data,omitempty"` + Data json.RawMessage `json:"data,omitempty"` + SessionDuration int64 `json:"sessionduration,omitempty"` + ImpDepth int64 `json:"impdepth,omitempty"` } // ExtUserPrebid defines the contract for bidrequest.user.ext.prebid From 08d2867495b2ae7785b551ad90b79969628ecffe Mon Sep 17 00:00:00 2001 From: Saurabh Narkhede <108730956+pm-saurabh-narkhede@users.noreply.github.com> Date: Wed, 9 Oct 2024 16:55:25 +0530 Subject: [PATCH 4/9] UOE-11168: Consume then Remove `app.id` from ApplovinMax Request (#931) --- modules/pubmatic/openwrap/applovinmax.go | 40 +++++++++++++------ modules/pubmatic/openwrap/applovinmax_test.go | 38 +++++++++++++----- modules/pubmatic/openwrap/entrypointhook.go | 10 +---- .../pubmatic/openwrap/entrypointhook_test.go | 2 +- 4 files changed, 56 insertions(+), 34 deletions(-) diff --git a/modules/pubmatic/openwrap/applovinmax.go b/modules/pubmatic/openwrap/applovinmax.go index a95ee5edbba..d5fdcbdaefa 100644 --- a/modules/pubmatic/openwrap/applovinmax.go +++ b/modules/pubmatic/openwrap/applovinmax.go @@ -5,6 +5,7 @@ import ( "strconv" "github.com/buger/jsonparser" + "github.com/golang/glog" "github.com/prebid/openrtb/v20/openrtb2" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" ) @@ -16,13 +17,13 @@ func getSignalData(requestBody []byte, rctx models.RequestCtx) *openrtb2.BidRequ if err == jsonparser.KeyPathNotFoundError { signalType = models.MissingSignal } - rctx.MetricsEngine.RecordSignalDataStatus(getAppPublisherID(requestBody), getProfileID(requestBody), signalType) + rctx.MetricsEngine.RecordSignalDataStatus(getAppPublisherID(requestBody), rctx.ProfileIDStr, signalType) return nil } signalData := &openrtb2.BidRequest{} if err := json.Unmarshal([]byte(signal), signalData); err != nil { - rctx.MetricsEngine.RecordSignalDataStatus(getAppPublisherID(requestBody), getProfileID(requestBody), models.InvalidSignal) + rctx.MetricsEngine.RecordSignalDataStatus(getAppPublisherID(requestBody), rctx.ProfileIDStr, models.InvalidSignal) return nil } return signalData @@ -234,6 +235,7 @@ func updateRequestWrapper(signalExt json.RawMessage, maxRequest *openrtb2.BidReq } func updateAppLovinMaxRequest(requestBody []byte, rctx models.RequestCtx) []byte { + requestBody, rctx.ProfileIDStr = setProfileID(requestBody) signalData := getSignalData(requestBody, rctx) if signalData == nil { return modifyRequestBody(requestBody) @@ -251,6 +253,29 @@ func updateAppLovinMaxRequest(requestBody []byte, rctx models.RequestCtx) []byte return requestBody } +func setProfileID(requestBody []byte) ([]byte, string) { + if profileId, err := jsonparser.GetInt(requestBody, "ext", "prebid", "bidderparams", "pubmatic", "wrapper", "profileid"); err == nil && profileId != 0 { + return requestBody, strconv.Itoa(int(profileId)) + } + + profIDStr, err := jsonparser.GetString(requestBody, "app", "id") + if err != nil || len(profIDStr) == 0 { + return requestBody, "" + } + + if _, err := strconv.Atoi(profIDStr); err != nil { + glog.Errorf("[AppLovinMax] [ProfileID]: %s [Error]: failed to convert app.id to integer %v", profIDStr, err) + return requestBody, "" + } + + requestBody = jsonparser.Delete(requestBody, "app", "id") + if newRequestBody, err := jsonparser.Set(requestBody, []byte(profIDStr), "ext", "prebid", "bidderparams", "pubmatic", "wrapper", "profileid"); err == nil { + return newRequestBody, profIDStr + } + glog.Errorf("[AppLovinMax] [ProfileID]: %s [Error]: failed to set profileid in wrapper %v", profIDStr, err) + return requestBody, "" +} + func updateAppLovinMaxResponse(rctx models.RequestCtx, bidResponse *openrtb2.BidResponse) models.AppLovinMax { rctx.AppLovinMax.Reject = false @@ -308,17 +333,6 @@ func getAppPublisherID(requestBody []byte) string { return "" } -func getProfileID(requestBody []byte) string { - if profileId, err := jsonparser.GetInt(requestBody, "ext", "prebid", "bidderparams", "pubmatic", "wrapper", "profileid"); err == nil { - profIDStr := strconv.Itoa(int(profileId)) - return profIDStr - } - if profIDStr, err := jsonparser.GetString(requestBody, "app", "id"); err == nil { - return profIDStr - } - return "" -} - // modifyRequestBody modifies displaymanger and banner object in req if signal is missing/invalid func modifyRequestBody(requestBody []byte) []byte { if body, err := jsonparser.Set(requestBody, []byte(strconv.Quote("PubMatic_OpenWrap_SDK")), "imp", "[0]", "displaymanager"); err == nil { diff --git a/modules/pubmatic/openwrap/applovinmax_test.go b/modules/pubmatic/openwrap/applovinmax_test.go index fec08e77116..53fcf7d4697 100644 --- a/modules/pubmatic/openwrap/applovinmax_test.go +++ b/modules/pubmatic/openwrap/applovinmax_test.go @@ -661,6 +661,7 @@ func TestGetSignalData(t *testing.T) { requestBody: []byte(`{"id":"123","app":{"publisher":{"id":"5890"}},"user":{"data":[{"id":"1","name":"Publisher Passed","segment":[{"signal":"{BIDDING_SIGNAL}"}]}]},"ext":{"prebid":{"bidderparams":{"pubmatic":{"wrapper":{"profileid":1234}}}}}}`), rctx: models.RequestCtx{ MetricsEngine: mockEngine, + ProfileIDStr: "1234", }, }, setup: func() { @@ -674,6 +675,7 @@ func TestGetSignalData(t *testing.T) { requestBody: []byte(`{"id":"123","user":{"data":[{"id":"1","name":"Publisher Passed","segment":[{"signal":{}}]}]},"ext":{"prebid":{"bidderparams":{"pubmatic":{"wrapper":{"profileid":1234}}}}}}`), rctx: models.RequestCtx{ MetricsEngine: mockEngine, + ProfileIDStr: "1234", }, }, setup: func() { @@ -1119,48 +1121,62 @@ func TestModifyRequestBody(t *testing.T) { } } -func TestGetProfileID(t *testing.T) { +func TestSetProfileID(t *testing.T) { type args struct { requestBody []byte } tests := []struct { - name string - args args - want string + name string + args args + wantProfile string + wantBody []byte }{ { name: "profileID present in request.ext.prebid.bidderparams.pubmatic.wrapper", args: args{ requestBody: []byte(`{"app":{"bundle":"com.pubmatic.openbid.app","name":"Sample","publisher":{"id":"156276"},"storeurl":"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098","ver":"1.0"},"at":1,"device":{"carrier":"MYTEL","connectiontype":2,"devicetype":4,"ext":{"atts":2},"geo":{"city":"Queens","country":"USA","dma":"501","ipservice":3,"lat":40.7429,"lon":-73.9392,"long":-73.9392,"metro":"501","region":"ny","type":2,"zip":"11101"},"h":2400,"hwv":"ruby","ifa":"497a10d6-c4dd-4e04-a986-c32b7180d462","ip":"38.158.207.171","js":1,"language":"en_US","make":"xiaomi","model":"22101316c","os":"android","osv":"13.0.0","ppi":440,"pxratio":2.75,"ua":"Mozilla/5.0 (Linux; Android 13; 22101316C Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.230 Mobile Safari/537.36","w":1080},"ext":{"prebid":{"bidderparams":{"pubmatic":{"wrapper":{"profileid":1234}}}}},"id":"95d6643c-3da6-40a2-b9ca-12279393ffbf","imp":[{"banner":{"format":[{"w":728,"h":90},{"w":320,"h":50}],"api":[5,2],"w":700,"h":900},"clickbrowser":0,"displaymanager":"OpenBid_SDK","displaymanagerver":"1.4.0","ext":{"reward":1},"id":"imp176227948","secure":0,"tagid":"OpenWrapBidderBannerAdUnit"}],"regs":{"ext":{"gdpr":0}},"source":{"ext":{"omidpn":"PubMatic","omidpv":"1.2.11-Pubmatic"}},"user":{"data":[{"id":"1","name":"Publisher Passed","segment":[{"signal":"{\"id\":\"95d6643c-3da6-40a2-b9ca-12279393ffbf\",\"at\":1,\"tmax\":500,\"cur\":[\"USD\"],\"imp\":[{\"id\":\"imp176227948\",\"clickbrowser\":0,\"displaymanager\":\"PubMatic_OpenBid_SDK\",\"displaymanagerver\":\"1.4.0\",\"tagid\":\"/43743431/DMDemo\",\"secure\":0,\"banner\":{\"pos\":7,\"format\":[{\"w\":300,\"h\":250}],\"api\":[5,6,7]},\"instl\":1}],\"app\":{\"name\":\"OpenWrapperSample\",\"bundle\":\"com.pubmatic.openbid.app\",\"storeurl\":\"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098?appnexus_banner_fixedbid=1&fixedbid=1\",\"ver\":\"1.0\",\"publisher\":{\"id\":\"5890\"}},\"device\":{\"ext\":{\"atts\":0},\"geo\":{\"type\":1,\"lat\":37.421998333333335,\"lon\":-122.08400000000002},\"pxratio\":2.625,\"mccmnc\":\"310-260\",\"lmt\":0,\"ifa\":\"07c387f2-e030-428f-8336-42f682150759\",\"connectiontype\":6,\"carrier\":\"Android\",\"js\":1,\"ua\":\"Mozilla/5.0(Linux;Android9;AndroidSDKbuiltforx86Build/PSR1.180720.075;wv)AppleWebKit/537.36(KHTML,likeGecko)Version/4.0Chrome/69.0.3497.100MobileSafari/537.36\",\"make\":\"Google\",\"model\":\"AndroidSDKbuiltforx86\",\"os\":\"Android\",\"osv\":\"9\",\"h\":1794,\"w\":1080,\"language\":\"en-US\",\"devicetype\":4},\"source\":{\"ext\":{\"omidpn\":\"PubMatic\",\"omidpv\":\"1.2.11-Pubmatic\"}},\"user\":{},\"ext\":{\"wrapper\":{\"ssauction\":0,\"sumry_disable\":0,\"profileid\":58135,\"versionid\":1,\"clientconfig\":1}}}"}]}],"ext":{"consent":"CP2KIMAP2KIgAEPgABBYJGNX_H__bX9j-Xr3"}}}`), }, - want: "1234", + wantProfile: "1234", + wantBody: []byte(`{"app":{"bundle":"com.pubmatic.openbid.app","name":"Sample","publisher":{"id":"156276"},"storeurl":"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098","ver":"1.0"},"at":1,"device":{"carrier":"MYTEL","connectiontype":2,"devicetype":4,"ext":{"atts":2},"geo":{"city":"Queens","country":"USA","dma":"501","ipservice":3,"lat":40.7429,"lon":-73.9392,"long":-73.9392,"metro":"501","region":"ny","type":2,"zip":"11101"},"h":2400,"hwv":"ruby","ifa":"497a10d6-c4dd-4e04-a986-c32b7180d462","ip":"38.158.207.171","js":1,"language":"en_US","make":"xiaomi","model":"22101316c","os":"android","osv":"13.0.0","ppi":440,"pxratio":2.75,"ua":"Mozilla/5.0 (Linux; Android 13; 22101316C Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.230 Mobile Safari/537.36","w":1080},"ext":{"prebid":{"bidderparams":{"pubmatic":{"wrapper":{"profileid":1234}}}}},"id":"95d6643c-3da6-40a2-b9ca-12279393ffbf","imp":[{"banner":{"format":[{"w":728,"h":90},{"w":320,"h":50}],"api":[5,2],"w":700,"h":900},"clickbrowser":0,"displaymanager":"OpenBid_SDK","displaymanagerver":"1.4.0","ext":{"reward":1},"id":"imp176227948","secure":0,"tagid":"OpenWrapBidderBannerAdUnit"}],"regs":{"ext":{"gdpr":0}},"source":{"ext":{"omidpn":"PubMatic","omidpv":"1.2.11-Pubmatic"}},"user":{"data":[{"id":"1","name":"Publisher Passed","segment":[{"signal":"{\"id\":\"95d6643c-3da6-40a2-b9ca-12279393ffbf\",\"at\":1,\"tmax\":500,\"cur\":[\"USD\"],\"imp\":[{\"id\":\"imp176227948\",\"clickbrowser\":0,\"displaymanager\":\"PubMatic_OpenBid_SDK\",\"displaymanagerver\":\"1.4.0\",\"tagid\":\"/43743431/DMDemo\",\"secure\":0,\"banner\":{\"pos\":7,\"format\":[{\"w\":300,\"h\":250}],\"api\":[5,6,7]},\"instl\":1}],\"app\":{\"name\":\"OpenWrapperSample\",\"bundle\":\"com.pubmatic.openbid.app\",\"storeurl\":\"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098?appnexus_banner_fixedbid=1&fixedbid=1\",\"ver\":\"1.0\",\"publisher\":{\"id\":\"5890\"}},\"device\":{\"ext\":{\"atts\":0},\"geo\":{\"type\":1,\"lat\":37.421998333333335,\"lon\":-122.08400000000002},\"pxratio\":2.625,\"mccmnc\":\"310-260\",\"lmt\":0,\"ifa\":\"07c387f2-e030-428f-8336-42f682150759\",\"connectiontype\":6,\"carrier\":\"Android\",\"js\":1,\"ua\":\"Mozilla/5.0(Linux;Android9;AndroidSDKbuiltforx86Build/PSR1.180720.075;wv)AppleWebKit/537.36(KHTML,likeGecko)Version/4.0Chrome/69.0.3497.100MobileSafari/537.36\",\"make\":\"Google\",\"model\":\"AndroidSDKbuiltforx86\",\"os\":\"Android\",\"osv\":\"9\",\"h\":1794,\"w\":1080,\"language\":\"en-US\",\"devicetype\":4},\"source\":{\"ext\":{\"omidpn\":\"PubMatic\",\"omidpv\":\"1.2.11-Pubmatic\"}},\"user\":{},\"ext\":{\"wrapper\":{\"ssauction\":0,\"sumry_disable\":0,\"profileid\":58135,\"versionid\":1,\"clientconfig\":1}}}"}]}],"ext":{"consent":"CP2KIMAP2KIgAEPgABBYJGNX_H__bX9j-Xr3"}}}`), }, { - name: "profileID not present in request.ext.prebid.bidderparams.pubmatic.wrapper but present in request.app.id", + name: "profileID not present in request.ext.prebid.bidderparams.pubmatic.wrapper but present in request.app.id, remove app.id", args: args{ requestBody: []byte(`{"app":{"bundle":"com.pubmatic.openbid.app","name":"Sample","publisher":{"id":"156276"},"id":"13137","storeurl":"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098","ver":"1.0"},"at":1,"device":{"carrier":"MYTEL","connectiontype":2,"devicetype":4,"ext":{"atts":2},"geo":{"city":"Queens","country":"USA","dma":"501","ipservice":3,"lat":40.7429,"lon":-73.9392,"long":-73.9392,"metro":"501","region":"ny","type":2,"zip":"11101"},"h":2400,"hwv":"ruby","ifa":"497a10d6-c4dd-4e04-a986-c32b7180d462","ip":"38.158.207.171","js":1,"language":"en_US","make":"xiaomi","model":"22101316c","os":"android","osv":"13.0.0","ppi":440,"pxratio":2.75,"ua":"Mozilla/5.0 (Linux; Android 13; 22101316C Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.230 Mobile Safari/537.36","w":1080},"ext":{"prebid":{"bidderparams":{"pubmatic":{"wrapper":{}}}}},"id":"95d6643c-3da6-40a2-b9ca-12279393ffbf","imp":[{"banner":{"format":[{"w":728,"h":90},{"w":320,"h":50}],"api":[5,2],"w":700,"h":900},"clickbrowser":0,"displaymanager":"OpenBid_SDK","displaymanagerver":"1.4.0","ext":{"reward":1},"id":"imp176227948","secure":0,"tagid":"OpenWrapBidderBannerAdUnit"}],"regs":{"ext":{"gdpr":0}},"source":{"ext":{"omidpn":"PubMatic","omidpv":"1.2.11-Pubmatic"}},"user":{"data":[{"id":"1","name":"Publisher Passed","segment":[{"signal":"{\"id\":\"95d6643c-3da6-40a2-b9ca-12279393ffbf\",\"at\":1,\"tmax\":500,\"cur\":[\"USD\"],\"imp\":[{\"id\":\"imp176227948\",\"clickbrowser\":0,\"displaymanager\":\"PubMatic_OpenBid_SDK\",\"displaymanagerver\":\"1.4.0\",\"tagid\":\"/43743431/DMDemo\",\"secure\":0,\"banner\":{\"pos\":7,\"format\":[{\"w\":300,\"h\":250}],\"api\":[5,6,7]},\"instl\":1}],\"app\":{\"name\":\"OpenWrapperSample\",\"bundle\":\"com.pubmatic.openbid.app\",\"storeurl\":\"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098?appnexus_banner_fixedbid=1&fixedbid=1\",\"ver\":\"1.0\",\"publisher\":{\"id\":\"5890\"}},\"device\":{\"ext\":{\"atts\":0},\"geo\":{\"type\":1,\"lat\":37.421998333333335,\"lon\":-122.08400000000002},\"pxratio\":2.625,\"mccmnc\":\"310-260\",\"lmt\":0,\"ifa\":\"07c387f2-e030-428f-8336-42f682150759\",\"connectiontype\":6,\"carrier\":\"Android\",\"js\":1,\"ua\":\"Mozilla/5.0(Linux;Android9;AndroidSDKbuiltforx86Build/PSR1.180720.075;wv)AppleWebKit/537.36(KHTML,likeGecko)Version/4.0Chrome/69.0.3497.100MobileSafari/537.36\",\"make\":\"Google\",\"model\":\"AndroidSDKbuiltforx86\",\"os\":\"Android\",\"osv\":\"9\",\"h\":1794,\"w\":1080,\"language\":\"en-US\",\"devicetype\":4},\"source\":{\"ext\":{\"omidpn\":\"PubMatic\",\"omidpv\":\"1.2.11-Pubmatic\"}},\"user\":{},\"ext\":{\"wrapper\":{\"ssauction\":0,\"sumry_disable\":0,\"profileid\":58135,\"versionid\":1,\"clientconfig\":1}}}"}]}],"ext":{"consent":"CP2KIMAP2KIgAEPgABBYJGNX_H__bX9j-Xr3"}}}`), }, - want: "13137", + wantProfile: "13137", + wantBody: []byte(`{"app":{"bundle":"com.pubmatic.openbid.app","name":"Sample","publisher":{"id":"156276"},"storeurl":"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098","ver":"1.0"},"at":1,"device":{"carrier":"MYTEL","connectiontype":2,"devicetype":4,"ext":{"atts":2},"geo":{"city":"Queens","country":"USA","dma":"501","ipservice":3,"lat":40.7429,"lon":-73.9392,"long":-73.9392,"metro":"501","region":"ny","type":2,"zip":"11101"},"h":2400,"hwv":"ruby","ifa":"497a10d6-c4dd-4e04-a986-c32b7180d462","ip":"38.158.207.171","js":1,"language":"en_US","make":"xiaomi","model":"22101316c","os":"android","osv":"13.0.0","ppi":440,"pxratio":2.75,"ua":"Mozilla/5.0 (Linux; Android 13; 22101316C Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.230 Mobile Safari/537.36","w":1080},"ext":{"prebid":{"bidderparams":{"pubmatic":{"wrapper":{"profileid":13137}}}}},"id":"95d6643c-3da6-40a2-b9ca-12279393ffbf","imp":[{"banner":{"format":[{"w":728,"h":90},{"w":320,"h":50}],"api":[5,2],"w":700,"h":900},"clickbrowser":0,"displaymanager":"OpenBid_SDK","displaymanagerver":"1.4.0","ext":{"reward":1},"id":"imp176227948","secure":0,"tagid":"OpenWrapBidderBannerAdUnit"}],"regs":{"ext":{"gdpr":0}},"source":{"ext":{"omidpn":"PubMatic","omidpv":"1.2.11-Pubmatic"}},"user":{"data":[{"id":"1","name":"Publisher Passed","segment":[{"signal":"{\"id\":\"95d6643c-3da6-40a2-b9ca-12279393ffbf\",\"at\":1,\"tmax\":500,\"cur\":[\"USD\"],\"imp\":[{\"id\":\"imp176227948\",\"clickbrowser\":0,\"displaymanager\":\"PubMatic_OpenBid_SDK\",\"displaymanagerver\":\"1.4.0\",\"tagid\":\"/43743431/DMDemo\",\"secure\":0,\"banner\":{\"pos\":7,\"format\":[{\"w\":300,\"h\":250}],\"api\":[5,6,7]},\"instl\":1}],\"app\":{\"name\":\"OpenWrapperSample\",\"bundle\":\"com.pubmatic.openbid.app\",\"storeurl\":\"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098?appnexus_banner_fixedbid=1&fixedbid=1\",\"ver\":\"1.0\",\"publisher\":{\"id\":\"5890\"}},\"device\":{\"ext\":{\"atts\":0},\"geo\":{\"type\":1,\"lat\":37.421998333333335,\"lon\":-122.08400000000002},\"pxratio\":2.625,\"mccmnc\":\"310-260\",\"lmt\":0,\"ifa\":\"07c387f2-e030-428f-8336-42f682150759\",\"connectiontype\":6,\"carrier\":\"Android\",\"js\":1,\"ua\":\"Mozilla/5.0(Linux;Android9;AndroidSDKbuiltforx86Build/PSR1.180720.075;wv)AppleWebKit/537.36(KHTML,likeGecko)Version/4.0Chrome/69.0.3497.100MobileSafari/537.36\",\"make\":\"Google\",\"model\":\"AndroidSDKbuiltforx86\",\"os\":\"Android\",\"osv\":\"9\",\"h\":1794,\"w\":1080,\"language\":\"en-US\",\"devicetype\":4},\"source\":{\"ext\":{\"omidpn\":\"PubMatic\",\"omidpv\":\"1.2.11-Pubmatic\"}},\"user\":{},\"ext\":{\"wrapper\":{\"ssauction\":0,\"sumry_disable\":0,\"profileid\":58135,\"versionid\":1,\"clientconfig\":1}}}"}]}],"ext":{"consent":"CP2KIMAP2KIgAEPgABBYJGNX_H__bX9j-Xr3"}}}`), }, { name: "profileID present in both request.ext.prebid.bidderparams.pubmatic.wrapper and request.app.id give priority to wrapper", args: args{ requestBody: []byte(`{"app":{"bundle":"com.pubmatic.openbid.app","name":"Sample","publisher":{"id":"156276"},"id":"13137","storeurl":"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098","ver":"1.0"},"at":1,"device":{"carrier":"MYTEL","connectiontype":2,"devicetype":4,"ext":{"atts":2},"geo":{"city":"Queens","country":"USA","dma":"501","ipservice":3,"lat":40.7429,"lon":-73.9392,"long":-73.9392,"metro":"501","region":"ny","type":2,"zip":"11101"},"h":2400,"hwv":"ruby","ifa":"497a10d6-c4dd-4e04-a986-c32b7180d462","ip":"38.158.207.171","js":1,"language":"en_US","make":"xiaomi","model":"22101316c","os":"android","osv":"13.0.0","ppi":440,"pxratio":2.75,"ua":"Mozilla/5.0 (Linux; Android 13; 22101316C Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.230 Mobile Safari/537.36","w":1080},"ext":{"prebid":{"bidderparams":{"pubmatic":{"wrapper":{"profileid":1234}}}}},"id":"95d6643c-3da6-40a2-b9ca-12279393ffbf","imp":[{"banner":{"format":[{"w":728,"h":90},{"w":320,"h":50}],"api":[5,2],"w":700,"h":900},"clickbrowser":0,"displaymanager":"OpenBid_SDK","displaymanagerver":"1.4.0","ext":{"reward":1},"id":"imp176227948","secure":0,"tagid":"OpenWrapBidderBannerAdUnit"}],"regs":{"ext":{"gdpr":0}},"source":{"ext":{"omidpn":"PubMatic","omidpv":"1.2.11-Pubmatic"}},"user":{"data":[{"id":"1","name":"Publisher Passed","segment":[{"signal":"{\"id\":\"95d6643c-3da6-40a2-b9ca-12279393ffbf\",\"at\":1,\"tmax\":500,\"cur\":[\"USD\"],\"imp\":[{\"id\":\"imp176227948\",\"clickbrowser\":0,\"displaymanager\":\"PubMatic_OpenBid_SDK\",\"displaymanagerver\":\"1.4.0\",\"tagid\":\"/43743431/DMDemo\",\"secure\":0,\"banner\":{\"pos\":7,\"format\":[{\"w\":300,\"h\":250}],\"api\":[5,6,7]},\"instl\":1}],\"app\":{\"name\":\"OpenWrapperSample\",\"bundle\":\"com.pubmatic.openbid.app\",\"storeurl\":\"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098?appnexus_banner_fixedbid=1&fixedbid=1\",\"ver\":\"1.0\",\"publisher\":{\"id\":\"5890\"}},\"device\":{\"ext\":{\"atts\":0},\"geo\":{\"type\":1,\"lat\":37.421998333333335,\"lon\":-122.08400000000002},\"pxratio\":2.625,\"mccmnc\":\"310-260\",\"lmt\":0,\"ifa\":\"07c387f2-e030-428f-8336-42f682150759\",\"connectiontype\":6,\"carrier\":\"Android\",\"js\":1,\"ua\":\"Mozilla/5.0(Linux;Android9;AndroidSDKbuiltforx86Build/PSR1.180720.075;wv)AppleWebKit/537.36(KHTML,likeGecko)Version/4.0Chrome/69.0.3497.100MobileSafari/537.36\",\"make\":\"Google\",\"model\":\"AndroidSDKbuiltforx86\",\"os\":\"Android\",\"osv\":\"9\",\"h\":1794,\"w\":1080,\"language\":\"en-US\",\"devicetype\":4},\"source\":{\"ext\":{\"omidpn\":\"PubMatic\",\"omidpv\":\"1.2.11-Pubmatic\"}},\"user\":{},\"ext\":{\"wrapper\":{\"ssauction\":0,\"sumry_disable\":0,\"profileid\":58135,\"versionid\":1,\"clientconfig\":1}}}"}]}],"ext":{"consent":"CP2KIMAP2KIgAEPgABBYJGNX_H__bX9j-Xr3"}}}`), }, - want: "1234", + wantProfile: "1234", + wantBody: []byte(`{"app":{"bundle":"com.pubmatic.openbid.app","name":"Sample","publisher":{"id":"156276"},"id":"13137","storeurl":"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098","ver":"1.0"},"at":1,"device":{"carrier":"MYTEL","connectiontype":2,"devicetype":4,"ext":{"atts":2},"geo":{"city":"Queens","country":"USA","dma":"501","ipservice":3,"lat":40.7429,"lon":-73.9392,"long":-73.9392,"metro":"501","region":"ny","type":2,"zip":"11101"},"h":2400,"hwv":"ruby","ifa":"497a10d6-c4dd-4e04-a986-c32b7180d462","ip":"38.158.207.171","js":1,"language":"en_US","make":"xiaomi","model":"22101316c","os":"android","osv":"13.0.0","ppi":440,"pxratio":2.75,"ua":"Mozilla/5.0 (Linux; Android 13; 22101316C Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.230 Mobile Safari/537.36","w":1080},"ext":{"prebid":{"bidderparams":{"pubmatic":{"wrapper":{"profileid":1234}}}}},"id":"95d6643c-3da6-40a2-b9ca-12279393ffbf","imp":[{"banner":{"format":[{"w":728,"h":90},{"w":320,"h":50}],"api":[5,2],"w":700,"h":900},"clickbrowser":0,"displaymanager":"OpenBid_SDK","displaymanagerver":"1.4.0","ext":{"reward":1},"id":"imp176227948","secure":0,"tagid":"OpenWrapBidderBannerAdUnit"}],"regs":{"ext":{"gdpr":0}},"source":{"ext":{"omidpn":"PubMatic","omidpv":"1.2.11-Pubmatic"}},"user":{"data":[{"id":"1","name":"Publisher Passed","segment":[{"signal":"{\"id\":\"95d6643c-3da6-40a2-b9ca-12279393ffbf\",\"at\":1,\"tmax\":500,\"cur\":[\"USD\"],\"imp\":[{\"id\":\"imp176227948\",\"clickbrowser\":0,\"displaymanager\":\"PubMatic_OpenBid_SDK\",\"displaymanagerver\":\"1.4.0\",\"tagid\":\"/43743431/DMDemo\",\"secure\":0,\"banner\":{\"pos\":7,\"format\":[{\"w\":300,\"h\":250}],\"api\":[5,6,7]},\"instl\":1}],\"app\":{\"name\":\"OpenWrapperSample\",\"bundle\":\"com.pubmatic.openbid.app\",\"storeurl\":\"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098?appnexus_banner_fixedbid=1&fixedbid=1\",\"ver\":\"1.0\",\"publisher\":{\"id\":\"5890\"}},\"device\":{\"ext\":{\"atts\":0},\"geo\":{\"type\":1,\"lat\":37.421998333333335,\"lon\":-122.08400000000002},\"pxratio\":2.625,\"mccmnc\":\"310-260\",\"lmt\":0,\"ifa\":\"07c387f2-e030-428f-8336-42f682150759\",\"connectiontype\":6,\"carrier\":\"Android\",\"js\":1,\"ua\":\"Mozilla/5.0(Linux;Android9;AndroidSDKbuiltforx86Build/PSR1.180720.075;wv)AppleWebKit/537.36(KHTML,likeGecko)Version/4.0Chrome/69.0.3497.100MobileSafari/537.36\",\"make\":\"Google\",\"model\":\"AndroidSDKbuiltforx86\",\"os\":\"Android\",\"osv\":\"9\",\"h\":1794,\"w\":1080,\"language\":\"en-US\",\"devicetype\":4},\"source\":{\"ext\":{\"omidpn\":\"PubMatic\",\"omidpv\":\"1.2.11-Pubmatic\"}},\"user\":{},\"ext\":{\"wrapper\":{\"ssauction\":0,\"sumry_disable\":0,\"profileid\":58135,\"versionid\":1,\"clientconfig\":1}}}"}]}],"ext":{"consent":"CP2KIMAP2KIgAEPgABBYJGNX_H__bX9j-Xr3"}}}`), }, { name: "profileID not present in both request.ext.prebid.bidderparams.pubmatic.wrapper and request.app.id", args: args{ requestBody: []byte(`{"app":{"bundle":"com.pubmatic.openbid.app","name":"Sample","publisher":{"id":"156276"},"storeurl":"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098","ver":"1.0"},"at":1,"device":{"carrier":"MYTEL","connectiontype":2,"devicetype":4,"ext":{"atts":2},"geo":{"city":"Queens","country":"USA","dma":"501","ipservice":3,"lat":40.7429,"lon":-73.9392,"long":-73.9392,"metro":"501","region":"ny","type":2,"zip":"11101"},"h":2400,"hwv":"ruby","ifa":"497a10d6-c4dd-4e04-a986-c32b7180d462","ip":"38.158.207.171","js":1,"language":"en_US","make":"xiaomi","model":"22101316c","os":"android","osv":"13.0.0","ppi":440,"pxratio":2.75,"ua":"Mozilla/5.0 (Linux; Android 13; 22101316C Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.230 Mobile Safari/537.36","w":1080},"ext":{"prebid":{"bidderparams":{"pubmatic":{"wrapper":{}}}}},"id":"95d6643c-3da6-40a2-b9ca-12279393ffbf","imp":[{"banner":{"format":[{"w":728,"h":90},{"w":320,"h":50}],"api":[5,2],"w":700,"h":900},"clickbrowser":0,"displaymanager":"OpenBid_SDK","displaymanagerver":"1.4.0","ext":{"reward":1},"id":"imp176227948","secure":0,"tagid":"OpenWrapBidderBannerAdUnit"}],"regs":{"ext":{"gdpr":0}},"source":{"ext":{"omidpn":"PubMatic","omidpv":"1.2.11-Pubmatic"}},"user":{"data":[{"id":"1","name":"Publisher Passed","segment":[{"signal":"{\"id\":\"95d6643c-3da6-40a2-b9ca-12279393ffbf\",\"at\":1,\"tmax\":500,\"cur\":[\"USD\"],\"imp\":[{\"id\":\"imp176227948\",\"clickbrowser\":0,\"displaymanager\":\"PubMatic_OpenBid_SDK\",\"displaymanagerver\":\"1.4.0\",\"tagid\":\"/43743431/DMDemo\",\"secure\":0,\"banner\":{\"pos\":7,\"format\":[{\"w\":300,\"h\":250}],\"api\":[5,6,7]},\"instl\":1}],\"app\":{\"name\":\"OpenWrapperSample\",\"bundle\":\"com.pubmatic.openbid.app\",\"storeurl\":\"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098?appnexus_banner_fixedbid=1&fixedbid=1\",\"ver\":\"1.0\",\"publisher\":{\"id\":\"5890\"}},\"device\":{\"ext\":{\"atts\":0},\"geo\":{\"type\":1,\"lat\":37.421998333333335,\"lon\":-122.08400000000002},\"pxratio\":2.625,\"mccmnc\":\"310-260\",\"lmt\":0,\"ifa\":\"07c387f2-e030-428f-8336-42f682150759\",\"connectiontype\":6,\"carrier\":\"Android\",\"js\":1,\"ua\":\"Mozilla/5.0(Linux;Android9;AndroidSDKbuiltforx86Build/PSR1.180720.075;wv)AppleWebKit/537.36(KHTML,likeGecko)Version/4.0Chrome/69.0.3497.100MobileSafari/537.36\",\"make\":\"Google\",\"model\":\"AndroidSDKbuiltforx86\",\"os\":\"Android\",\"osv\":\"9\",\"h\":1794,\"w\":1080,\"language\":\"en-US\",\"devicetype\":4},\"source\":{\"ext\":{\"omidpn\":\"PubMatic\",\"omidpv\":\"1.2.11-Pubmatic\"}},\"user\":{},\"ext\":{\"wrapper\":{\"ssauction\":0,\"sumry_disable\":0,\"profileid\":58135,\"versionid\":1,\"clientconfig\":1}}}"}]}],"ext":{"consent":"CP2KIMAP2KIgAEPgABBYJGNX_H__bX9j-Xr3"}}}`), }, - want: "", + wantProfile: "", + wantBody: []byte(`{"app":{"bundle":"com.pubmatic.openbid.app","name":"Sample","publisher":{"id":"156276"},"storeurl":"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098","ver":"1.0"},"at":1,"device":{"carrier":"MYTEL","connectiontype":2,"devicetype":4,"ext":{"atts":2},"geo":{"city":"Queens","country":"USA","dma":"501","ipservice":3,"lat":40.7429,"lon":-73.9392,"long":-73.9392,"metro":"501","region":"ny","type":2,"zip":"11101"},"h":2400,"hwv":"ruby","ifa":"497a10d6-c4dd-4e04-a986-c32b7180d462","ip":"38.158.207.171","js":1,"language":"en_US","make":"xiaomi","model":"22101316c","os":"android","osv":"13.0.0","ppi":440,"pxratio":2.75,"ua":"Mozilla/5.0 (Linux; Android 13; 22101316C Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.230 Mobile Safari/537.36","w":1080},"ext":{"prebid":{"bidderparams":{"pubmatic":{"wrapper":{}}}}},"id":"95d6643c-3da6-40a2-b9ca-12279393ffbf","imp":[{"banner":{"format":[{"w":728,"h":90},{"w":320,"h":50}],"api":[5,2],"w":700,"h":900},"clickbrowser":0,"displaymanager":"OpenBid_SDK","displaymanagerver":"1.4.0","ext":{"reward":1},"id":"imp176227948","secure":0,"tagid":"OpenWrapBidderBannerAdUnit"}],"regs":{"ext":{"gdpr":0}},"source":{"ext":{"omidpn":"PubMatic","omidpv":"1.2.11-Pubmatic"}},"user":{"data":[{"id":"1","name":"Publisher Passed","segment":[{"signal":"{\"id\":\"95d6643c-3da6-40a2-b9ca-12279393ffbf\",\"at\":1,\"tmax\":500,\"cur\":[\"USD\"],\"imp\":[{\"id\":\"imp176227948\",\"clickbrowser\":0,\"displaymanager\":\"PubMatic_OpenBid_SDK\",\"displaymanagerver\":\"1.4.0\",\"tagid\":\"/43743431/DMDemo\",\"secure\":0,\"banner\":{\"pos\":7,\"format\":[{\"w\":300,\"h\":250}],\"api\":[5,6,7]},\"instl\":1}],\"app\":{\"name\":\"OpenWrapperSample\",\"bundle\":\"com.pubmatic.openbid.app\",\"storeurl\":\"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098?appnexus_banner_fixedbid=1&fixedbid=1\",\"ver\":\"1.0\",\"publisher\":{\"id\":\"5890\"}},\"device\":{\"ext\":{\"atts\":0},\"geo\":{\"type\":1,\"lat\":37.421998333333335,\"lon\":-122.08400000000002},\"pxratio\":2.625,\"mccmnc\":\"310-260\",\"lmt\":0,\"ifa\":\"07c387f2-e030-428f-8336-42f682150759\",\"connectiontype\":6,\"carrier\":\"Android\",\"js\":1,\"ua\":\"Mozilla/5.0(Linux;Android9;AndroidSDKbuiltforx86Build/PSR1.180720.075;wv)AppleWebKit/537.36(KHTML,likeGecko)Version/4.0Chrome/69.0.3497.100MobileSafari/537.36\",\"make\":\"Google\",\"model\":\"AndroidSDKbuiltforx86\",\"os\":\"Android\",\"osv\":\"9\",\"h\":1794,\"w\":1080,\"language\":\"en-US\",\"devicetype\":4},\"source\":{\"ext\":{\"omidpn\":\"PubMatic\",\"omidpv\":\"1.2.11-Pubmatic\"}},\"user\":{},\"ext\":{\"wrapper\":{\"ssauction\":0,\"sumry_disable\":0,\"profileid\":58135,\"versionid\":1,\"clientconfig\":1}}}"}]}],"ext":{"consent":"CP2KIMAP2KIgAEPgABBYJGNX_H__bX9j-Xr3"}}}`), + }, + { + name: "profileID not present in request.ext.prebid.bidderparams.pubmatic.wrapper but invalid present in request.app.id", + args: args{ + requestBody: []byte(`{"app":{"bundle":"com.pubmatic.openbid.app","name":"Sample","publisher":{"id":"156276"},"id":"abcde","storeurl":"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098","ver":"1.0"},"at":1,"device":{"carrier":"MYTEL","connectiontype":2,"devicetype":4,"ext":{"atts":2},"geo":{"city":"Queens","country":"USA","dma":"501","ipservice":3,"lat":40.7429,"lon":-73.9392,"long":-73.9392,"metro":"501","region":"ny","type":2,"zip":"11101"},"h":2400,"hwv":"ruby","ifa":"497a10d6-c4dd-4e04-a986-c32b7180d462","ip":"38.158.207.171","js":1,"language":"en_US","make":"xiaomi","model":"22101316c","os":"android","osv":"13.0.0","ppi":440,"pxratio":2.75,"ua":"Mozilla/5.0 (Linux; Android 13; 22101316C Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.230 Mobile Safari/537.36","w":1080},"ext":{"prebid":{"bidderparams":{"pubmatic":{"wrapper":{}}}}},"id":"95d6643c-3da6-40a2-b9ca-12279393ffbf","imp":[{"banner":{"format":[{"w":728,"h":90},{"w":320,"h":50}],"api":[5,2],"w":700,"h":900},"clickbrowser":0,"displaymanager":"OpenBid_SDK","displaymanagerver":"1.4.0","ext":{"reward":1},"id":"imp176227948","secure":0,"tagid":"OpenWrapBidderBannerAdUnit"}],"regs":{"ext":{"gdpr":0}},"source":{"ext":{"omidpn":"PubMatic","omidpv":"1.2.11-Pubmatic"}},"user":{"data":[{"id":"1","name":"Publisher Passed","segment":[{"signal":"{\"id\":\"95d6643c-3da6-40a2-b9ca-12279393ffbf\",\"at\":1,\"tmax\":500,\"cur\":[\"USD\"],\"imp\":[{\"id\":\"imp176227948\",\"clickbrowser\":0,\"displaymanager\":\"PubMatic_OpenBid_SDK\",\"displaymanagerver\":\"1.4.0\",\"tagid\":\"/43743431/DMDemo\",\"secure\":0,\"banner\":{\"pos\":7,\"format\":[{\"w\":300,\"h\":250}],\"api\":[5,6,7]},\"instl\":1}],\"app\":{\"name\":\"OpenWrapperSample\",\"bundle\":\"com.pubmatic.openbid.app\",\"storeurl\":\"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098?appnexus_banner_fixedbid=1&fixedbid=1\",\"ver\":\"1.0\",\"publisher\":{\"id\":\"5890\"}},\"device\":{\"ext\":{\"atts\":0},\"geo\":{\"type\":1,\"lat\":37.421998333333335,\"lon\":-122.08400000000002},\"pxratio\":2.625,\"mccmnc\":\"310-260\",\"lmt\":0,\"ifa\":\"07c387f2-e030-428f-8336-42f682150759\",\"connectiontype\":6,\"carrier\":\"Android\",\"js\":1,\"ua\":\"Mozilla/5.0(Linux;Android9;AndroidSDKbuiltforx86Build/PSR1.180720.075;wv)AppleWebKit/537.36(KHTML,likeGecko)Version/4.0Chrome/69.0.3497.100MobileSafari/537.36\",\"make\":\"Google\",\"model\":\"AndroidSDKbuiltforx86\",\"os\":\"Android\",\"osv\":\"9\",\"h\":1794,\"w\":1080,\"language\":\"en-US\",\"devicetype\":4},\"source\":{\"ext\":{\"omidpn\":\"PubMatic\",\"omidpv\":\"1.2.11-Pubmatic\"}},\"user\":{},\"ext\":{\"wrapper\":{\"ssauction\":0,\"sumry_disable\":0,\"profileid\":58135,\"versionid\":1,\"clientconfig\":1}}}"}]}],"ext":{"consent":"CP2KIMAP2KIgAEPgABBYJGNX_H__bX9j-Xr3"}}}`), + }, + wantProfile: "", + wantBody: []byte(`{"app":{"bundle":"com.pubmatic.openbid.app","name":"Sample","publisher":{"id":"156276"},"id":"abcde","storeurl":"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098","ver":"1.0"},"at":1,"device":{"carrier":"MYTEL","connectiontype":2,"devicetype":4,"ext":{"atts":2},"geo":{"city":"Queens","country":"USA","dma":"501","ipservice":3,"lat":40.7429,"lon":-73.9392,"long":-73.9392,"metro":"501","region":"ny","type":2,"zip":"11101"},"h":2400,"hwv":"ruby","ifa":"497a10d6-c4dd-4e04-a986-c32b7180d462","ip":"38.158.207.171","js":1,"language":"en_US","make":"xiaomi","model":"22101316c","os":"android","osv":"13.0.0","ppi":440,"pxratio":2.75,"ua":"Mozilla/5.0 (Linux; Android 13; 22101316C Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.230 Mobile Safari/537.36","w":1080},"ext":{"prebid":{"bidderparams":{"pubmatic":{"wrapper":{}}}}},"id":"95d6643c-3da6-40a2-b9ca-12279393ffbf","imp":[{"banner":{"format":[{"w":728,"h":90},{"w":320,"h":50}],"api":[5,2],"w":700,"h":900},"clickbrowser":0,"displaymanager":"OpenBid_SDK","displaymanagerver":"1.4.0","ext":{"reward":1},"id":"imp176227948","secure":0,"tagid":"OpenWrapBidderBannerAdUnit"}],"regs":{"ext":{"gdpr":0}},"source":{"ext":{"omidpn":"PubMatic","omidpv":"1.2.11-Pubmatic"}},"user":{"data":[{"id":"1","name":"Publisher Passed","segment":[{"signal":"{\"id\":\"95d6643c-3da6-40a2-b9ca-12279393ffbf\",\"at\":1,\"tmax\":500,\"cur\":[\"USD\"],\"imp\":[{\"id\":\"imp176227948\",\"clickbrowser\":0,\"displaymanager\":\"PubMatic_OpenBid_SDK\",\"displaymanagerver\":\"1.4.0\",\"tagid\":\"/43743431/DMDemo\",\"secure\":0,\"banner\":{\"pos\":7,\"format\":[{\"w\":300,\"h\":250}],\"api\":[5,6,7]},\"instl\":1}],\"app\":{\"name\":\"OpenWrapperSample\",\"bundle\":\"com.pubmatic.openbid.app\",\"storeurl\":\"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098?appnexus_banner_fixedbid=1&fixedbid=1\",\"ver\":\"1.0\",\"publisher\":{\"id\":\"5890\"}},\"device\":{\"ext\":{\"atts\":0},\"geo\":{\"type\":1,\"lat\":37.421998333333335,\"lon\":-122.08400000000002},\"pxratio\":2.625,\"mccmnc\":\"310-260\",\"lmt\":0,\"ifa\":\"07c387f2-e030-428f-8336-42f682150759\",\"connectiontype\":6,\"carrier\":\"Android\",\"js\":1,\"ua\":\"Mozilla/5.0(Linux;Android9;AndroidSDKbuiltforx86Build/PSR1.180720.075;wv)AppleWebKit/537.36(KHTML,likeGecko)Version/4.0Chrome/69.0.3497.100MobileSafari/537.36\",\"make\":\"Google\",\"model\":\"AndroidSDKbuiltforx86\",\"os\":\"Android\",\"osv\":\"9\",\"h\":1794,\"w\":1080,\"language\":\"en-US\",\"devicetype\":4},\"source\":{\"ext\":{\"omidpn\":\"PubMatic\",\"omidpv\":\"1.2.11-Pubmatic\"}},\"user\":{},\"ext\":{\"wrapper\":{\"ssauction\":0,\"sumry_disable\":0,\"profileid\":58135,\"versionid\":1,\"clientconfig\":1}}}"}]}],"ext":{"consent":"CP2KIMAP2KIgAEPgABBYJGNX_H__bX9j-Xr3"}}}`), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := getProfileID(tt.args.requestBody) - assert.Equal(t, tt.want, got) + got, got1 := setProfileID(tt.args.requestBody) + assert.Equal(t, tt.wantBody, got) + assert.Equal(t, tt.wantProfile, got1) }) } } diff --git a/modules/pubmatic/openwrap/entrypointhook.go b/modules/pubmatic/openwrap/entrypointhook.go index d5b7eb6c2b0..dc447ffefb3 100644 --- a/modules/pubmatic/openwrap/entrypointhook.go +++ b/modules/pubmatic/openwrap/entrypointhook.go @@ -207,15 +207,7 @@ func GetRequestWrapper(payload hookstage.EntrypointPayload, result hookstage.Hoo case models.EndpointVideo, models.EndpointORTB, models.EndpointVAST, models.EndpointJson: requestExtWrapper, err = models.GetRequestExtWrapper(payload.Body, "ext", "wrapper") case models.EndpointAppLovinMax: - requestExtWrapper, err = models.GetRequestExtWrapper(payload.Body) - if requestExtWrapper.ProfileId == 0 { - profileIDStr := getProfileID(payload.Body) - if profileIDStr != "" { - if ProfileId, newErr := strconv.Atoi(profileIDStr); newErr == nil { - requestExtWrapper.ProfileId = ProfileId - } - } - } + fallthrough case models.EndpointWebS2S: fallthrough default: diff --git a/modules/pubmatic/openwrap/entrypointhook_test.go b/modules/pubmatic/openwrap/entrypointhook_test.go index 429d0fb3e4f..8921abcd24c 100644 --- a/modules/pubmatic/openwrap/entrypointhook_test.go +++ b/modules/pubmatic/openwrap/entrypointhook_test.go @@ -701,7 +701,7 @@ func TestGetRequestWrapper(t *testing.T) { name: "EndpointAppLovinMax", args: args{ payload: hookstage.EntrypointPayload{ - Body: []byte(`{"app":{"bundle":"com.pubmatic.openbid.app","name":"Sample","publisher":{"id":"156276"},"id":"13137","storeurl":"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098","ver":"1.0"},"at":1,"device":{"carrier":"MYTEL","connectiontype":2,"devicetype":4,"ext":{"atts":2},"geo":{"city":"Queens","country":"USA","dma":"501","ipservice":3,"lat":40.7429,"lon":-73.9392,"long":-73.9392,"metro":"501","region":"ny","type":2,"zip":"11101"},"h":2400,"hwv":"ruby","ifa":"497a10d6-c4dd-4e04-a986-c32b7180d462","ip":"38.158.207.171","js":1,"language":"en_US","make":"xiaomi","model":"22101316c","os":"android","osv":"13.0.0","ppi":440,"pxratio":2.75,"ua":"Mozilla/5.0 (Linux; Android 13; 22101316C Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.230 Mobile Safari/537.36","w":1080},"ext":{"prebid":{"bidderparams":{"pubmatic":{"wrapper":{}}}}},"id":"95d6643c-3da6-40a2-b9ca-12279393ffbf","imp":[{"banner":{"format":[{"w":728,"h":90},{"w":320,"h":50}],"api":[5,2],"w":700,"h":900},"clickbrowser":0,"displaymanager":"OpenBid_SDK","displaymanagerver":"1.4.0","ext":{"reward":1},"id":"imp176227948","secure":0,"tagid":"OpenWrapBidderBannerAdUnit"}],"regs":{"ext":{"gdpr":0}},"source":{"ext":{"omidpn":"PubMatic","omidpv":"1.2.11-Pubmatic"}},"user":{"data":[{"id":"1","name":"Publisher Passed","segment":[{"signal":"{\"id\":\"95d6643c-3da6-40a2-b9ca-12279393ffbf\",\"at\":1,\"tmax\":500,\"cur\":[\"USD\"],\"imp\":[{\"id\":\"imp176227948\",\"clickbrowser\":0,\"displaymanager\":\"PubMatic_OpenBid_SDK\",\"displaymanagerver\":\"1.4.0\",\"tagid\":\"/43743431/DMDemo\",\"secure\":0,\"banner\":{\"pos\":7,\"format\":[{\"w\":300,\"h\":250}],\"api\":[5,6,7]},\"instl\":1}],\"app\":{\"name\":\"OpenWrapperSample\",\"bundle\":\"com.pubmatic.openbid.app\",\"storeurl\":\"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098?appnexus_banner_fixedbid=1&fixedbid=1\",\"ver\":\"1.0\",\"publisher\":{\"id\":\"5890\"}},\"device\":{\"ext\":{\"atts\":0},\"geo\":{\"type\":1,\"lat\":37.421998333333335,\"lon\":-122.08400000000002},\"pxratio\":2.625,\"mccmnc\":\"310-260\",\"lmt\":0,\"ifa\":\"07c387f2-e030-428f-8336-42f682150759\",\"connectiontype\":6,\"carrier\":\"Android\",\"js\":1,\"ua\":\"Mozilla/5.0(Linux;Android9;AndroidSDKbuiltforx86Build/PSR1.180720.075;wv)AppleWebKit/537.36(KHTML,likeGecko)Version/4.0Chrome/69.0.3497.100MobileSafari/537.36\",\"make\":\"Google\",\"model\":\"AndroidSDKbuiltforx86\",\"os\":\"Android\",\"osv\":\"9\",\"h\":1794,\"w\":1080,\"language\":\"en-US\",\"devicetype\":4},\"source\":{\"ext\":{\"omidpn\":\"PubMatic\",\"omidpv\":\"1.2.11-Pubmatic\"}},\"user\":{},\"ext\":{\"wrapper\":{\"ssauction\":0,\"sumry_disable\":0,\"profileid\":58135,\"versionid\":1,\"clientconfig\":1}}}"}]}],"ext":{"consent":"CP2KIMAP2KIgAEPgABBYJGNX_H__bX9j-Xr3"}}}`), + Body: []byte(`{"app":{"bundle":"com.pubmatic.openbid.app","name":"Sample","publisher":{"id":"156276"},"id":"13137","storeurl":"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098","ver":"1.0"},"at":1,"device":{"carrier":"MYTEL","connectiontype":2,"devicetype":4,"ext":{"atts":2},"geo":{"city":"Queens","country":"USA","dma":"501","ipservice":3,"lat":40.7429,"lon":-73.9392,"long":-73.9392,"metro":"501","region":"ny","type":2,"zip":"11101"},"h":2400,"hwv":"ruby","ifa":"497a10d6-c4dd-4e04-a986-c32b7180d462","ip":"38.158.207.171","js":1,"language":"en_US","make":"xiaomi","model":"22101316c","os":"android","osv":"13.0.0","ppi":440,"pxratio":2.75,"ua":"Mozilla/5.0 (Linux; Android 13; 22101316C Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.230 Mobile Safari/537.36","w":1080},"ext":{"prebid":{"bidderparams":{"pubmatic":{"wrapper":{"profileid":13137}}}}},"id":"95d6643c-3da6-40a2-b9ca-12279393ffbf","imp":[{"banner":{"format":[{"w":728,"h":90},{"w":320,"h":50}],"api":[5,2],"w":700,"h":900},"clickbrowser":0,"displaymanager":"OpenBid_SDK","displaymanagerver":"1.4.0","ext":{"reward":1},"id":"imp176227948","secure":0,"tagid":"OpenWrapBidderBannerAdUnit"}],"regs":{"ext":{"gdpr":0}},"source":{"ext":{"omidpn":"PubMatic","omidpv":"1.2.11-Pubmatic"}},"user":{"data":[{"id":"1","name":"Publisher Passed","segment":[{"signal":"{\"id\":\"95d6643c-3da6-40a2-b9ca-12279393ffbf\",\"at\":1,\"tmax\":500,\"cur\":[\"USD\"],\"imp\":[{\"id\":\"imp176227948\",\"clickbrowser\":0,\"displaymanager\":\"PubMatic_OpenBid_SDK\",\"displaymanagerver\":\"1.4.0\",\"tagid\":\"/43743431/DMDemo\",\"secure\":0,\"banner\":{\"pos\":7,\"format\":[{\"w\":300,\"h\":250}],\"api\":[5,6,7]},\"instl\":1}],\"app\":{\"name\":\"OpenWrapperSample\",\"bundle\":\"com.pubmatic.openbid.app\",\"storeurl\":\"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098?appnexus_banner_fixedbid=1&fixedbid=1\",\"ver\":\"1.0\",\"publisher\":{\"id\":\"5890\"}},\"device\":{\"ext\":{\"atts\":0},\"geo\":{\"type\":1,\"lat\":37.421998333333335,\"lon\":-122.08400000000002},\"pxratio\":2.625,\"mccmnc\":\"310-260\",\"lmt\":0,\"ifa\":\"07c387f2-e030-428f-8336-42f682150759\",\"connectiontype\":6,\"carrier\":\"Android\",\"js\":1,\"ua\":\"Mozilla/5.0(Linux;Android9;AndroidSDKbuiltforx86Build/PSR1.180720.075;wv)AppleWebKit/537.36(KHTML,likeGecko)Version/4.0Chrome/69.0.3497.100MobileSafari/537.36\",\"make\":\"Google\",\"model\":\"AndroidSDKbuiltforx86\",\"os\":\"Android\",\"osv\":\"9\",\"h\":1794,\"w\":1080,\"language\":\"en-US\",\"devicetype\":4},\"source\":{\"ext\":{\"omidpn\":\"PubMatic\",\"omidpv\":\"1.2.11-Pubmatic\"}},\"user\":{},\"ext\":{\"wrapper\":{\"ssauction\":0,\"sumry_disable\":0,\"profileid\":58135,\"versionid\":1,\"clientconfig\":1}}}"}]}],"ext":{"consent":"CP2KIMAP2KIgAEPgABBYJGNX_H__bX9j-Xr3"}}}`), }, result: hookstage.HookResult[hookstage.EntrypointPayload]{}, endpoint: models.EndpointAppLovinMax, From 07dac81d77f83b072346ee3529ff1af65cd36604 Mon Sep 17 00:00:00 2001 From: pm-avinash-kapre <112699665+AvinashKapre@users.noreply.github.com> Date: Thu, 10 Oct 2024 13:03:57 +0530 Subject: [PATCH 5/9] UOE-11322: Forward displaymanager and displaymanagerver from app extension to pubmatic ssp (#935) * UOE-11322: Forward displaymanager and displaymanagerver from app extension to pubmatic ssp * test case name update * comment * review comment --- adapters/pubmatic/pubmatic.go | 31 +++- adapters/pubmatic/pubmatic_test.go | 244 ++++++++++++++++++++++++++--- 2 files changed, 255 insertions(+), 20 deletions(-) diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go index eea6babf34e..27ac67255ba 100644 --- a/adapters/pubmatic/pubmatic.go +++ b/adapters/pubmatic/pubmatic.go @@ -113,6 +113,11 @@ func (a *PubmaticAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ad extractWrapperExtFromImp := true extractPubIDFromImp := true + displayManager, displayManagerVer := "", "" + if request.App != nil && request.App.Ext != nil { + displayManager, displayManagerVer = getDisplayManagerAndVer(request.App) + } + newReqExt, cookies, err := extractPubmaticExtFromRequest(request) if err != nil { return nil, []error{err} @@ -125,7 +130,7 @@ func (a *PubmaticAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ad impFloorsMap := map[string][]float64{} for i := 0; i < len(request.Imp); i++ { - wrapperExtFromImp, pubIDFromImp, floors, err := parseImpressionObject(&request.Imp[i], extractWrapperExtFromImp, extractPubIDFromImp) + wrapperExtFromImp, pubIDFromImp, floors, err := parseImpressionObject(&request.Imp[i], extractWrapperExtFromImp, extractPubIDFromImp, displayManager, displayManagerVer) // If the parsing is failed, remove imp and add the error. if err != nil { errs = append(errs, err) @@ -383,7 +388,7 @@ func assignBannerWidthAndHeight(banner *openrtb2.Banner, w, h int64) *openrtb2.B } // parseImpressionObject parse the imp to get it ready to send to pubmatic -func parseImpressionObject(imp *openrtb2.Imp, extractWrapperExtFromImp, extractPubIDFromImp bool) (*pubmaticWrapperExt, string, []float64, error) { +func parseImpressionObject(imp *openrtb2.Imp, extractWrapperExtFromImp, extractPubIDFromImp bool, displayManager, displayManagerVer string) (*pubmaticWrapperExt, string, []float64, error) { var wrapExt *pubmaticWrapperExt var pubID string var floors []float64 @@ -397,6 +402,12 @@ func parseImpressionObject(imp *openrtb2.Imp, extractWrapperExtFromImp, extractP imp.Audio = nil } + // Populate imp.displaymanager and imp.displaymanagerver if the SDK failed to do it. + if imp.DisplayManager == "" && imp.DisplayManagerVer == "" && displayManager != "" && displayManagerVer != "" { + imp.DisplayManager = displayManager + imp.DisplayManagerVer = displayManagerVer + } + var bidderExt ExtImpBidderPubmatic if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return wrapExt, pubID, floors, err @@ -854,3 +865,19 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server co } return bidder, nil } + +// getDisplayManagerAndVer returns the display manager and version from the request.app.ext or request.app.prebid.ext source and version +func getDisplayManagerAndVer(app *openrtb2.App) (string, string) { + if source, err := jsonparser.GetString(app.Ext, openrtb_ext.PrebidExtKey, "source"); err == nil && source != "" { + if version, err := jsonparser.GetString(app.Ext, openrtb_ext.PrebidExtKey, "version"); err == nil && version != "" { + return source, version + } + } + + if source, err := jsonparser.GetString(app.Ext, "source"); err == nil && source != "" { + if version, err := jsonparser.GetString(app.Ext, "version"); err == nil && version != "" { + return source, version + } + } + return "", "" +} diff --git a/adapters/pubmatic/pubmatic_test.go b/adapters/pubmatic/pubmatic_test.go index 78d495ba983..981a8025dd2 100644 --- a/adapters/pubmatic/pubmatic_test.go +++ b/adapters/pubmatic/pubmatic_test.go @@ -81,15 +81,22 @@ func TestParseImpressionObject(t *testing.T) { imp *openrtb2.Imp extractWrapperExtFromImp bool extractPubIDFromImp bool + displayManager string + displayManagerVer string + } + type want struct { + bidfloor float64 + impExt json.RawMessage + displayManager string + displayManagerVer string } tests := []struct { name string args args expectedWrapperExt *pubmaticWrapperExt expectedPublisherId string + want want wantErr bool - expectedBidfloor float64 - expectedImpExt json.RawMessage }{ { name: "imp.bidfloor empty and kadfloor set", @@ -99,8 +106,10 @@ func TestParseImpressionObject(t *testing.T) { Ext: json.RawMessage(`{"bidder":{"kadfloor":"0.12"}}`), }, }, - expectedBidfloor: 0.12, - expectedImpExt: json.RawMessage(nil), + want: want{ + bidfloor: 0.12, + impExt: json.RawMessage(nil), + }, }, { name: "imp.bidfloor set and kadfloor empty", @@ -111,8 +120,10 @@ func TestParseImpressionObject(t *testing.T) { Ext: json.RawMessage(`{"bidder":{}}`), }, }, - expectedBidfloor: 0.12, - expectedImpExt: json.RawMessage(nil), + want: want{ + bidfloor: 0.12, + impExt: json.RawMessage(nil), + }, }, { name: "imp.bidfloor set and kadfloor invalid", @@ -123,8 +134,10 @@ func TestParseImpressionObject(t *testing.T) { Ext: json.RawMessage(`{"bidder":{"kadfloor":"aaa"}}`), }, }, - expectedBidfloor: 0.12, - expectedImpExt: json.RawMessage(nil), + want: want{ + bidfloor: 0.12, + impExt: json.RawMessage(nil), + }, }, { name: "imp.bidfloor set and kadfloor set, higher imp.bidfloor", @@ -135,7 +148,10 @@ func TestParseImpressionObject(t *testing.T) { Ext: json.RawMessage(`{"bidder":{"kadfloor":"0.11"}}`), }, }, - expectedBidfloor: 0.12, + want: want{ + bidfloor: 0.12, + impExt: json.RawMessage(nil), + }, }, { name: "imp.bidfloor set and kadfloor set, higher kadfloor", @@ -146,8 +162,10 @@ func TestParseImpressionObject(t *testing.T) { Ext: json.RawMessage(`{"bidder":{"kadfloor":"0.13"}}`), }, }, - expectedBidfloor: 0.13, - expectedImpExt: json.RawMessage(nil), + want: want{ + bidfloor: 0.13, + impExt: json.RawMessage(nil), + }, }, { name: "kadfloor string set with whitespace", @@ -158,8 +176,10 @@ func TestParseImpressionObject(t *testing.T) { Ext: json.RawMessage(`{"bidder":{"kadfloor":" \t 0.13 "}}`), }, }, - expectedBidfloor: 0.13, - expectedImpExt: json.RawMessage(nil), + want: want{ + bidfloor: 0.13, + impExt: json.RawMessage(nil), + }, }, { name: "bidViewability Object is set in imp.ext.prebid.pubmatic, pass to imp.ext", @@ -169,7 +189,9 @@ func TestParseImpressionObject(t *testing.T) { Ext: json.RawMessage(`{"bidder":{"bidViewability":{"adSizes":{"728x90":{"createdAt":1679993940011,"rendered":20,"totalViewTime":424413,"viewed":17}},"adUnit":{"createdAt":1679993940011,"rendered":25,"totalViewTime":424413,"viewed":17}}}}`), }, }, - expectedImpExt: json.RawMessage(`{"bidViewability":{"adSizes":{"728x90":{"createdAt":1679993940011,"rendered":20,"totalViewTime":424413,"viewed":17}},"adUnit":{"createdAt":1679993940011,"rendered":25,"totalViewTime":424413,"viewed":17}}}`), + want: want{ + impExt: json.RawMessage(`{"bidViewability":{"adSizes":{"728x90":{"createdAt":1679993940011,"rendered":20,"totalViewTime":424413,"viewed":17}},"adUnit":{"createdAt":1679993940011,"rendered":25,"totalViewTime":424413,"viewed":17}}}`), + }, }, { name: "sendburl set in imp.ext.prebid.pubmatic, pass to imp.ext", @@ -179,17 +201,74 @@ func TestParseImpressionObject(t *testing.T) { Ext: json.RawMessage(`{"bidder":{"sendburl":true}}`), }, }, - expectedImpExt: json.RawMessage(`{"sendburl":true}`), + want: want{ + impExt: json.RawMessage(`{"sendburl":true}`), + }, + }, + { + name: "Populate imp.displaymanager and imp.displaymanagerver if both are empty in imp", + args: args{ + imp: &openrtb2.Imp{ + Video: &openrtb2.Video{}, + Ext: json.RawMessage(`{"bidder":{"kadfloor":"0.12"}}`), + }, + displayManager: "prebid-mobile", + displayManagerVer: "1.0.0", + }, + want: want{ + bidfloor: 0.12, + impExt: json.RawMessage(nil), + displayManager: "prebid-mobile", + displayManagerVer: "1.0.0", + }, + }, + { + name: "do not populate imp.displaymanager and imp.displaymanagerver in imp if only displaymanager or displaymanagerver is present in args", + args: args{ + imp: &openrtb2.Imp{ + Video: &openrtb2.Video{}, + Ext: json.RawMessage(`{"bidder":{"kadfloor":"0.12"}}`), + DisplayManagerVer: "1.0.0", + }, + displayManager: "prebid-mobile", + displayManagerVer: "1.0.0", + }, + want: want{ + bidfloor: 0.12, + impExt: json.RawMessage(nil), + displayManagerVer: "1.0.0", + }, + }, + { + name: "do not populate imp.displaymanager and imp.displaymanagerver if already present in imp", + args: args{ + imp: &openrtb2.Imp{ + Video: &openrtb2.Video{}, + Ext: json.RawMessage(`{"bidder":{"kadfloor":"0.12"}}`), + DisplayManager: "prebid-mobile", + DisplayManagerVer: "1.0.0", + }, + displayManager: "prebid-android", + displayManagerVer: "2.0.0", + }, + want: want{ + bidfloor: 0.12, + impExt: json.RawMessage(nil), + displayManager: "prebid-mobile", + displayManagerVer: "1.0.0", + }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - receivedWrapperExt, receivedPublisherId, _, err := parseImpressionObject(tt.args.imp, tt.args.extractWrapperExtFromImp, tt.args.extractPubIDFromImp) + receivedWrapperExt, receivedPublisherId, _, err := parseImpressionObject(tt.args.imp, tt.args.extractWrapperExtFromImp, tt.args.extractPubIDFromImp, tt.args.displayManager, tt.args.displayManagerVer) assert.Equal(t, tt.wantErr, err != nil) assert.Equal(t, tt.expectedWrapperExt, receivedWrapperExt) assert.Equal(t, tt.expectedPublisherId, receivedPublisherId) - assert.Equal(t, tt.expectedBidfloor, tt.args.imp.BidFloor) - assert.Equal(t, tt.expectedImpExt, tt.args.imp.Ext) + assert.Equal(t, tt.want.bidfloor, tt.args.imp.BidFloor) + assert.Equal(t, tt.want.impExt, tt.args.imp.Ext) + assert.Equal(t, tt.want.displayManager, tt.args.imp.DisplayManager) + assert.Equal(t, tt.want.displayManagerVer, tt.args.imp.DisplayManagerVer) }) } } @@ -1499,3 +1578,132 @@ func TestTrimSuffixWithPattern(t *testing.T) { }) } } + +func TestGetDisplayManagerAndVer(t *testing.T) { + type args struct { + app *openrtb2.App + } + type want struct { + displayManager string + displayManagerVer string + } + tests := []struct { + name string + args args + want want + }{ + { + name: "request app object is not nil but app.ext has no source and version", + args: args{ + + app: &openrtb2.App{ + Name: "AutoScout24", + Ext: json.RawMessage(`{}`), + }, + }, + want: want{ + displayManager: "", + displayManagerVer: "", + }, + }, + { + name: "request app object is not nil and app.ext has source and version", + args: args{ + + app: &openrtb2.App{ + Name: "AutoScout24", + Ext: json.RawMessage(`{"source":"prebid-mobile","version":"1.0.0"}`), + }, + }, + want: want{ + displayManager: "prebid-mobile", + displayManagerVer: "1.0.0", + }, + }, + { + name: "request app object is not nil and app.ext.prebid has source and version", + args: args{ + app: &openrtb2.App{ + Name: "AutoScout24", + Ext: json.RawMessage(`{"prebid":{"source":"prebid-mobile","version":"1.0.0"}}`), + }, + }, + want: want{ + displayManager: "prebid-mobile", + displayManagerVer: "1.0.0", + }, + }, + { + name: "request app object is not nil and app.ext has only version", + args: args{ + app: &openrtb2.App{ + Name: "AutoScout24", + Ext: json.RawMessage(`{"version":"1.0.0"}`), + }, + }, + want: want{ + displayManager: "", + displayManagerVer: "", + }, + }, + { + name: "request app object is not nil and app.ext has only source", + args: args{ + app: &openrtb2.App{ + Name: "AutoScout24", + Ext: json.RawMessage(`{"source":"prebid-mobile"}`), + }, + }, + want: want{ + displayManager: "", + displayManagerVer: "", + }, + }, + { + name: "request app object is not nil and app.ext have empty source but version is present", + args: args{ + app: &openrtb2.App{ + Name: "AutoScout24", + Ext: json.RawMessage(`{"source":"", "version":"1.0.0"}`), + }, + }, + want: want{ + displayManager: "", + displayManagerVer: "", + }, + }, + { + name: "request app object is not nil and app.ext have empty version but source is present", + args: args{ + app: &openrtb2.App{ + Name: "AutoScout24", + Ext: json.RawMessage(`{"source":"prebid-mobile", "version":""}`), + }, + }, + want: want{ + displayManager: "", + displayManagerVer: "", + }, + }, + { + name: "request app object is not nil and both app.ext and app.ext.prebid have source and version", + args: args{ + app: &openrtb2.App{ + Name: "AutoScout24", + Ext: json.RawMessage(`{"source":"prebid-mobile-android","version":"2.0.0","prebid":{"source":"prebid-mobile","version":"1.0.0"}}`), + }, + }, + want: want{ + displayManager: "prebid-mobile", + displayManagerVer: "1.0.0", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + displayManager, displayManagerVer := getDisplayManagerAndVer(tt.args.app) + assert.Equal(t, tt.want.displayManager, displayManager) + assert.Equal(t, tt.want.displayManagerVer, displayManagerVer) + }) + } +} From dc19862b219e6e3a5c5a4785c0997ddfdfc58821 Mon Sep 17 00:00:00 2001 From: pm-avinash-kapre <112699665+AvinashKapre@users.noreply.github.com> Date: Fri, 11 Oct 2024 16:07:22 +0530 Subject: [PATCH 6/9] UOE-11355: bug fix- AppLovinMax: sendburl is getting passed at imp.ext instead of req.ext (#940) --- adapters/pubmatic/pubmatic.go | 8 ++-- adapters/pubmatic/pubmatic_test.go | 48 ++++++++++++++----- .../pubmatic/openwrap/beforevalidationhook.go | 10 ++-- .../openwrap/beforevalidationhook_test.go | 36 +++++++++++++- .../openwrap/bidderparams/pubmatic.go | 1 - .../openwrap/bidderparams/pubmatic_test.go | 3 +- modules/pubmatic/openwrap/models/constants.go | 1 + openrtb_ext/imp_pubmatic.go | 1 - 8 files changed, 84 insertions(+), 24 deletions(-) diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go index 27ac67255ba..84eec48e74a 100644 --- a/adapters/pubmatic/pubmatic.go +++ b/adapters/pubmatic/pubmatic.go @@ -88,6 +88,7 @@ type extRequestAdServer struct { Wrapper *pubmaticWrapperExt `json:"wrapper,omitempty"` Acat []string `json:"acat,omitempty"` Marketplace *marketplaceReqExt `json:"marketplace,omitempty"` + SendBurl bool `json:"sendburl,omitempty"` openrtb_ext.ExtRequest } @@ -462,10 +463,6 @@ func parseImpressionObject(imp *openrtb2.Imp, extractWrapperExtFromImp, extractP extMap[pmZoneIDKeyName] = pubmaticExt.PmZoneID } - if pubmaticExt.SendBurl { - extMap[sendBurlKey] = pubmaticExt.SendBurl - } - if bidderExt.SKAdnetwork != nil { extMap[skAdnetworkKey] = bidderExt.SKAdnetwork } @@ -571,6 +568,9 @@ func extractPubmaticExtFromRequest(request *openrtb2.BidRequest) (extRequestAdSe if wrapperObj, present := reqExtBidderParams["Cookie"]; present && len(wrapperObj) != 0 { err = json.Unmarshal(wrapperObj, &cookies) } + if sendBurl, ok := reqExtBidderParams[sendBurlKey]; ok { + pmReqExt.SendBurl, _ = strconv.ParseBool(string(sendBurl)) + } // OW patch -end- return pmReqExt, cookies, nil diff --git a/adapters/pubmatic/pubmatic_test.go b/adapters/pubmatic/pubmatic_test.go index 981a8025dd2..984458017e4 100644 --- a/adapters/pubmatic/pubmatic_test.go +++ b/adapters/pubmatic/pubmatic_test.go @@ -193,18 +193,6 @@ func TestParseImpressionObject(t *testing.T) { impExt: json.RawMessage(`{"bidViewability":{"adSizes":{"728x90":{"createdAt":1679993940011,"rendered":20,"totalViewTime":424413,"viewed":17}},"adUnit":{"createdAt":1679993940011,"rendered":25,"totalViewTime":424413,"viewed":17}}}`), }, }, - { - name: "sendburl set in imp.ext.prebid.pubmatic, pass to imp.ext", - args: args{ - imp: &openrtb2.Imp{ - Video: &openrtb2.Video{}, - Ext: json.RawMessage(`{"bidder":{"sendburl":true}}`), - }, - }, - want: want{ - impExt: json.RawMessage(`{"sendburl":true}`), - }, - }, { name: "Populate imp.displaymanager and imp.displaymanagerver if both are empty in imp", args: args{ @@ -394,6 +382,42 @@ func TestExtractPubmaticExtFromRequest(t *testing.T) { }, wantErr: false, }, + { + name: "valid wrapper object and senburl true in bidderparams", + args: args{ + request: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"bidderparams":{"wrapper":{"profile":123,"version":456},"sendburl":true}}}`), + }, + }, + expectedReqExt: extRequestAdServer{ + Wrapper: &pubmaticWrapperExt{ProfileID: 123, VersionID: 456}, + ExtRequest: openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + BidderParams: json.RawMessage(`{"wrapper":{"profile":123,"version":456},"sendburl":true}`), + }, + }, + SendBurl: true, + }, + wantErr: false, + }, + { + name: "valid wrapper object and invalid senburl true in bidderparams", + args: args{ + request: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"bidderparams":{"wrapper":{"profile":123,"version":456},"sendburl":{}}}}`), + }, + }, + expectedReqExt: extRequestAdServer{ + Wrapper: &pubmaticWrapperExt{ProfileID: 123, VersionID: 456}, + ExtRequest: openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + BidderParams: json.RawMessage(`{"wrapper":{"profile":123,"version":456},"sendburl":{}}`), + }, + }, + SendBurl: false, + }, + wantErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/modules/pubmatic/openwrap/beforevalidationhook.go b/modules/pubmatic/openwrap/beforevalidationhook.go index 9a4a70be69e..aa24a6d6c0f 100644 --- a/modules/pubmatic/openwrap/beforevalidationhook.go +++ b/modules/pubmatic/openwrap/beforevalidationhook.go @@ -653,13 +653,13 @@ func (m OpenWrap) handleBeforeValidationHook( requestExt.Prebid.AliasGVLIDs = aliasgvlids if _, ok := rCtx.AdapterThrottleMap[string(openrtb_ext.BidderPubmatic)]; !ok { - requestExt.Prebid.BidderParams, _ = updateRequestExtBidderParamsPubmatic(requestExt.Prebid.BidderParams, rCtx.Cookies, rCtx.LoggerImpressionID, string(openrtb_ext.BidderPubmatic)) + requestExt.Prebid.BidderParams, _ = updateRequestExtBidderParamsPubmatic(requestExt.Prebid.BidderParams, rCtx.Cookies, rCtx.LoggerImpressionID, string(openrtb_ext.BidderPubmatic), rCtx.SendBurl) } for bidderCode, coreBidder := range rCtx.Aliases { if coreBidder == string(openrtb_ext.BidderPubmatic) { if _, ok := rCtx.AdapterThrottleMap[bidderCode]; !ok { - requestExt.Prebid.BidderParams, _ = updateRequestExtBidderParamsPubmatic(requestExt.Prebid.BidderParams, rCtx.Cookies, rCtx.LoggerImpressionID, bidderCode) + requestExt.Prebid.BidderParams, _ = updateRequestExtBidderParamsPubmatic(requestExt.Prebid.BidderParams, rCtx.Cookies, rCtx.LoggerImpressionID, bidderCode, rCtx.SendBurl) } } } @@ -966,7 +966,7 @@ func getDomainFromUrl(pageUrl string) string { // } // NYC: make this generic. Do we need this?. PBS now has auto_gen_source_tid generator. We can make it to wiid for pubmatic adapter in pubmatic.go -func updateRequestExtBidderParamsPubmatic(bidderParams json.RawMessage, cookie, loggerID, bidderCode string) (json.RawMessage, error) { +func updateRequestExtBidderParamsPubmatic(bidderParams json.RawMessage, cookie, loggerID, bidderCode string, sendBurl bool) (json.RawMessage, error) { bidderParamsMap := make(map[string]map[string]interface{}) _ = json.Unmarshal(bidderParams, &bidderParamsMap) // ignore error, incoming might be nil for now but we still have data to put @@ -978,6 +978,10 @@ func updateRequestExtBidderParamsPubmatic(bidderParams json.RawMessage, cookie, bidderParamsMap[bidderCode][models.COOKIE] = cookie } + if sendBurl { + bidderParamsMap[bidderCode][models.SendBurl] = true + } + return json.Marshal(bidderParamsMap) } diff --git a/modules/pubmatic/openwrap/beforevalidationhook_test.go b/modules/pubmatic/openwrap/beforevalidationhook_test.go index 1d683f2aaf3..24412c402e6 100644 --- a/modules/pubmatic/openwrap/beforevalidationhook_test.go +++ b/modules/pubmatic/openwrap/beforevalidationhook_test.go @@ -3019,6 +3019,7 @@ func TestUpdateRequestExtBidderParamsPubmatic(t *testing.T) { cookie string loggerID string bidderCode string + sendBurl bool } tests := []struct { name string @@ -3065,10 +3066,43 @@ func TestUpdateRequestExtBidderParamsPubmatic(t *testing.T) { }, want: json.RawMessage(`{"pubmatic":{"Cookie":"test_cookie","wiid":"b441a46e-8c1f-428b-9c29-44e2a408a954"}}`), }, + { + name: "sendburl is true and both cookie and loggerID are present", + args: args{ + bidderParams: json.RawMessage(`{"pubmatic":{"pmzoneid":"zone1","adSlot":"38519891"}}`), + cookie: "test_cookie", + loggerID: "b441a46e-8c1f-428b-9c29-44e2a408a954", + bidderCode: "pubmatic", + sendBurl: true, + }, + want: json.RawMessage(`{"pubmatic":{"Cookie":"test_cookie","sendburl":true,"wiid":"b441a46e-8c1f-428b-9c29-44e2a408a954"}}`), + }, + { + name: "sendburl is true and both cookie and loggerID are empty", + args: args{ + bidderParams: json.RawMessage(`{"pubmatic":{"pmzoneid":"zone1","adSlot":"38519891"}}`), + cookie: "", + loggerID: "", + bidderCode: "pubmatic", + sendBurl: true, + }, + want: json.RawMessage(`{"pubmatic":{"sendburl":true,"wiid":""}}`), + }, + { + name: "sendburl is false and both cookie and loggerID are present", + args: args{ + bidderParams: json.RawMessage(`{"pubmatic":{"pmzoneid":"zone1","adSlot":"38519891"}}`), + cookie: "test_cookie", + loggerID: "b441a46e-8c1f-428b-9c29-44e2a408a954", + bidderCode: "pubmatic", + sendBurl: false, + }, + want: json.RawMessage(`{"pubmatic":{"Cookie":"test_cookie","wiid":"b441a46e-8c1f-428b-9c29-44e2a408a954"}}`), + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := updateRequestExtBidderParamsPubmatic(tt.args.bidderParams, tt.args.cookie, tt.args.loggerID, tt.args.bidderCode) + got, err := updateRequestExtBidderParamsPubmatic(tt.args.bidderParams, tt.args.cookie, tt.args.loggerID, tt.args.bidderCode, tt.args.sendBurl) if (err != nil) != tt.wantErr { assert.Equal(t, tt.wantErr, err != nil) return diff --git a/modules/pubmatic/openwrap/bidderparams/pubmatic.go b/modules/pubmatic/openwrap/bidderparams/pubmatic.go index e1583092c34..57e8a0a0741 100644 --- a/modules/pubmatic/openwrap/bidderparams/pubmatic.go +++ b/modules/pubmatic/openwrap/bidderparams/pubmatic.go @@ -16,7 +16,6 @@ func PreparePubMaticParamsV25(rctx models.RequestCtx, cache cache.Cache, bidRequ WrapExt: getPubMaticWrapperExt(rctx, partnerID), Keywords: getImpExtPubMaticKeyWords(impExt, rctx.PartnerConfigMap[partnerID][models.BidderCode]), Floors: getApplovinBidFloors(rctx, imp), - SendBurl: rctx.SendBurl, } slots, slotMap, slotMappingInfo, _ := getSlotMeta(rctx, cache, bidRequest, imp, impExt, partnerID) diff --git a/modules/pubmatic/openwrap/bidderparams/pubmatic_test.go b/modules/pubmatic/openwrap/bidderparams/pubmatic_test.go index 14b004fd4f1..4ab88fd2fa5 100644 --- a/modules/pubmatic/openwrap/bidderparams/pubmatic_test.go +++ b/modules/pubmatic/openwrap/bidderparams/pubmatic_test.go @@ -1609,7 +1609,6 @@ func TestPreparePubMaticParamsV25(t *testing.T) { }, }, }, - SendBurl: true, }, cache: mockCache, impExt: models.ImpExtension{ @@ -1655,7 +1654,7 @@ func TestPreparePubMaticParamsV25(t *testing.T) { matchedSlot: "/Test_Adunit1234@Div1@200x300", matchedPattern: "", isRegexSlot: false, - params: []byte(`{"publisherId":"5890","adSlot":"/Test_Adunit1234@DIV1@200x300","wrapper":{"version":1,"profile":123},"keywords":[{"key":"test_key1","value":["test_value1","test_value2"]},{"key":"test_key2","value":["test_value1","test_value2"]}],"floors":[1.5,1.2,2.2],"sendburl":true}`), + params: []byte(`{"publisherId":"5890","adSlot":"/Test_Adunit1234@DIV1@200x300","wrapper":{"version":1,"profile":123},"keywords":[{"key":"test_key1","value":["test_value1","test_value2"]},{"key":"test_key2","value":["test_value1","test_value2"]}],"floors":[1.5,1.2,2.2]}`), wantErr: false, }, }, diff --git a/modules/pubmatic/openwrap/models/constants.go b/modules/pubmatic/openwrap/models/constants.go index 7b49843fff7..02812466bea 100755 --- a/modules/pubmatic/openwrap/models/constants.go +++ b/modules/pubmatic/openwrap/models/constants.go @@ -611,6 +611,7 @@ const ( MissingSignal = "missing" InvalidSignal = "invalid" AppStoreUrl = "appStoreUrl" + SendBurl = "sendburl" ) // constants for log level diff --git a/openrtb_ext/imp_pubmatic.go b/openrtb_ext/imp_pubmatic.go index f6c4b74f2bd..28891b959ee 100644 --- a/openrtb_ext/imp_pubmatic.go +++ b/openrtb_ext/imp_pubmatic.go @@ -19,7 +19,6 @@ type ExtImpPubmatic struct { BidViewabilityScore map[string]interface{} `json:"bidViewability,omitempty"` DealTier *DealTier `json:"dealtier,omitempty"` Floors []float64 `json:"floors,omitempty"` - SendBurl bool `json:"sendburl,omitempty"` } // ExtImpPubmaticKeyVal defines the contract for bidrequest.imp[i].ext.prebid.bidder.pubmatic.keywords[i] From ff936e288c1051213ee8629bb47dfe0987b8a21e Mon Sep 17 00:00:00 2001 From: pm-priyanka-bagade <156899734+pm-priyanka-bagade@users.noreply.github.com> Date: Mon, 14 Oct 2024 18:15:11 +0530 Subject: [PATCH 7/9] UOE-11310: ApplovinMax Signal data not visible in the wakanda logs (#936) --- analytics/pubmatic/helper.go | 2 +- analytics/pubmatic/pubmatic.go | 10 + analytics/pubmatic/pubmatic_test.go | 203 +++++++++++++++++- endpoints/openrtb2/auction_ow.go | 22 +- modules/pubmatic/openwrap/entrypointhook.go | 4 +- .../pubmatic/openwrap/entrypointhook_test.go | 110 +++++++++- modules/pubmatic/openwrap/wakanda/wakanda.go | 12 ++ 7 files changed, 346 insertions(+), 17 deletions(-) diff --git a/analytics/pubmatic/helper.go b/analytics/pubmatic/helper.go index a0bc446b1c2..f725d9980b0 100644 --- a/analytics/pubmatic/helper.go +++ b/analytics/pubmatic/helper.go @@ -51,7 +51,7 @@ func getGdprEnabledFlag(partnerConfigMap map[int]map[string]string) int { } // send function will send the owlogger to analytics endpoint -func send(rCtx *models.RequestCtx, url string, headers http.Header, mhc mhttp.MultiHttpContextInterface) { +var send = func(rCtx *models.RequestCtx, url string, headers http.Header, mhc mhttp.MultiHttpContextInterface) { startTime := time.Now() hc, _ := mhttp.NewHttpCall(url, "") diff --git a/analytics/pubmatic/pubmatic.go b/analytics/pubmatic/pubmatic.go index 5e5e8a11a7d..3b6f20ce34d 100644 --- a/analytics/pubmatic/pubmatic.go +++ b/analytics/pubmatic/pubmatic.go @@ -5,6 +5,7 @@ import ( "sync" "github.com/golang/glog" + "github.com/prebid/openrtb/v20/openrtb2" "github.com/prebid/prebid-server/v2/analytics" "github.com/prebid/prebid-server/v2/analytics/pubmatic/mhttp" @@ -59,6 +60,12 @@ func (ow HTTPLogger) LogAuctionObject(ao *analytics.AuctionObject) { return } + var orignalMaxBidResponse *openrtb2.BidResponse + if rCtx.Endpoint == models.EndpointAppLovinMax { + orignalMaxBidResponse = new(openrtb2.BidResponse) + *orignalMaxBidResponse = *ao.Response + } + err := RestoreBidResponse(rCtx, *ao) if err != nil { glog.Error("Failed to restore bid response for pub:[%d], profile:[%d], version:[%d], err:[%s].", rCtx.PubID, rCtx.ProfileID, rCtx.VersionID, err.Error()) @@ -73,6 +80,9 @@ func (ow HTTPLogger) LogAuctionObject(ao *analytics.AuctionObject) { go send(rCtx, loggerURL, headers, mhttp.NewMultiHttpContext()) + if rCtx.Endpoint == models.EndpointAppLovinMax { + ao.Response = orignalMaxBidResponse + } setWakandaObject(rCtx, ao, loggerURL) } diff --git a/analytics/pubmatic/pubmatic_test.go b/analytics/pubmatic/pubmatic_test.go index 186c684e8e6..4ee47bf0615 100644 --- a/analytics/pubmatic/pubmatic_test.go +++ b/analytics/pubmatic/pubmatic_test.go @@ -2,14 +2,18 @@ package pubmatic import ( "encoding/json" + "net/http" "testing" "github.com/prebid/openrtb/v20/openrtb2" "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/analytics/pubmatic/mhttp" "github.com/prebid/prebid-server/v2/config" "github.com/prebid/prebid-server/v2/hooks/hookanalytics" "github.com/prebid/prebid-server/v2/hooks/hookexecution" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/wakanda" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -36,10 +40,16 @@ func TestNewHTTPLogger(t *testing.T) { // TestLogAuctionObject just increases code coverage, it does not validate anything func TestLogAuctionObject(t *testing.T) { + oldSend := send + send = func(rCtx *models.RequestCtx, url string, headers http.Header, mhc mhttp.MultiHttpContextInterface) {} + defer func() { + send = oldSend + }() tests := []struct { name string ao *analytics.AuctionObject RestoredResponse *openrtb2.BidResponse + wantWakanda wakanda.WakandaDebug }{ { name: "rctx is nil", @@ -106,7 +116,6 @@ func TestLogAuctionObject(t *testing.T) { }, }, }, - Response: &openrtb2.BidResponse{ ID: "123", BidID: "bid-id-1", @@ -175,9 +184,201 @@ func TestLogAuctionObject(t *testing.T) { }, }, }, + { + name: "AppLovinMax request, RestoreBidResponse for logger and wakanda enable", + ao: &analytics.AuctionObject{ + HookExecutionOutcome: []hookexecution.StageOutcome{ + { + Groups: []hookexecution.GroupOutcome{ + { + InvocationResults: []hookexecution.HookOutcome{ + { + AnalyticsTags: hookanalytics.Analytics{ + Activities: []hookanalytics.Activity{ + { + Results: []hookanalytics.Result{ + { + Values: map[string]interface{}{ + "request-ctx": &models.RequestCtx{ + Endpoint: models.EndpointAppLovinMax, + Debug: false, + PubID: 5890, + PubIDStr: "5890", + ProfileID: 1234, + ProfileIDStr: "1234", + WakandaDebug: &wakanda.Debug{ + Enabled: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + Response: &openrtb2.BidResponse{ + ID: "123", + BidID: "bid-id-1", + Cur: "USD", + SeatBid: []openrtb2.SeatBid{ + { + Seat: "pubmatic", + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp_1", + Ext: json.RawMessage(`{"signaldata":"{\"id\":\"123\",\"seatbid\":[{\"bid\":[{\"id\":\"bid-id-1\",\"impid\":\"imp_1\",\"price\":0}],\"seat\":\"pubmatic\"}],\"bidid\":\"bid-id-1\",\"cur\":\"USD\",\"ext\":{\"matchedimpression\":{\"appnexus\":50,\"pubmatic\":50}}}\r\n"}`), + }, + }, + }, + }, + }, + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + }, + RestoredResponse: &openrtb2.BidResponse{ + ID: "123", + BidID: "bid-id-1", + Cur: "USD", + SeatBid: []openrtb2.SeatBid{ + { + Seat: "pubmatic", + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp_1", + Ext: json.RawMessage(`{"signaldata":"{\"id\":\"123\",\"seatbid\":[{\"bid\":[{\"id\":\"bid-id-1\",\"impid\":\"imp_1\",\"price\":0}],\"seat\":\"pubmatic\"}],\"bidid\":\"bid-id-1\",\"cur\":\"USD\",\"ext\":{\"matchedimpression\":{\"appnexus\":50,\"pubmatic\":50}}}\r\n"}`), + }, + }, + }, + }, + }, + wantWakanda: &wakanda.Debug{ + Enabled: true, + FolderPaths: nil, + DebugLevel: 0, + DebugData: wakanda.DebugData{ + HTTPRequest: nil, + HTTPRequestBody: nil, + HTTPResponse: nil, + HTTPResponseBody: "{\"id\":\"123\",\"seatbid\":[{\"bid\":[{\"id\":\"bid-id-1\",\"impid\":\"imp_1\",\"price\":0,\"ext\":{\"signaldata\":\"{\\\"id\\\":\\\"123\\\",\\\"seatbid\\\":[{\\\"bid\\\":[{\\\"id\\\":\\\"bid-id-1\\\",\\\"impid\\\":\\\"imp_1\\\",\\\"price\\\":0}],\\\"seat\\\":\\\"pubmatic\\\"}],\\\"bidid\\\":\\\"bid-id-1\\\",\\\"cur\\\":\\\"USD\\\",\\\"ext\\\":{\\\"matchedimpression\\\":{\\\"appnexus\\\":50,\\\"pubmatic\\\":50}}}\\r\\n\"}}],\"seat\":\"pubmatic\"}],\"bidid\":\"bid-id-1\",\"cur\":\"USD\"}", + PrebidHTTPRequest: nil, + PrebidRequestBody: nil, + PrebidHTTPResponse: nil, + OpenRTB: &openrtb2.BidRequest{}, + WinningBid: false, + Logger: json.RawMessage(`{"pubid":5890,"pid":"1234","pdvid":"0","sl":1,"dvc":{},"ft":0,"it":"sdk"}`), + }, + Config: wakanda.Wakanda{ + SFTP: wakanda.SFTP{ + User: "", + Password: "", + ServerIP: "", + Destination: "", + }, + HostName: "", + DCName: "", + PodName: "", + MaxDurationInMin: 0, + CleanupFrequencyInMin: 0, + }, + }, + }, + { + name: "AppLovinMax request, RestoreBidResponse for logger and wakanda disable", + ao: &analytics.AuctionObject{ + HookExecutionOutcome: []hookexecution.StageOutcome{ + { + Groups: []hookexecution.GroupOutcome{ + { + InvocationResults: []hookexecution.HookOutcome{ + { + AnalyticsTags: hookanalytics.Analytics{ + Activities: []hookanalytics.Activity{ + { + Results: []hookanalytics.Result{ + { + Values: map[string]interface{}{ + "request-ctx": &models.RequestCtx{ + Endpoint: models.EndpointAppLovinMax, + Debug: false, + PubID: 5890, + PubIDStr: "5890", + ProfileID: 1234, + ProfileIDStr: "1234", + WakandaDebug: &wakanda.Debug{ + Enabled: false, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + Response: &openrtb2.BidResponse{ + ID: "123", + BidID: "bid-id-1", + Cur: "USD", + SeatBid: []openrtb2.SeatBid{ + { + Seat: "pubmatic", + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp_1", + Ext: json.RawMessage(`{"signaldata":"{\"id\":\"123\",\"seatbid\":[{\"bid\":[{\"id\":\"bid-id-1\",\"impid\":\"imp_1\",\"price\":0}],\"seat\":\"pubmatic\"}],\"bidid\":\"bid-id-1\",\"cur\":\"USD\",\"ext\":{\"matchedimpression\":{\"appnexus\":50,\"pubmatic\":50}}}\r\n"}`), + }, + }, + }, + }, + }, + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + }, + RestoredResponse: &openrtb2.BidResponse{ + ID: "123", + BidID: "bid-id-1", + Cur: "USD", + SeatBid: []openrtb2.SeatBid{ + { + Seat: "pubmatic", + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp_1", + Ext: json.RawMessage(`{"signaldata":"{\"id\":\"123\",\"seatbid\":[{\"bid\":[{\"id\":\"bid-id-1\",\"impid\":\"imp_1\",\"price\":0}],\"seat\":\"pubmatic\"}],\"bidid\":\"bid-id-1\",\"cur\":\"USD\",\"ext\":{\"matchedimpression\":{\"appnexus\":50,\"pubmatic\":50}}}\r\n"}`), + }, + }, + }, + }, + }, + wantWakanda: &wakanda.Debug{}, + }, } for _, tt := range tests { HTTPLogger{}.LogAuctionObject(tt.ao) assert.Equal(t, tt.RestoredResponse, tt.ao.Response, tt.name) + var rctx *models.RequestCtx + if tt.ao != nil && tt.ao.HookExecutionOutcome != nil { + rctx = tt.ao.HookExecutionOutcome[0].Groups[0].InvocationResults[0].AnalyticsTags.Activities[0].Results[0].Values["request-ctx"].(*models.RequestCtx) + } + if rctx != nil { + assert.Equal(t, rctx.WakandaDebug, tt.wantWakanda) + } } } diff --git a/endpoints/openrtb2/auction_ow.go b/endpoints/openrtb2/auction_ow.go index 30747eb98a5..69bb24d47e9 100644 --- a/endpoints/openrtb2/auction_ow.go +++ b/endpoints/openrtb2/auction_ow.go @@ -57,7 +57,7 @@ func UpdateResponseExtOW(w http.ResponseWriter, bidResponse *openrtb2.BidRespons } //Send owlogger in response only in case of debug mode - if rCtx.Debug { + if rCtx.Debug && !rCtx.LoggerDisabled { var orignalMaxBidResponse *openrtb2.BidResponse if rCtx.Endpoint == models.EndpointAppLovinMax { orignalMaxBidResponse = new(openrtb2.BidResponse) @@ -65,17 +65,15 @@ func UpdateResponseExtOW(w http.ResponseWriter, bidResponse *openrtb2.BidRespons pubmatic.RestoreBidResponse(rCtx, ao) } - if !rCtx.LoggerDisabled { - owlogger, _ := pubmatic.GetLogAuctionObjectAsURL(ao, rCtx, false, true) - if rCtx.Endpoint == models.EndpointAppLovinMax { - *bidResponse = *orignalMaxBidResponse - } - if len(bidResponse.Ext) == 0 { - bidResponse.Ext = []byte("{}") - } - if updatedExt, err := jsonparser.Set([]byte(bidResponse.Ext), []byte(strconv.Quote(owlogger)), "owlogger"); err == nil { - bidResponse.Ext = updatedExt - } + owlogger, _ := pubmatic.GetLogAuctionObjectAsURL(ao, rCtx, false, true) + if rCtx.Endpoint == models.EndpointAppLovinMax { + *bidResponse = *orignalMaxBidResponse + } + if len(bidResponse.Ext) == 0 { + bidResponse.Ext = []byte("{}") + } + if updatedExt, err := jsonparser.Set([]byte(bidResponse.Ext), []byte(strconv.Quote(owlogger)), "owlogger"); err == nil { + bidResponse.Ext = updatedExt } } else if rCtx.Endpoint == models.EndpointAppLovinMax { bidResponse.Ext = nil diff --git a/modules/pubmatic/openwrap/entrypointhook.go b/modules/pubmatic/openwrap/entrypointhook.go index dc447ffefb3..5934a4ecde2 100644 --- a/modules/pubmatic/openwrap/entrypointhook.go +++ b/modules/pubmatic/openwrap/entrypointhook.go @@ -71,6 +71,8 @@ func (m OpenWrap) handleEntrypointHook( return result, nil } + originalRequestBody := payload.Body + if endpoint == models.EndpointAppLovinMax { rCtx.MetricsEngine = m.metricEngine // updating body locally to access updated fields from signal @@ -179,7 +181,7 @@ func (m OpenWrap) handleEntrypointHook( rCtx.WakandaDebug.EnableIfRequired(pubIdStr, rCtx.ProfileIDStr) if rCtx.WakandaDebug.IsEnable() { - rCtx.WakandaDebug.SetHTTPRequestData(payload.Request, payload.Body) + rCtx.WakandaDebug.SetHTTPRequestData(payload.Request, originalRequestBody) } result.Reject = false diff --git a/modules/pubmatic/openwrap/entrypointhook_test.go b/modules/pubmatic/openwrap/entrypointhook_test.go index 8921abcd24c..40e3e461a04 100644 --- a/modules/pubmatic/openwrap/entrypointhook_test.go +++ b/modules/pubmatic/openwrap/entrypointhook_test.go @@ -23,9 +23,12 @@ func TestOpenWrap_handleEntrypointHook(t *testing.T) { ctrl := gomock.NewController(t) mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) mockFeature := mock_feature.NewMockFeature(ctrl) - wakanda.Init(wakanda.Wakanda{}) + reset := wakanda.TestInstance("111", "222") - defer ctrl.Finish() + defer func() { + ctrl.Finish() + reset() + }() type fields struct { cfg config.Config @@ -607,6 +610,109 @@ func TestOpenWrap_handleEntrypointHook(t *testing.T) { wantErr: nil, wantBody: []byte(`{"id":"test-case-1","imp":[{"id":"1","banner":{"format":[{"w":728,"h":90},{"w":300,"h":250}],"w":700,"h":900,"api":[5,6,7]},"video":{"mimes":["video/mp4","video/mpeg"],"w":640,"h":480},"displaymanager":"PubMatic_OpenBid_SDK","displaymanagerver":"1.4.0","tagid":"/43743431/DMDemo","bidfloor":0.01,"bidfloorcur":"USD","clickbrowser":0,"secure":0,"exp":14400}],"app":{"id":"1234567","name":"DrawHappyAngel","bundle":"com.newstory.DrawHappyAngel","storeurl":"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098","cat":["IAB9-30"],"ver":"0.5.4","paid":4,"publisher":{"id":"5890","name":"New Story Inc.","ext":{"installed_sdk":{"id":"MOLOCO_BIDDING","sdk_version":{"major":1,"minor":0,"micro":0},"adapter_version":{"major":1,"minor":0,"micro":0}}}},"ext":{"orientation":1}},"device":{"geo":{"lat":40.7429,"lon":-73.9392,"type":2,"ipservice":3,"country":"USA","region":"ny","metro":"501","city":"Queens","zip":"11101","ext":{"org":"Myanmar Broadband Telecom Co.","isp":"Myanmar Broadband Telecom Co."}},"ua":"Mozilla/5.0 (Linux; Android 13; 22101316C Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.230 Mobile Safari/537.36","ip":"38.158.207.171","devicetype":4,"make":"xiaomi","model":"AndroidSDKbuiltforx86","os":"android","osv":"13.0.0","hwv":"ruby","h":2400,"w":1080,"ppi":440,"pxratio":2.75,"js":1,"language":"en_US","carrier":"MYTEL","mccmnc":"310-260","connectiontype":5,"ifa":"497a10d6-c4dd-4e04-a986-c32b7180d462","ext":{"atts":3}},"user":{"data":[{"id":"1234"}],"ext":{"gdpr":0}},"at":1,"tmax":1000,"bcat":["IAB26-4","IAB26-2","IAB25-6","IAB25-5","IAB25-4","IAB25-3","IAB25-1","IAB25-7","IAB8-18","IAB26-3","IAB26-1","IAB8-5","IAB25-2","IAB11-4"],"source":{"ext":{"schain":{"ver":"1.0","complete":1,"nodes":[{"asi":"applovin.com","sid":"53bf468f18c5a0e2b7d4e3f748c677c1","rid":"494dbe15a3ce08c54f4e456363f35a022247f997","hp":1}]},"omidpn":"PubMatic","omidpv":"1.2.11-Pubmatic"}},"regs":{"ext":{"gdpr":0}},"ext":{"prebid":{"bidderparams":{"pubmatic":{"sendburl":true,"wrapper":{"profileid":12929,"versionid":1,"clientconfig":1}}}}}}`), }, + { + name: "valid request for applovin max /openrtb2/auction?source=owsdk&agent=max request with wakanda enable", + fields: fields{ + cfg: config.Config{ + Tracker: config.Tracker{ + Endpoint: "t.pubmatic.com", + VideoErrorTrackerEndpoint: "t.pubmatic.com/error", + }, + }, + cache: nil, + }, + args: args{ + in0: context.Background(), + miCtx: hookstage.ModuleInvocationContext{}, + payload: hookstage.EntrypointPayload{ + Request: func() *http.Request { + r, err := http.NewRequest("POST", "http://localhost/openrtb2/auction?source=owsdk&agent=max&debug=1", nil) + if err != nil { + panic(err) + } + r.Header.Add("User-Agent", "go-test") + r.Header.Add("Source_ip", "127.0.0.1") + r.Header.Add("Cookie", `KADUSERCOOKIE=7D75D25F-FAC9-443D-B2D1-B17FEE11E027; DPSync3=1684886400%3A248%7C1685491200%3A245_226_201; KRTBCOOKIE_80=16514-CAESEMih0bN7ISRdZT8xX8LXzEw&KRTB&22987-CAESEMih0bN7ISRdZT8xX8LXzEw&KRTB&23025-CAESEMih0bN7ISRdZT8xX8LXzEw&KRTB&23386-CAESEMih0bN7ISRdZT8xX8LXzEw; KRTBCOOKIE_377=6810-59dc50c9-d658-44ce-b442-5a1f344d97c0&KRTB&22918-59dc50c9-d658-44ce-b442-5a1f344d97c0&KRTB&23031-59dc50c9-d658-44ce-b442-5a1f344d97c0; uids=eyJ0ZW1wVUlEcyI6eyIzM2Fjcm9zcyI6eyJ1aWQiOiIxMTkxNzkxMDk5Nzc2NjEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTo0My4zODg4Nzk5NVoifSwiYWRmIjp7InVpZCI6IjgwNDQ2MDgzMzM3Nzg4MzkwNzgiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMS4wMzMwNTQ3MjdaIn0sImFka2VybmVsIjp7InVpZCI6IkE5MTYzNTAwNzE0OTkyOTMyOTkwIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuMzczMzg1NjYyWiJ9LCJhZGtlcm5lbEFkbiI6eyJ1aWQiOiJBOTE2MzUwMDcxNDk5MjkzMjk5MCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEzLjQzNDkyNTg5NloifSwiYWRtaXhlciI6eyJ1aWQiOiIzNjZhMTdiMTJmMjI0ZDMwOGYzZTNiOGRhOGMzYzhhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjU5MjkxNDgwMVoifSwiYWRueHMiOnsidWlkIjoiNDE5Mjg5ODUzMDE0NTExOTMiLCJleHBpcmVzIjoiMjAyMy0wMS0xOFQwOTo1MzowOC44MjU0NDI2NzZaIn0sImFqYSI6eyJ1aWQiOiJzMnN1aWQ2RGVmMFl0bjJveGQ1aG9zS1AxVmV3IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTMuMjM5MTc2MDU0WiJ9LCJlcGxhbm5pbmciOnsidWlkIjoiQUoxRjBTOE5qdTdTQ0xWOSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjkyOTk2MDQ3M1oifSwiZ2Ftb3NoaSI6eyJ1aWQiOiJndXNyXzM1NmFmOWIxZDhjNjQyYjQ4MmNiYWQyYjdhMjg4MTYxIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuNTI0MTU3MjI1WiJ9LCJncmlkIjp7InVpZCI6IjRmYzM2MjUwLWQ4NTItNDU5Yy04NzcyLTczNTZkZTE3YWI5NyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE0LjY5NjMxNjIyN1oifSwiZ3JvdXBtIjp7InVpZCI6IjdENzVEMjVGLUZBQzktNDQzRC1CMkQxLUIxN0ZFRTExRTAyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjM5LjIyNjIxMjUzMloifSwiaXgiOnsidWlkIjoiWW9ORlNENlc5QkphOEh6eEdtcXlCUUFBXHUwMDI2Mjk3IiwiZXhwaXJlcyI6IjIwMjMtMDUtMzFUMDc6NTM6MzguNTU1ODI3MzU0WiJ9LCJqaXhpZSI6eyJ1aWQiOiI3MzY3MTI1MC1lODgyLTExZWMtYjUzOC0xM2FjYjdhZjBkZTQiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi4xOTEwOTk3MzJaIn0sImxvZ2ljYWQiOnsidWlkIjoiQVZ4OVROQS11c25pa3M4QURzTHpWa3JvaDg4QUFBR0JUREh0UUEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS40NTUxNDk2MTZaIn0sIm1lZGlhbmV0Ijp7InVpZCI6IjI5Nzg0MjM0OTI4OTU0MTAwMDBWMTAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMy42NzIyMTUxMjhaIn0sIm1naWQiOnsidWlkIjoibTU5Z1hyN0xlX1htIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTcuMDk3MDAxNDcxWiJ9LCJuYW5vaW50ZXJhY3RpdmUiOnsidWlkIjoiNmFlYzhjMTAzNzlkY2I3ODQxMmJjODBiNmRkOWM5NzMxNzNhYjdkNzEyZTQzMWE1YTVlYTcwMzRlNTZhNThhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE2LjcxNDgwNzUwNVoifSwib25ldGFnIjp7InVpZCI6IjdPelZoVzFOeC1LOGFVak1HMG52NXVNYm5YNEFHUXZQbnVHcHFrZ3k0ckEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS4xNDE3NDEyNjJaIn0sIm9wZW54Ijp7InVpZCI6IjVkZWNlNjIyLTBhMjMtMGRhYi0zYTI0LTVhNzcwMTBlNDU4MiIsImV4cGlyZXMiOiIyMDIzLTA1LTMxVDA3OjUyOjQ3LjE0MDQxNzM2M1oifSwicHVibWF0aWMiOnsidWlkIjoiN0Q3NUQyNUYtRkFDOS00NDNELUIyRDEtQjE3RkVFMTFFMDI3IiwiZXhwaXJlcyI6IjIwMjItMTAtMzFUMDk6MTQ6MjUuNzM3MjU2ODk5WiJ9LCJyaWNoYXVkaWVuY2UiOnsidWlkIjoiY2I2YzYzMjAtMzNlMi00Nzc0LWIxNjAtMXp6MTY1NDg0MDc0OSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjUyNTA3NDE4WiJ9LCJzbWFydHlhZHMiOnsidWlkIjoiMTJhZjE1ZTQ0ZjAwZDA3NjMwZTc0YzQ5MTU0Y2JmYmE0Zjg0N2U4ZDRhMTU0YzhjM2Q1MWY1OGNmNzJhNDYyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjgyNTAzMTg4NFoifSwic21pbGV3YW50ZWQiOnsidWlkIjoiZGQ5YzNmZTE4N2VmOWIwOWNhYTViNzExNDA0YzI4MzAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNC4yNTU2MDkzNjNaIn0sInN5bmFjb3JtZWRpYSI6eyJ1aWQiOiJHRFBSIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuOTc5NTgzNDM4WiJ9LCJ0cmlwbGVsaWZ0Ijp7InVpZCI6IjcwMjE5NzUwNTQ4MDg4NjUxOTQ2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA4Ljk4OTY3MzU3NFoifSwidmFsdWVpbXByZXNzaW9uIjp7InVpZCI6IjlkMDgxNTVmLWQ5ZmUtNGI1OC04OThlLWUyYzU2MjgyYWIzZSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjA2NzgzOTE2NFoifSwidmlzeCI6eyJ1aWQiOiIyN2UwYWMzYy1iNDZlLTQxYjMtOTkyYy1mOGQyNzE0OTQ5NWUiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi45ODk1MjM1NzNaIn0sInlpZWxkbGFiIjp7InVpZCI6IjY5NzE0ZDlmLWZiMDAtNGE1Zi04MTljLTRiZTE5MTM2YTMyNSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjExLjMwMzAyNjYxNVoifSwieWllbGRtbyI6eyJ1aWQiOiJnOTZjMmY3MTlmMTU1MWIzMWY2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjExMDUyODYwOVoifSwieWllbGRvbmUiOnsidWlkIjoiMmE0MmZiZDMtMmM3MC00ZWI5LWIxYmQtMDQ2OTY2NTBkOTQ4IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuMzE4MzMzOTM5WiJ9LCJ6ZXJvY2xpY2tmcmF1ZCI6eyJ1aWQiOiJiOTk5NThmZS0yYTg3LTJkYTQtOWNjNC05NjFmZDExM2JlY2UiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNS43MTk1OTQ1NjZaIn19LCJiZGF5IjoiMjAyMi0wNS0xN1QwNjo0ODozOC4wMTc5ODgyMDZaIn0=; KRTBCOOKIE_153=1923-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse&KRTB&19420-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse&KRTB&22979-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse&KRTB&23462-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse; KRTBCOOKIE_57=22776-41928985301451193&KRTB&23339-41928985301451193; KRTBCOOKIE_27=16735-uid:3cab6283-4546-4500-a7b6-40ef605fe745&KRTB&16736-uid:3cab6283-4546-4500-a7b6-40ef605fe745&KRTB&23019-uid:3cab6283-4546-4500-a7b6-40ef605fe745&KRTB&23114-uid:3cab6283-4546-4500-a7b6-40ef605fe745; KRTBCOOKIE_18=22947-1978557989514665832; KRTBCOOKIE_466=16530-4fc36250-d852-459c-8772-7356de17ab97; KRTBCOOKIE_391=22924-8044608333778839078&KRTB&23263-8044608333778839078&KRTB&23481-8044608333778839078; KRTBCOOKIE_1310=23431-b81c3g7dr67i&KRTB&23446-b81c3g7dr67i&KRTB&23465-b81c3g7dr67i; KRTBCOOKIE_1290=23368-vkf3yv9lbbl; KRTBCOOKIE_22=14911-4554572065121110164&KRTB&23150-4554572065121110164; KRTBCOOKIE_860=16335-YGAqDU1zUTdjyAFxCoe3kctlNPo&KRTB&23334-YGAqDU1zUTdjyAFxCoe3kctlNPo&KRTB&23417-YGAqDU1zUTdjyAFxCoe3kctlNPo&KRTB&23426-YGAqDU1zUTdjyAFxCoe3kctlNPo; KRTBCOOKIE_904=16787-KwJwE7NkCZClNJRysN2iYg; KRTBCOOKIE_1159=23138-5545f53f3d6e4ec199d8ed627ff026f3&KRTB&23328-5545f53f3d6e4ec199d8ed627ff026f3&KRTB&23427-5545f53f3d6e4ec199d8ed627ff026f3&KRTB&23445-5545f53f3d6e4ec199d8ed627ff026f3; KRTBCOOKIE_32=11175-AQEI_1QecY2ESAIjEW6KAQEBAQE&KRTB&22713-AQEI_1QecY2ESAIjEW6KAQEBAQE&KRTB&22715-AQEI_1QecY2ESAIjEW6KAQEBAQE; SyncRTB3=1685577600%3A35%7C1685491200%3A107_21_71_56_204_247_165_231_233_179_22_209_54_254_238_96_99_220_7_214_13_3_8_234_176_46_5%7C1684886400%3A2_223_15%7C1689465600%3A69%7C1685145600%3A63; KRTBCOOKIE_107=1471-uid:EK38R0PM1NQR0H5&KRTB&23421-uid:EK38R0PM1NQR0H5; KRTBCOOKIE_594=17105-RX-447a6332-530e-456a-97f4-3f0fd1ed48c9-004&KRTB&17107-RX-447a6332-530e-456a-97f4-3f0fd1ed48c9-004; SPugT=1684310122; chkChromeAb67Sec=133; KRTBCOOKIE_699=22727-AAFy2k7FBosAAEasbJoXnw; PugT=1684310473; origin=go-test`) + return r + }(), + Body: []byte(`{"id":"test-case-1","at":1,"bcat":["IAB26-4","IAB26-2","IAB25-6","IAB25-5","IAB25-4","IAB25-3","IAB25-1","IAB25-7","IAB8-18","IAB26-3","IAB26-1","IAB8-5","IAB25-2","IAB11-4"],"tmax":1000,"app":{"publisher":{"name":"New Story Inc.","id":"111","ext":{"installed_sdk":{"id":"MOLOCO_BIDDING","sdk_version":{"major":1,"minor":0,"micro":0},"adapter_version":{"major":1,"minor":0,"micro":0}}}},"paid":0,"name":"DrawHappyAngel","ver":"0.5.4","bundle":"com.newstory.DrawHappyAngel","cat":["IAB9-30"],"id":"1234567","ext":{"orientation":1}},"device":{"ifa":"497a10d6-c4dd-4e04-a986-c32b7180d462","ip":"38.158.207.171","carrier":"MYTEL","language":"en_US","hwv":"ruby","ppi":440,"pxratio":2.75,"devicetype":4,"connectiontype":2,"js":1,"h":2400,"w":1080,"geo":{"type":2,"ipservice":3,"lat":40.7429,"lon":-73.9392,"long":-73.9392,"city":"Queens","country":"USA","region":"ny","dma":"501","metro":"501","zip":"11101","ext":{"org":"Myanmar Broadband Telecom Co.","isp":"Myanmar Broadband Telecom Co."}},"ext":{},"osv":"13.0.0","ua":"Mozilla/5.0 (Linux; Android 13; 22101316C Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.230 Mobile Safari/537.36","make":"xiaomi","model":"22101316c","os":"android"},"imp":[{"id":"1","displaymanager":"applovin_mediation","displaymanagerver":"11.8.2","instl":0,"secure":0,"tagid":"/43743431/DMDemo","bidfloor":0.01,"bidfloorcur":"USD","exp":14400,"banner":{"format":[{"w":728,"h":90},{"w":300,"h":250}],"w":700,"h":900},"video":{"mimes":["video/mp4","video/mpeg"],"w":640,"h":480},"rwdd":0}],"user":{"data":[{"id":"1","name":"Publisher Passed","segment":[{"signal":"{\"id\":\"95d6643c-3da6-40a2-b9ca-12279393ffbf\",\"at\":1,\"tmax\":500,\"cur\":[\"USD\"],\"imp\":[{\"id\":\"imp176227948\",\"clickbrowser\":0,\"displaymanager\":\"PubMatic_OpenBid_SDK\",\"displaymanagerver\":\"1.4.0\",\"tagid\":\"/43743431/DMDemo\",\"secure\":0,\"banner\":{\"pos\":7,\"format\":[{\"w\":300,\"h\":250}],\"api\":[5,6,7]},\"instl\":1}],\"app\":{\"paid\":4,\"name\":\"OpenWrapperSample\",\"bundle\":\"com.pubmatic.openbid.app\",\"storeurl\":\"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098\",\"ver\":\"1.0\",\"publisher\":{\"id\":\"111\"}},\"device\":{\"geo\":{\"type\":1,\"lat\":37.421998333333335,\"lon\":-122.08400000000002},\"pxratio\":2.625,\"mccmnc\":\"310-260\",\"lmt\":0,\"ifa\":\"07c387f2-e030-428f-8336-42f682150759\",\"connectiontype\":5,\"carrier\":\"Android\",\"js\":1,\"ua\":\"Mozilla/5.0(Linux;Android9;AndroidSDKbuiltforx86Build/PSR1.180720.075;wv)AppleWebKit/537.36(KHTML,likeGecko)Version/4.0Chrome/69.0.3497.100MobileSafari/537.36\",\"make\":\"Google\",\"model\":\"AndroidSDKbuiltforx86\",\"os\":\"Android\",\"osv\":\"9\",\"h\":1794,\"w\":1080,\"language\":\"en-US\",\"devicetype\":4,\"ext\":{\"atts\":3}},\"source\":{\"ext\":{\"omidpn\":\"PubMatic\",\"omidpv\":\"1.2.11-Pubmatic\"}},\"user\":{\"data\":[{\"id\":\"1234\"}]},\"ext\":{\"wrapper\":{\"ssauction\":1,\"sumry_disable\":0,\"profileid\":58135,\"versionid\":1,\"clientconfig\":1}}}"}]}],"ext":{"gdpr":0}},"regs":{"coppa":0,"ext":{"gdpr":0}},"source":{"ext":{"schain":{"ver":"1.0","complete":1,"nodes":[{"asi":"applovin.com","sid":"53bf468f18c5a0e2b7d4e3f748c677c1","rid":"494dbe15a3ce08c54f4e456363f35a022247f997","hp":1}]}}},"ext":{"prebid":{"bidderparams":{"pubmatic":{"sendburl":true,"wrapper":{"profileid":222,"versionid":1}}}}}}`), + }, + setup: func(mme *mock_metrics.MockMetricsEngine) { + mockFeature.EXPECT().GetApplovinMultiFloors(111, "222").Return(models.ApplovinAdUnitFloors{ + "adunit_name": {5.1, 2.1, 4, 4.3}, + }) + mockFeature.EXPECT().IsApplovinMultiFloorsEnabled(111, "222").Return(true) + }, + }, + want: hookstage.HookResult[hookstage.EntrypointPayload]{ + ModuleContext: hookstage.ModuleContext{ + "rctx": models.RequestCtx{ + PubIDStr: "111", + PubID: 111, + ProfileID: 222, + DisplayID: 1, + DisplayVersionID: 1, + SSAuction: -1, + ClientConfigFlag: 1, + Debug: true, + UA: "go-test", + IP: "127.0.0.1", + IsCTVRequest: false, + TrackerEndpoint: "t.pubmatic.com", + VideoErrorTrackerEndpoint: "t.pubmatic.com/error", + UidCookie: &http.Cookie{ + Name: "uids", + Value: `eyJ0ZW1wVUlEcyI6eyIzM2Fjcm9zcyI6eyJ1aWQiOiIxMTkxNzkxMDk5Nzc2NjEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTo0My4zODg4Nzk5NVoifSwiYWRmIjp7InVpZCI6IjgwNDQ2MDgzMzM3Nzg4MzkwNzgiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMS4wMzMwNTQ3MjdaIn0sImFka2VybmVsIjp7InVpZCI6IkE5MTYzNTAwNzE0OTkyOTMyOTkwIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuMzczMzg1NjYyWiJ9LCJhZGtlcm5lbEFkbiI6eyJ1aWQiOiJBOTE2MzUwMDcxNDk5MjkzMjk5MCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEzLjQzNDkyNTg5NloifSwiYWRtaXhlciI6eyJ1aWQiOiIzNjZhMTdiMTJmMjI0ZDMwOGYzZTNiOGRhOGMzYzhhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjU5MjkxNDgwMVoifSwiYWRueHMiOnsidWlkIjoiNDE5Mjg5ODUzMDE0NTExOTMiLCJleHBpcmVzIjoiMjAyMy0wMS0xOFQwOTo1MzowOC44MjU0NDI2NzZaIn0sImFqYSI6eyJ1aWQiOiJzMnN1aWQ2RGVmMFl0bjJveGQ1aG9zS1AxVmV3IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTMuMjM5MTc2MDU0WiJ9LCJlcGxhbm5pbmciOnsidWlkIjoiQUoxRjBTOE5qdTdTQ0xWOSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjkyOTk2MDQ3M1oifSwiZ2Ftb3NoaSI6eyJ1aWQiOiJndXNyXzM1NmFmOWIxZDhjNjQyYjQ4MmNiYWQyYjdhMjg4MTYxIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuNTI0MTU3MjI1WiJ9LCJncmlkIjp7InVpZCI6IjRmYzM2MjUwLWQ4NTItNDU5Yy04NzcyLTczNTZkZTE3YWI5NyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE0LjY5NjMxNjIyN1oifSwiZ3JvdXBtIjp7InVpZCI6IjdENzVEMjVGLUZBQzktNDQzRC1CMkQxLUIxN0ZFRTExRTAyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjM5LjIyNjIxMjUzMloifSwiaXgiOnsidWlkIjoiWW9ORlNENlc5QkphOEh6eEdtcXlCUUFBXHUwMDI2Mjk3IiwiZXhwaXJlcyI6IjIwMjMtMDUtMzFUMDc6NTM6MzguNTU1ODI3MzU0WiJ9LCJqaXhpZSI6eyJ1aWQiOiI3MzY3MTI1MC1lODgyLTExZWMtYjUzOC0xM2FjYjdhZjBkZTQiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi4xOTEwOTk3MzJaIn0sImxvZ2ljYWQiOnsidWlkIjoiQVZ4OVROQS11c25pa3M4QURzTHpWa3JvaDg4QUFBR0JUREh0UUEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS40NTUxNDk2MTZaIn0sIm1lZGlhbmV0Ijp7InVpZCI6IjI5Nzg0MjM0OTI4OTU0MTAwMDBWMTAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMy42NzIyMTUxMjhaIn0sIm1naWQiOnsidWlkIjoibTU5Z1hyN0xlX1htIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTcuMDk3MDAxNDcxWiJ9LCJuYW5vaW50ZXJhY3RpdmUiOnsidWlkIjoiNmFlYzhjMTAzNzlkY2I3ODQxMmJjODBiNmRkOWM5NzMxNzNhYjdkNzEyZTQzMWE1YTVlYTcwMzRlNTZhNThhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE2LjcxNDgwNzUwNVoifSwib25ldGFnIjp7InVpZCI6IjdPelZoVzFOeC1LOGFVak1HMG52NXVNYm5YNEFHUXZQbnVHcHFrZ3k0ckEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS4xNDE3NDEyNjJaIn0sIm9wZW54Ijp7InVpZCI6IjVkZWNlNjIyLTBhMjMtMGRhYi0zYTI0LTVhNzcwMTBlNDU4MiIsImV4cGlyZXMiOiIyMDIzLTA1LTMxVDA3OjUyOjQ3LjE0MDQxNzM2M1oifSwicHVibWF0aWMiOnsidWlkIjoiN0Q3NUQyNUYtRkFDOS00NDNELUIyRDEtQjE3RkVFMTFFMDI3IiwiZXhwaXJlcyI6IjIwMjItMTAtMzFUMDk6MTQ6MjUuNzM3MjU2ODk5WiJ9LCJyaWNoYXVkaWVuY2UiOnsidWlkIjoiY2I2YzYzMjAtMzNlMi00Nzc0LWIxNjAtMXp6MTY1NDg0MDc0OSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjUyNTA3NDE4WiJ9LCJzbWFydHlhZHMiOnsidWlkIjoiMTJhZjE1ZTQ0ZjAwZDA3NjMwZTc0YzQ5MTU0Y2JmYmE0Zjg0N2U4ZDRhMTU0YzhjM2Q1MWY1OGNmNzJhNDYyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjgyNTAzMTg4NFoifSwic21pbGV3YW50ZWQiOnsidWlkIjoiZGQ5YzNmZTE4N2VmOWIwOWNhYTViNzExNDA0YzI4MzAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNC4yNTU2MDkzNjNaIn0sInN5bmFjb3JtZWRpYSI6eyJ1aWQiOiJHRFBSIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuOTc5NTgzNDM4WiJ9LCJ0cmlwbGVsaWZ0Ijp7InVpZCI6IjcwMjE5NzUwNTQ4MDg4NjUxOTQ2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA4Ljk4OTY3MzU3NFoifSwidmFsdWVpbXByZXNzaW9uIjp7InVpZCI6IjlkMDgxNTVmLWQ5ZmUtNGI1OC04OThlLWUyYzU2MjgyYWIzZSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjA2NzgzOTE2NFoifSwidmlzeCI6eyJ1aWQiOiIyN2UwYWMzYy1iNDZlLTQxYjMtOTkyYy1mOGQyNzE0OTQ5NWUiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi45ODk1MjM1NzNaIn0sInlpZWxkbGFiIjp7InVpZCI6IjY5NzE0ZDlmLWZiMDAtNGE1Zi04MTljLTRiZTE5MTM2YTMyNSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjExLjMwMzAyNjYxNVoifSwieWllbGRtbyI6eyJ1aWQiOiJnOTZjMmY3MTlmMTU1MWIzMWY2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjExMDUyODYwOVoifSwieWllbGRvbmUiOnsidWlkIjoiMmE0MmZiZDMtMmM3MC00ZWI5LWIxYmQtMDQ2OTY2NTBkOTQ4IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuMzE4MzMzOTM5WiJ9LCJ6ZXJvY2xpY2tmcmF1ZCI6eyJ1aWQiOiJiOTk5NThmZS0yYTg3LTJkYTQtOWNjNC05NjFmZDExM2JlY2UiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNS43MTk1OTQ1NjZaIn19LCJiZGF5IjoiMjAyMi0wNS0xN1QwNjo0ODozOC4wMTc5ODgyMDZaIn0=`, + }, + KADUSERCookie: &http.Cookie{ + Name: "KADUSERCOOKIE", + Value: `7D75D25F-FAC9-443D-B2D1-B17FEE11E027`, + }, + OriginCookie: "go-test", + Aliases: make(map[string]string), + ImpBidCtx: make(map[string]models.ImpCtx), + PrebidBidderCode: make(map[string]string), + BidderResponseTimeMillis: make(map[string]int), + ProfileIDStr: "222", + Endpoint: models.EndpointAppLovinMax, + MetricsEngine: mockEngine, + SeatNonBids: make(map[string][]openrtb_ext.NonBid), + Method: "POST", + WakandaDebug: &wakanda.Debug{ + Enabled: true, + FolderPaths: []string{"DC1__PUB:111__PROF:222"}, + DebugLevel: 2, + DebugData: wakanda.DebugData{ + HTTPRequest: func() *http.Request { + r, err := http.NewRequest("POST", "http://localhost/openrtb2/auction?source=owsdk&agent=max&debug=1", nil) + if err != nil { + panic(err) + } + r.Header.Add("User-Agent", "go-test") + r.Header.Add("Source_ip", "127.0.0.1") + r.Header.Add("Cookie", `KADUSERCOOKIE=7D75D25F-FAC9-443D-B2D1-B17FEE11E027; DPSync3=1684886400%3A248%7C1685491200%3A245_226_201; KRTBCOOKIE_80=16514-CAESEMih0bN7ISRdZT8xX8LXzEw&KRTB&22987-CAESEMih0bN7ISRdZT8xX8LXzEw&KRTB&23025-CAESEMih0bN7ISRdZT8xX8LXzEw&KRTB&23386-CAESEMih0bN7ISRdZT8xX8LXzEw; KRTBCOOKIE_377=6810-59dc50c9-d658-44ce-b442-5a1f344d97c0&KRTB&22918-59dc50c9-d658-44ce-b442-5a1f344d97c0&KRTB&23031-59dc50c9-d658-44ce-b442-5a1f344d97c0; uids=eyJ0ZW1wVUlEcyI6eyIzM2Fjcm9zcyI6eyJ1aWQiOiIxMTkxNzkxMDk5Nzc2NjEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTo0My4zODg4Nzk5NVoifSwiYWRmIjp7InVpZCI6IjgwNDQ2MDgzMzM3Nzg4MzkwNzgiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMS4wMzMwNTQ3MjdaIn0sImFka2VybmVsIjp7InVpZCI6IkE5MTYzNTAwNzE0OTkyOTMyOTkwIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuMzczMzg1NjYyWiJ9LCJhZGtlcm5lbEFkbiI6eyJ1aWQiOiJBOTE2MzUwMDcxNDk5MjkzMjk5MCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEzLjQzNDkyNTg5NloifSwiYWRtaXhlciI6eyJ1aWQiOiIzNjZhMTdiMTJmMjI0ZDMwOGYzZTNiOGRhOGMzYzhhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjU5MjkxNDgwMVoifSwiYWRueHMiOnsidWlkIjoiNDE5Mjg5ODUzMDE0NTExOTMiLCJleHBpcmVzIjoiMjAyMy0wMS0xOFQwOTo1MzowOC44MjU0NDI2NzZaIn0sImFqYSI6eyJ1aWQiOiJzMnN1aWQ2RGVmMFl0bjJveGQ1aG9zS1AxVmV3IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTMuMjM5MTc2MDU0WiJ9LCJlcGxhbm5pbmciOnsidWlkIjoiQUoxRjBTOE5qdTdTQ0xWOSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjkyOTk2MDQ3M1oifSwiZ2Ftb3NoaSI6eyJ1aWQiOiJndXNyXzM1NmFmOWIxZDhjNjQyYjQ4MmNiYWQyYjdhMjg4MTYxIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuNTI0MTU3MjI1WiJ9LCJncmlkIjp7InVpZCI6IjRmYzM2MjUwLWQ4NTItNDU5Yy04NzcyLTczNTZkZTE3YWI5NyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE0LjY5NjMxNjIyN1oifSwiZ3JvdXBtIjp7InVpZCI6IjdENzVEMjVGLUZBQzktNDQzRC1CMkQxLUIxN0ZFRTExRTAyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjM5LjIyNjIxMjUzMloifSwiaXgiOnsidWlkIjoiWW9ORlNENlc5QkphOEh6eEdtcXlCUUFBXHUwMDI2Mjk3IiwiZXhwaXJlcyI6IjIwMjMtMDUtMzFUMDc6NTM6MzguNTU1ODI3MzU0WiJ9LCJqaXhpZSI6eyJ1aWQiOiI3MzY3MTI1MC1lODgyLTExZWMtYjUzOC0xM2FjYjdhZjBkZTQiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi4xOTEwOTk3MzJaIn0sImxvZ2ljYWQiOnsidWlkIjoiQVZ4OVROQS11c25pa3M4QURzTHpWa3JvaDg4QUFBR0JUREh0UUEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS40NTUxNDk2MTZaIn0sIm1lZGlhbmV0Ijp7InVpZCI6IjI5Nzg0MjM0OTI4OTU0MTAwMDBWMTAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMy42NzIyMTUxMjhaIn0sIm1naWQiOnsidWlkIjoibTU5Z1hyN0xlX1htIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTcuMDk3MDAxNDcxWiJ9LCJuYW5vaW50ZXJhY3RpdmUiOnsidWlkIjoiNmFlYzhjMTAzNzlkY2I3ODQxMmJjODBiNmRkOWM5NzMxNzNhYjdkNzEyZTQzMWE1YTVlYTcwMzRlNTZhNThhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE2LjcxNDgwNzUwNVoifSwib25ldGFnIjp7InVpZCI6IjdPelZoVzFOeC1LOGFVak1HMG52NXVNYm5YNEFHUXZQbnVHcHFrZ3k0ckEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS4xNDE3NDEyNjJaIn0sIm9wZW54Ijp7InVpZCI6IjVkZWNlNjIyLTBhMjMtMGRhYi0zYTI0LTVhNzcwMTBlNDU4MiIsImV4cGlyZXMiOiIyMDIzLTA1LTMxVDA3OjUyOjQ3LjE0MDQxNzM2M1oifSwicHVibWF0aWMiOnsidWlkIjoiN0Q3NUQyNUYtRkFDOS00NDNELUIyRDEtQjE3RkVFMTFFMDI3IiwiZXhwaXJlcyI6IjIwMjItMTAtMzFUMDk6MTQ6MjUuNzM3MjU2ODk5WiJ9LCJyaWNoYXVkaWVuY2UiOnsidWlkIjoiY2I2YzYzMjAtMzNlMi00Nzc0LWIxNjAtMXp6MTY1NDg0MDc0OSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjUyNTA3NDE4WiJ9LCJzbWFydHlhZHMiOnsidWlkIjoiMTJhZjE1ZTQ0ZjAwZDA3NjMwZTc0YzQ5MTU0Y2JmYmE0Zjg0N2U4ZDRhMTU0YzhjM2Q1MWY1OGNmNzJhNDYyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjgyNTAzMTg4NFoifSwic21pbGV3YW50ZWQiOnsidWlkIjoiZGQ5YzNmZTE4N2VmOWIwOWNhYTViNzExNDA0YzI4MzAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNC4yNTU2MDkzNjNaIn0sInN5bmFjb3JtZWRpYSI6eyJ1aWQiOiJHRFBSIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuOTc5NTgzNDM4WiJ9LCJ0cmlwbGVsaWZ0Ijp7InVpZCI6IjcwMjE5NzUwNTQ4MDg4NjUxOTQ2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA4Ljk4OTY3MzU3NFoifSwidmFsdWVpbXByZXNzaW9uIjp7InVpZCI6IjlkMDgxNTVmLWQ5ZmUtNGI1OC04OThlLWUyYzU2MjgyYWIzZSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjA2NzgzOTE2NFoifSwidmlzeCI6eyJ1aWQiOiIyN2UwYWMzYy1iNDZlLTQxYjMtOTkyYy1mOGQyNzE0OTQ5NWUiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi45ODk1MjM1NzNaIn0sInlpZWxkbGFiIjp7InVpZCI6IjY5NzE0ZDlmLWZiMDAtNGE1Zi04MTljLTRiZTE5MTM2YTMyNSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjExLjMwMzAyNjYxNVoifSwieWllbGRtbyI6eyJ1aWQiOiJnOTZjMmY3MTlmMTU1MWIzMWY2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjExMDUyODYwOVoifSwieWllbGRvbmUiOnsidWlkIjoiMmE0MmZiZDMtMmM3MC00ZWI5LWIxYmQtMDQ2OTY2NTBkOTQ4IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuMzE4MzMzOTM5WiJ9LCJ6ZXJvY2xpY2tmcmF1ZCI6eyJ1aWQiOiJiOTk5NThmZS0yYTg3LTJkYTQtOWNjNC05NjFmZDExM2JlY2UiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNS43MTk1OTQ1NjZaIn19LCJiZGF5IjoiMjAyMi0wNS0xN1QwNjo0ODozOC4wMTc5ODgyMDZaIn0=; KRTBCOOKIE_153=1923-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse&KRTB&19420-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse&KRTB&22979-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse&KRTB&23462-LZw-Ny-bMjI2m2Q8IpsrNnnIYzw2yjdnLJsrdYse; KRTBCOOKIE_57=22776-41928985301451193&KRTB&23339-41928985301451193; KRTBCOOKIE_27=16735-uid:3cab6283-4546-4500-a7b6-40ef605fe745&KRTB&16736-uid:3cab6283-4546-4500-a7b6-40ef605fe745&KRTB&23019-uid:3cab6283-4546-4500-a7b6-40ef605fe745&KRTB&23114-uid:3cab6283-4546-4500-a7b6-40ef605fe745; KRTBCOOKIE_18=22947-1978557989514665832; KRTBCOOKIE_466=16530-4fc36250-d852-459c-8772-7356de17ab97; KRTBCOOKIE_391=22924-8044608333778839078&KRTB&23263-8044608333778839078&KRTB&23481-8044608333778839078; KRTBCOOKIE_1310=23431-b81c3g7dr67i&KRTB&23446-b81c3g7dr67i&KRTB&23465-b81c3g7dr67i; KRTBCOOKIE_1290=23368-vkf3yv9lbbl; KRTBCOOKIE_22=14911-4554572065121110164&KRTB&23150-4554572065121110164; KRTBCOOKIE_860=16335-YGAqDU1zUTdjyAFxCoe3kctlNPo&KRTB&23334-YGAqDU1zUTdjyAFxCoe3kctlNPo&KRTB&23417-YGAqDU1zUTdjyAFxCoe3kctlNPo&KRTB&23426-YGAqDU1zUTdjyAFxCoe3kctlNPo; KRTBCOOKIE_904=16787-KwJwE7NkCZClNJRysN2iYg; KRTBCOOKIE_1159=23138-5545f53f3d6e4ec199d8ed627ff026f3&KRTB&23328-5545f53f3d6e4ec199d8ed627ff026f3&KRTB&23427-5545f53f3d6e4ec199d8ed627ff026f3&KRTB&23445-5545f53f3d6e4ec199d8ed627ff026f3; KRTBCOOKIE_32=11175-AQEI_1QecY2ESAIjEW6KAQEBAQE&KRTB&22713-AQEI_1QecY2ESAIjEW6KAQEBAQE&KRTB&22715-AQEI_1QecY2ESAIjEW6KAQEBAQE; SyncRTB3=1685577600%3A35%7C1685491200%3A107_21_71_56_204_247_165_231_233_179_22_209_54_254_238_96_99_220_7_214_13_3_8_234_176_46_5%7C1684886400%3A2_223_15%7C1689465600%3A69%7C1685145600%3A63; KRTBCOOKIE_107=1471-uid:EK38R0PM1NQR0H5&KRTB&23421-uid:EK38R0PM1NQR0H5; KRTBCOOKIE_594=17105-RX-447a6332-530e-456a-97f4-3f0fd1ed48c9-004&KRTB&17107-RX-447a6332-530e-456a-97f4-3f0fd1ed48c9-004; SPugT=1684310122; chkChromeAb67Sec=133; KRTBCOOKIE_699=22727-AAFy2k7FBosAAEasbJoXnw; PugT=1684310473; origin=go-test`) + return r + }(), + HTTPRequestBody: []byte(`{"id":"test-case-1","at":1,"bcat":["IAB26-4","IAB26-2","IAB25-6","IAB25-5","IAB25-4","IAB25-3","IAB25-1","IAB25-7","IAB8-18","IAB26-3","IAB26-1","IAB8-5","IAB25-2","IAB11-4"],"tmax":1000,"app":{"publisher":{"name":"New Story Inc.","id":"111","ext":{"installed_sdk":{"id":"MOLOCO_BIDDING","sdk_version":{"major":1,"minor":0,"micro":0},"adapter_version":{"major":1,"minor":0,"micro":0}}}},"paid":0,"name":"DrawHappyAngel","ver":"0.5.4","bundle":"com.newstory.DrawHappyAngel","cat":["IAB9-30"],"id":"1234567","ext":{"orientation":1}},"device":{"ifa":"497a10d6-c4dd-4e04-a986-c32b7180d462","ip":"38.158.207.171","carrier":"MYTEL","language":"en_US","hwv":"ruby","ppi":440,"pxratio":2.75,"devicetype":4,"connectiontype":2,"js":1,"h":2400,"w":1080,"geo":{"type":2,"ipservice":3,"lat":40.7429,"lon":-73.9392,"long":-73.9392,"city":"Queens","country":"USA","region":"ny","dma":"501","metro":"501","zip":"11101","ext":{"org":"Myanmar Broadband Telecom Co.","isp":"Myanmar Broadband Telecom Co."}},"ext":{},"osv":"13.0.0","ua":"Mozilla/5.0 (Linux; Android 13; 22101316C Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.230 Mobile Safari/537.36","make":"xiaomi","model":"22101316c","os":"android"},"imp":[{"id":"1","displaymanager":"applovin_mediation","displaymanagerver":"11.8.2","instl":0,"secure":0,"tagid":"/43743431/DMDemo","bidfloor":0.01,"bidfloorcur":"USD","exp":14400,"banner":{"format":[{"w":728,"h":90},{"w":300,"h":250}],"w":700,"h":900},"video":{"mimes":["video/mp4","video/mpeg"],"w":640,"h":480},"rwdd":0}],"user":{"data":[{"id":"1","name":"Publisher Passed","segment":[{"signal":"{\"id\":\"95d6643c-3da6-40a2-b9ca-12279393ffbf\",\"at\":1,\"tmax\":500,\"cur\":[\"USD\"],\"imp\":[{\"id\":\"imp176227948\",\"clickbrowser\":0,\"displaymanager\":\"PubMatic_OpenBid_SDK\",\"displaymanagerver\":\"1.4.0\",\"tagid\":\"/43743431/DMDemo\",\"secure\":0,\"banner\":{\"pos\":7,\"format\":[{\"w\":300,\"h\":250}],\"api\":[5,6,7]},\"instl\":1}],\"app\":{\"paid\":4,\"name\":\"OpenWrapperSample\",\"bundle\":\"com.pubmatic.openbid.app\",\"storeurl\":\"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098\",\"ver\":\"1.0\",\"publisher\":{\"id\":\"111\"}},\"device\":{\"geo\":{\"type\":1,\"lat\":37.421998333333335,\"lon\":-122.08400000000002},\"pxratio\":2.625,\"mccmnc\":\"310-260\",\"lmt\":0,\"ifa\":\"07c387f2-e030-428f-8336-42f682150759\",\"connectiontype\":5,\"carrier\":\"Android\",\"js\":1,\"ua\":\"Mozilla/5.0(Linux;Android9;AndroidSDKbuiltforx86Build/PSR1.180720.075;wv)AppleWebKit/537.36(KHTML,likeGecko)Version/4.0Chrome/69.0.3497.100MobileSafari/537.36\",\"make\":\"Google\",\"model\":\"AndroidSDKbuiltforx86\",\"os\":\"Android\",\"osv\":\"9\",\"h\":1794,\"w\":1080,\"language\":\"en-US\",\"devicetype\":4,\"ext\":{\"atts\":3}},\"source\":{\"ext\":{\"omidpn\":\"PubMatic\",\"omidpv\":\"1.2.11-Pubmatic\"}},\"user\":{\"data\":[{\"id\":\"1234\"}]},\"ext\":{\"wrapper\":{\"ssauction\":1,\"sumry_disable\":0,\"profileid\":58135,\"versionid\":1,\"clientconfig\":1}}}"}]}],"ext":{"gdpr":0}},"regs":{"coppa":0,"ext":{"gdpr":0}},"source":{"ext":{"schain":{"ver":"1.0","complete":1,"nodes":[{"asi":"applovin.com","sid":"53bf468f18c5a0e2b7d4e3f748c677c1","rid":"494dbe15a3ce08c54f4e456363f35a022247f997","hp":1}]}}},"ext":{"prebid":{"bidderparams":{"pubmatic":{"sendburl":true,"wrapper":{"profileid":222,"versionid":1}}}}}}`), + }, + }, + AppLovinMax: models.AppLovinMax{ + MultiFloorsConfig: models.MultiFloorsConfig{ + Enabled: true, + Config: models.ApplovinAdUnitFloors{ + "adunit_name": {5.1, 2.1, 4, 4.3}, + }, + }, + }, + SendBurl: true, + ImpCountingMethodEnabledBidders: make(map[string]struct{}), + }, + }, + }, + doMutate: true, + wantErr: nil, + wantBody: []byte(`{"id":"test-case-1","imp":[{"id":"1","banner":{"format":[{"w":728,"h":90},{"w":300,"h":250}],"w":700,"h":900,"api":[5,6,7]},"video":{"mimes":["video/mp4","video/mpeg"],"w":640,"h":480},"displaymanager":"PubMatic_OpenBid_SDK","displaymanagerver":"1.4.0","tagid":"/43743431/DMDemo","bidfloor":0.01,"bidfloorcur":"USD","clickbrowser":0,"secure":0,"exp":14400}],"app":{"id":"1234567","name":"DrawHappyAngel","bundle":"com.newstory.DrawHappyAngel","storeurl":"https://itunes.apple.com/us/app/pubmatic-sdk-app/id1175273098","cat":["IAB9-30"],"ver":"0.5.4","paid":4,"publisher":{"id":"111","name":"New Story Inc.","ext":{"installed_sdk":{"id":"MOLOCO_BIDDING","sdk_version":{"major":1,"minor":0,"micro":0},"adapter_version":{"major":1,"minor":0,"micro":0}}}},"ext":{"orientation":1}},"device":{"geo":{"lat":40.7429,"lon":-73.9392,"type":2,"ipservice":3,"country":"USA","region":"ny","metro":"501","city":"Queens","zip":"11101","ext":{"org":"Myanmar Broadband Telecom Co.","isp":"Myanmar Broadband Telecom Co."}},"ua":"Mozilla/5.0 (Linux; Android 13; 22101316C Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.230 Mobile Safari/537.36","ip":"38.158.207.171","devicetype":4,"make":"xiaomi","model":"AndroidSDKbuiltforx86","os":"android","osv":"13.0.0","hwv":"ruby","h":2400,"w":1080,"ppi":440,"pxratio":2.75,"js":1,"language":"en_US","carrier":"MYTEL","mccmnc":"310-260","connectiontype":5,"ifa":"497a10d6-c4dd-4e04-a986-c32b7180d462","ext":{"atts":3}},"user":{"data":[{"id":"1234"}],"ext":{"gdpr":0}},"at":1,"tmax":1000,"bcat":["IAB26-4","IAB26-2","IAB25-6","IAB25-5","IAB25-4","IAB25-3","IAB25-1","IAB25-7","IAB8-18","IAB26-3","IAB26-1","IAB8-5","IAB25-2","IAB11-4"],"source":{"ext":{"schain":{"ver":"1.0","complete":1,"nodes":[{"asi":"applovin.com","sid":"53bf468f18c5a0e2b7d4e3f748c677c1","rid":"494dbe15a3ce08c54f4e456363f35a022247f997","hp":1}]},"omidpn":"PubMatic","omidpv":"1.2.11-Pubmatic"}},"regs":{"ext":{"gdpr":0}},"ext":{"prebid":{"bidderparams":{"pubmatic":{"sendburl":true,"wrapper":{"profileid":222,"versionid":1,"clientconfig":1}}}}}}`), + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/modules/pubmatic/openwrap/wakanda/wakanda.go b/modules/pubmatic/openwrap/wakanda/wakanda.go index 96e39130dd1..8ed29321924 100644 --- a/modules/pubmatic/openwrap/wakanda/wakanda.go +++ b/modules/pubmatic/openwrap/wakanda/wakanda.go @@ -90,3 +90,15 @@ func setCommandHandler() { commandExecutor: &CommandHandler{}, } } + +func TestInstance(pubId string, profileId string) func() { + wakandaRulesMap = &rulesMap{ + rules: make(map[string]*wakandaRule), + } + + key := "PUB:" + pubId + "__PROF:" + profileId + wakandaRulesMap.AddIfNotPresent(key, 2, "DC1") + return func() { + wakandaRulesMap = nil + } +} From 2a6d84d34740fa629d25764dc2a3c97eb0614229 Mon Sep 17 00:00:00 2001 From: Pubmatic-Supriya-Patil <131644110+Pubmatic-Supriya-Patil@users.noreply.github.com> Date: Tue, 15 Oct 2024 11:51:11 +0530 Subject: [PATCH 8/9] UOE-11326: Multiimpression request not logger in case one of the bid is rejected because of invalid mapping (#937) --- analytics/pubmatic/logger.go | 2 +- analytics/pubmatic/logger_test.go | 204 ++++++++++++++++++++++++++++++ 2 files changed, 205 insertions(+), 1 deletion(-) diff --git a/analytics/pubmatic/logger.go b/analytics/pubmatic/logger.go index 75364070d55..569740069c0 100644 --- a/analytics/pubmatic/logger.go +++ b/analytics/pubmatic/logger.go @@ -332,7 +332,7 @@ func getPartnerRecordsByImp(ao analytics.AuctionObject, rCtx *models.RequestCtx) // owlogger would not contain non-mapped bidders, do we need this if _, ok := impCtx.NonMapped[seat]; ok { - break + continue } var kgp, kgpv, kgpsv, adFormat string diff --git a/analytics/pubmatic/logger_test.go b/analytics/pubmatic/logger_test.go index 41ef74a283f..2f7ddb7ecca 100644 --- a/analytics/pubmatic/logger_test.go +++ b/analytics/pubmatic/logger_test.go @@ -1242,6 +1242,210 @@ func TestGetPartnerRecordsByImpForDefaultBids(t *testing.T) { }, partners: map[string][]PartnerRecord{}, }, + { + name: "Multi_impression_request_slot_not_mapped_for_imp1_for_appnexus", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 10, + }, + }, + Seat: "pubmatic", + }, + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp2", + Price: 10, + }, + }, + Seat: "pubmatic", + }, + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-2", + ImpID: "imp2", + Price: 20, + }, + }, + Seat: "appnexus", + }, + }, + }, + SeatNonBid: []openrtb_ext.SeatNonBid{}, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidFloorCur: "USD", + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + OriginalBidCPM: 10, + OriginalBidCur: "USD", + }, + }, + "bid-id-2": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + Nbr: exchange.ErrorGeneral.Ptr(), + }, + }, + }, + NonMapped: map[string]struct{}{ + "appnexus": {}, + }, + }, + "imp2": { + BidFloorCur: "USD", + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + OriginalBidCPM: 10, + OriginalBidCur: "USD", + }, + }, + "bid-id-2": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + OriginalBidCPM: 20, + OriginalBidCur: "USD", + }, + }, + }, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + NetECPM: 10, + GrossECPM: 10, + OriginalCPM: 10, + OriginalCur: "USD", + PartnerID: "pubmatic", + BidderCode: "pubmatic", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + }, + }, + "imp2": { + { + NetECPM: 10, + GrossECPM: 10, + OriginalCPM: 10, + OriginalCur: "USD", + PartnerID: "pubmatic", + BidderCode: "pubmatic", + PartnerSize: "0x0", + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + }, + { + NetECPM: 20, + GrossECPM: 20, + OriginalCPM: 20, + OriginalCur: "USD", + PartnerID: "appnexus", + BidderCode: "appnexus", + PartnerSize: "0x0", + BidID: "bid-id-2", + OrigBidID: "bid-id-2", + DealID: "-1", + ServerSide: 1, + }, + }, + }, + }, + { + name: "slot_not_mapped_for_pubmatic", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp1", + Price: 0, + }, + }, + Seat: "pubmatic", + }, + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-2", + ImpID: "imp1", + Price: 10, + }, + }, + Seat: "appnexus", + }, + }, + }, + SeatNonBid: []openrtb_ext.SeatNonBid{}, + }, + rCtx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidFloorCur: "USD", + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + }, + }, + "bid-id-2": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + OriginalBidCPM: 10, + OriginalBidCur: "USD", + }, + }, + }, + NonMapped: map[string]struct{}{ + "pubmatic": {}, + }, + }, + }, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + NetECPM: 10, + GrossECPM: 10, + OriginalCPM: 10, + OriginalCur: "USD", + PartnerID: "appnexus", + BidderCode: "appnexus", + PartnerSize: "0x0", + BidID: "bid-id-2", + OrigBidID: "bid-id-2", + DealID: "-1", + ServerSide: 1, + }, + }, + }, + }, { name: "partner throttled, default bid present is seat-bid but absent in seat-non-bid", args: args{ From e8de58039ab2db32de655c13962288ba1512a8fc Mon Sep 17 00:00:00 2001 From: pm-avinash-kapre <112699665+AvinashKapre@users.noreply.github.com> Date: Wed, 16 Oct 2024 14:13:02 +0530 Subject: [PATCH 9/9] UOE-11339: Bug fix- Default Bid is not getting formed for Prebid S2S (#941) * UOE-11339: Bug fix- Default Bid is not getting formed for Prebid S2S * test cases * comment * review comment --- analytics/pubmatic/logger_test.go | 134 ++++++ modules/pubmatic/openwrap/defaultbids.go | 9 +- modules/pubmatic/openwrap/defaultbids_test.go | 132 ++++++ modules/pubmatic/openwrap/nonbids.go | 22 + modules/pubmatic/openwrap/nonbids_test.go | 430 ++++++++++++++++++ modules/pubmatic/openwrap/openwrap.go | 3 + 6 files changed, 728 insertions(+), 2 deletions(-) diff --git a/analytics/pubmatic/logger_test.go b/analytics/pubmatic/logger_test.go index 2f7ddb7ecca..65d2e6d048e 100644 --- a/analytics/pubmatic/logger_test.go +++ b/analytics/pubmatic/logger_test.go @@ -1930,6 +1930,140 @@ func TestGetPartnerRecordsByImpForSeatNonBid(t *testing.T) { }, }, }, + { + name: "log from seat-non-bid and seat-bid for Endpoint webs2s: here default/proxy bids are present in seat non-bid", + args: args{ + ao: analytics.AuctionObject{ + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Seat: "pubmatic", + Bid: []openrtb2.Bid{ + { + ID: "bid-id-2", + Price: 30, + W: 10, + H: 50, + ImpID: "imp1", + Ext: json.RawMessage(`{"origbidcpm":30}`), + }, + }, + }, + }, + }, + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "appnexus", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: int(exchange.ResponseRejectedBelowFloor), + Ext: openrtb_ext.ExtNonBid{ + Prebid: openrtb_ext.ExtNonBidPrebid{ + Bid: openrtb_ext.ExtNonBidPrebidBid{ + Price: 10, + ID: "bid-id-1", + W: 10, + H: 50, + OriginalBidCPM: 10, + }, + }, + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + PriceGranularity: &pg, + CurrencyConversion: func(from, to string, value float64) (float64, error) { + if from == "USD" && to == "EUR" { + return value * 1.2, nil + } + if from == "EUR" && to == "USD" { + return value * 0.8, nil + } + return 0, nil + }, + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + Bidders: map[string]models.PartnerData{ + "appnexus": { + PartnerID: 1, + PrebidBidderCode: "appnexus", + KGP: "kgp", + KGPV: "kgpv", + }, + "pubmatic": { + PartnerID: 2, + PrebidBidderCode: "pubmatic", + KGP: "kgp", + KGPV: "kgpv", + }, + }, + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + ExtBid: openrtb_ext.ExtBid{}, + Nbr: exchange.ResponseRejectedBelowFloor.Ptr(), + }, + }, + }, + BidFloor: 10.5, + BidFloorCur: "USD", + }, + }, + PartnerConfigMap: map[int]map[string]string{ + 1: { + "rev_share": "0", + }, + }, + WinningBids: make(models.WinningBids), + Platform: models.PLATFORM_APP, + }, + }, + partners: map[string][]PartnerRecord{ + "imp1": { + { + PartnerID: "appnexus", + BidderCode: "appnexus", + KGPV: "kgpv", + KGPSV: "kgpv", + PartnerSize: "10x50", + GrossECPM: 10, + NetECPM: 10, + BidID: "bid-id-1", + OrigBidID: "bid-id-1", + DealID: "-1", + ServerSide: 1, + OriginalCPM: 0, + OriginalCur: models.USD, + FloorValue: 10.5, + FloorRuleValue: 10.5, + PriceBucket: "10.00", + Nbr: exchange.ResponseRejectedBelowFloor.Ptr(), + }, + { + PartnerID: "pubmatic", + BidderCode: "pubmatic", + KGPV: "kgpv", + KGPSV: "kgpv", + PartnerSize: "10x50", + GrossECPM: 30, + NetECPM: 30, + BidID: "bid-id-2", + OrigBidID: "bid-id-2", + DealID: "-1", + ServerSide: 1, + OriginalCPM: 0, + OriginalCur: models.USD, + FloorValue: 10.5, + FloorRuleValue: 10.5, + PriceBucket: "20.00", + }, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/modules/pubmatic/openwrap/defaultbids.go b/modules/pubmatic/openwrap/defaultbids.go index af2a3fedb08..ebc3af57eae 100644 --- a/modules/pubmatic/openwrap/defaultbids.go +++ b/modules/pubmatic/openwrap/defaultbids.go @@ -55,8 +55,7 @@ func (m *OpenWrap) addDefaultBids(rctx *models.RequestCtx, bidResponse *openrtb2 if defaultBids[impID] == nil { defaultBids[impID] = make(map[string][]openrtb2.Bid) } - - uuid := uuid.NewV4().String() + uuid, _ := m.uuidGenerator.Generate() bidExt := newDefaultBidExt(*rctx, impID, bidder, bidResponseExt) bidExtJson, _ := json.Marshal(bidExt) @@ -128,6 +127,12 @@ func (m *OpenWrap) addDefaultBids(rctx *models.RequestCtx, bidResponse *openrtb2 } } + //Do not add nobids in default bids for throttled adapter and non-mapped bidders in case of web-s2s + //as we are forming forming seatNonBids from defaultBids which is used for owlogger + if rctx.Endpoint == models.EndpointWebS2S { + return defaultBids + } + // add nobids for throttled adapter to all the impressions (how do we set profile with custom list of bidders at impression level?) for bidder := range rctx.AdapterThrottleMap { for impID := range rctx.ImpBidCtx { // ImpBidCtx is used only for list of impID, it does not have data of throttled adapters diff --git a/modules/pubmatic/openwrap/defaultbids_test.go b/modules/pubmatic/openwrap/defaultbids_test.go index 7e493d4c373..b321aa9687a 100644 --- a/modules/pubmatic/openwrap/defaultbids_test.go +++ b/modules/pubmatic/openwrap/defaultbids_test.go @@ -3,13 +3,33 @@ package openwrap import ( "testing" + "github.com/golang/mock/gomock" + "github.com/prebid/openrtb/v20/openrtb2" "github.com/prebid/openrtb/v20/openrtb3" + "github.com/prebid/prebid-server/v2/currency" "github.com/prebid/prebid-server/v2/errortypes" "github.com/prebid/prebid-server/v2/exchange" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/config" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/geodb" + metrics "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics" + mock_metrics "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics/mock" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/profilemetadata" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/publisherfeature" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/unwrap" "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/uuidutil" "github.com/stretchr/testify/assert" ) +const fakeUuid = "30470a14-2949-4110-abce-b62d57304ad5" + +type TestUUIDGenerator struct{} + +func (TestUUIDGenerator) Generate() (string, error) { + return fakeUuid, nil +} + func TestGetNonBRCodeFromBidRespExt(t *testing.T) { type args struct { bidder string @@ -86,3 +106,115 @@ func TestGetNonBRCodeFromBidRespExt(t *testing.T) { }) } } + +func TestOpenWrap_addDefaultBids(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) + type fields struct { + cfg config.Config + rateConvertor *currency.RateConverter + metricEngine metrics.MetricsEngine + geoInfoFetcher geodb.Geography + pubFeatures publisherfeature.Feature + unwrap unwrap.Unwrap + profileMetaData profilemetadata.ProfileMetaData + uuidGenerator uuidutil.UUIDGenerator + } + type args struct { + rctx *models.RequestCtx + bidResponse *openrtb2.BidResponse + bidResponseExt openrtb_ext.ExtBidResponse + } + tests := []struct { + name string + fields fields + args args + setup func() + want map[string]map[string][]openrtb2.Bid + }{ + { + name: "EndpointWebS2S do not add default bids for slot-not-mapped and partner-throttled", + fields: fields{ + metricEngine: mockEngine, + uuidGenerator: TestUUIDGenerator{}, + }, + args: args{ + rctx: &models.RequestCtx{ + Endpoint: models.EndpointWebS2S, + ImpBidCtx: map[string]models.ImpCtx{ + "imp-1": { + Bidders: map[string]models.PartnerData{ + "pubmatic": { + PrebidBidderCode: "pubmatic", + }, + "openx": { + PrebidBidderCode: "openx", + }, + }, + NonMapped: map[string]struct{}{ + "appnexus": {}, + }, + BidCtx: map[string]models.BidCtx{ + "pubmatic-bid-1": { + BidExt: models.BidExt{}, + }, + }, + }, + }, + AdapterThrottleMap: map[string]struct{}{ + "rubicon": {}, + }, + }, + bidResponse: &openrtb2.BidResponse{ + ID: "bid-1", + SeatBid: []openrtb2.SeatBid{ + { + Seat: "pubmatic", + Bid: []openrtb2.Bid{ + { + ID: "pubmatic-bid-1", + ImpID: "imp-1", + Price: 1.0, + }, + }, + }, + }, + }, + }, + setup: func() { + mockEngine.EXPECT().RecordPartnerResponseErrors(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + }, + want: map[string]map[string][]openrtb2.Bid{ + "imp-1": { + "openx": { + { + ID: "30470a14-2949-4110-abce-b62d57304ad5", + ImpID: "imp-1", + Ext: []byte(`{}`), + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } + m := &OpenWrap{ + cfg: tt.fields.cfg, + metricEngine: tt.fields.metricEngine, + rateConvertor: tt.fields.rateConvertor, + geoInfoFetcher: tt.fields.geoInfoFetcher, + pubFeatures: tt.fields.pubFeatures, + unwrap: tt.fields.unwrap, + profileMetaData: tt.fields.profileMetaData, + uuidGenerator: tt.fields.uuidGenerator, + } + got := m.addDefaultBids(tt.args.rctx, tt.args.bidResponse, tt.args.bidResponseExt) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/modules/pubmatic/openwrap/nonbids.go b/modules/pubmatic/openwrap/nonbids.go index b0d13c94627..888d453c5e1 100644 --- a/modules/pubmatic/openwrap/nonbids.go +++ b/modules/pubmatic/openwrap/nonbids.go @@ -30,9 +30,31 @@ func prepareSeatNonBids(rctx models.RequestCtx) openrtb_ext.NonBidCollection { seatNonBid.AddBid(nonBid, bidder) } } + + // Update seat-non-bids with default-bids for the web-s2s endpoint + // In other endpoints, default bids are added to response.seatbid, but for web-s2s, we must return a vanilla prebid response. + if rctx.Endpoint == models.EndpointWebS2S { + updateSeatNonBidsFromDefaultBids(rctx, &seatNonBid) + } + return seatNonBid } +func updateSeatNonBidsFromDefaultBids(rctx models.RequestCtx, seatNonBid *openrtb_ext.NonBidCollection) { + for impID, defaultBid := range rctx.DefaultBids { + for seat, bids := range defaultBid { + for _, bid := range bids { + if rctx.ImpBidCtx != nil && rctx.ImpBidCtx[impID].BidCtx != nil && rctx.ImpBidCtx[impID].BidCtx[bid.ID].Nbr != nil { + nbr := rctx.ImpBidCtx[impID].BidCtx[bid.ID].Nbr + nonBid := openrtb_ext.NewNonBid(openrtb_ext.NonBidParams{Bid: &openrtb2.Bid{ImpID: impID}, NonBidReason: int(*nbr)}) + seatNonBid.AddBid(nonBid, seat) + } + + } + } + } +} + // addSeatNonBidsInResponseExt adds the rctx.SeatNonBids in the response-ext func addSeatNonBidsInResponseExt(rctx models.RequestCtx, responseExt *openrtb_ext.ExtBidResponse) { if len(rctx.SeatNonBids) == 0 { diff --git a/modules/pubmatic/openwrap/nonbids_test.go b/modules/pubmatic/openwrap/nonbids_test.go index 0d00ea2a0b9..a3d0b7db039 100644 --- a/modules/pubmatic/openwrap/nonbids_test.go +++ b/modules/pubmatic/openwrap/nonbids_test.go @@ -3,7 +3,10 @@ package openwrap import ( "testing" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v2/exchange" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/nbr" "github.com/prebid/prebid-server/v2/openrtb_ext" @@ -130,6 +133,44 @@ func TestPrepareSeatNonBids(t *testing.T) { }, }), }, + { + name: "seatnonbid_should_be_updated_from_defaultbids_from_webs2s_endpoint", + args: args{ + rctx: models.RequestCtx{ + Endpoint: models.EndpointWebS2S, + DefaultBids: map[string]map[string][]openrtb2.Bid{ + "imp1": { + "pubmatic": { + { + ID: "bid-id-1", + ImpID: "imp1", + Ext: []byte(`{}`), + }, + }, + }, + }, + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + Nbr: exchange.ErrorGeneral.Ptr(), + }, + }, + }, + }, + }, + }, + }, + seatNonBids: getNonBids(map[string][]openrtb_ext.NonBidParams{ + "pubmatic": { + { + Bid: &openrtb2.Bid{ImpID: "imp1"}, + NonBidReason: int(exchange.ErrorGeneral), + }, + }, + }), + }, } for _, tt := range tests { @@ -832,3 +873,392 @@ func getNonBids(bidParamsMap map[string][]openrtb_ext.NonBidParams) openrtb_ext. } return nonBids } + +func TestUpdateSeatNonBidsFromDefaultBids(t *testing.T) { + type args struct { + rctx models.RequestCtx + seatNonBid *openrtb_ext.NonBidCollection + } + tests := []struct { + name string + args args + wantSeatNonBid []openrtb_ext.SeatNonBid + }{ + { + name: "no default bids", + args: args{ + rctx: models.RequestCtx{ + DefaultBids: nil, + }, + seatNonBid: &openrtb_ext.NonBidCollection{}, + }, + wantSeatNonBid: nil, + }, + { + name: "imp not present in impbidctx for default bid", + args: args{ + rctx: models.RequestCtx{ + DefaultBids: map[string]map[string][]openrtb2.Bid{ + "imp1": { + "pubmatic": { + { + ID: "bid-id-1", + ImpID: "imp1", + Ext: []byte(`{}`), + }, + }, + }, + }, + ImpBidCtx: map[string]models.ImpCtx{ + "imp2": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + Nbr: exchange.ErrorGeneral.Ptr(), + }, + }, + }, + }, + }, + }, + seatNonBid: &openrtb_ext.NonBidCollection{}, + }, + }, + { + name: "bid absent in impbidctx for default bid", + args: args{ + rctx: models.RequestCtx{ + DefaultBids: map[string]map[string][]openrtb2.Bid{ + "imp1": { + "pubmatic": { + { + ID: "bid-id-1", + ImpID: "imp1", + Ext: []byte(`{}`), + }, + }, + }, + }, + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-2": { + BidExt: models.BidExt{ + Nbr: exchange.ErrorGeneral.Ptr(), + }, + }, + }, + }, + }, + }, + seatNonBid: &openrtb_ext.NonBidCollection{}, + }, + }, + { + name: "default bid with no non-bid reason", + args: args{ + rctx: models.RequestCtx{ + DefaultBids: map[string]map[string][]openrtb2.Bid{ + "imp1": { + "pubmatic": { + { + ID: "bid-id-1", + ImpID: "imp1", + Ext: []byte(`{}`), + }, + }, + }, + }, + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + Nbr: nil, + }, + }, + }, + }, + }, + }, + seatNonBid: &openrtb_ext.NonBidCollection{}, + }, + wantSeatNonBid: nil, + }, + { + name: "singal default bid", + args: args{ + rctx: models.RequestCtx{ + DefaultBids: map[string]map[string][]openrtb2.Bid{ + "imp1": { + "pubmatic": { + { + ID: "bid-id-1", + ImpID: "imp1", + Ext: []byte(`{}`), + }, + }, + }, + }, + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + Nbr: exchange.ErrorGeneral.Ptr(), + }, + }, + }, + }, + }, + }, + seatNonBid: &openrtb_ext.NonBidCollection{}, + }, + wantSeatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "pubmatic", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: 100, + }, + }, + }, + }, + }, + { + name: "multiple default bids for same imp", + args: args{ + rctx: models.RequestCtx{ + DefaultBids: map[string]map[string][]openrtb2.Bid{ + "imp1": { + "pubmatic": { + { + ID: "bid-id-1", + ImpID: "imp1", + }, + }, + "appnexus": { + { + ID: "bid-id-2", + ImpID: "imp1", + }, + }, + "rubicon": { + { + ID: "bid-id-3", + ImpID: "imp1", + }, + }, + }, + }, + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + Nbr: exchange.ErrorGeneral.Ptr(), + }, + }, + "bid-id-2": { + BidExt: models.BidExt{ + Nbr: exchange.ErrorTimeout.Ptr(), + }, + }, + "bid-id-3": { + BidExt: models.BidExt{ + Nbr: exchange.ErrorBidderUnreachable.Ptr(), + }, + }, + }, + }, + }, + }, + seatNonBid: &openrtb_ext.NonBidCollection{}, + }, + wantSeatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "pubmatic", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: 100, + }, + }, + }, + { + Seat: "appnexus", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: 101, + }, + }, + }, + { + Seat: "rubicon", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: 103, + }, + }, + }, + }, + }, + { + name: "multiple default bids for different imp", + args: args{ + rctx: models.RequestCtx{ + DefaultBids: map[string]map[string][]openrtb2.Bid{ + "imp1": { + "pubmatic": { + { + ID: "bid-id-1", + ImpID: "imp1", + Ext: []byte(`{}`), + }, + }, + "appnexus": { + { + ID: "bid-id-2", + ImpID: "imp1", + Ext: []byte(`{}`), + }, + }, + "rubicon": { + { + ID: "bid-id-3", + ImpID: "imp1", + Ext: []byte(`{}`), + }, + }, + }, + "imp2": { + "pubmatic": { + { + ID: "bid-id-4", + ImpID: "imp2", + Ext: []byte(`{}`), + }, + }, + "appnexus": { + { + ID: "bid-id-5", + ImpID: "imp2", + Ext: []byte(`{}`), + }, + }, + "rubicon": { + { + ID: "bid-id-6", + ImpID: "imp2", + Ext: []byte(`{}`), + }, + }, + }, + }, + ImpBidCtx: map[string]models.ImpCtx{ + "imp1": { + BidCtx: map[string]models.BidCtx{ + "bid-id-1": { + BidExt: models.BidExt{ + Nbr: exchange.ErrorGeneral.Ptr(), + }, + }, + "bid-id-2": { + BidExt: models.BidExt{ + Nbr: exchange.ErrorTimeout.Ptr(), + }, + }, + "bid-id-3": { + BidExt: models.BidExt{ + Nbr: exchange.ErrorBidderUnreachable.Ptr(), + }, + }, + }, + }, + "imp2": { + BidCtx: map[string]models.BidCtx{ + "bid-id-4": { + BidExt: models.BidExt{ + Nbr: exchange.ErrorGeneral.Ptr(), + }, + }, + "bid-id-5": { + BidExt: models.BidExt{ + Nbr: exchange.ErrorTimeout.Ptr(), + }, + }, + "bid-id-6": { + BidExt: models.BidExt{ + Nbr: exchange.ErrorBidderUnreachable.Ptr(), + }, + }, + }, + }, + }, + }, + seatNonBid: &openrtb_ext.NonBidCollection{}, + }, + wantSeatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "pubmatic", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: 100, + }, + { + ImpId: "imp2", + StatusCode: 100, + }, + }, + }, + { + Seat: "appnexus", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: 101, + }, + { + ImpId: "imp2", + StatusCode: 101, + }, + }, + }, + { + Seat: "rubicon", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: 103, + }, + { + ImpId: "imp2", + StatusCode: 103, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + updateSeatNonBidsFromDefaultBids(tt.args.rctx, tt.args.seatNonBid) + gotSetaNonBid := tt.args.seatNonBid.Get() + + cmp.Equal(tt.wantSeatNonBid, gotSetaNonBid, + cmpopts.SortSlices(func(a, b openrtb_ext.SeatNonBid) bool { + return a.Seat < b.Seat + }), + cmpopts.SortSlices(sortNonBids), + ) + }) + } +} + +func sortNonBids(a, b openrtb_ext.NonBid) bool { + return a.ImpId < b.ImpId +} diff --git a/modules/pubmatic/openwrap/openwrap.go b/modules/pubmatic/openwrap/openwrap.go index fe88c71e94a..e4fa5fa8c21 100644 --- a/modules/pubmatic/openwrap/openwrap.go +++ b/modules/pubmatic/openwrap/openwrap.go @@ -28,6 +28,7 @@ import ( "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/profilemetadata" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/publisherfeature" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/unwrap" + "github.com/prebid/prebid-server/v2/util/uuidutil" ) const ( @@ -43,6 +44,7 @@ type OpenWrap struct { pubFeatures publisherfeature.Feature unwrap unwrap.Unwrap profileMetaData profilemetadata.ProfileMetaData + uuidGenerator uuidutil.UUIDGenerator } var ow *OpenWrap @@ -125,6 +127,7 @@ func initOpenWrap(rawCfg json.RawMessage, moduleDeps moduledeps.ModuleDeps) (Ope pubFeatures: pubFeatures, unwrap: uw, profileMetaData: profileMetaData, + uuidGenerator: uuidutil.UUIDRandomGenerator{}, } })