diff --git a/modules/pubmatic/openwrap/auctionresponsehook.go b/modules/pubmatic/openwrap/auctionresponsehook.go index 6b4011d0100..cacda092cfa 100644 --- a/modules/pubmatic/openwrap/auctionresponsehook.go +++ b/modules/pubmatic/openwrap/auctionresponsehook.go @@ -67,6 +67,11 @@ func (m OpenWrap) handleAuctionResponseHook( return result, nil } + //Impression counting method enabled bidders + if rctx.Endpoint == models.EndpointV25 || rctx.Endpoint == models.EndpointAppLovinMax { + rctx.ImpCountingMethodEnabledBidders = m.pubFeatures.GetImpCountingMethodEnabledBidders() + } + var winningAdpodBidIds map[string][]string var errs []error if rctx.IsCTVRequest { diff --git a/modules/pubmatic/openwrap/auctionresponsehook_test.go b/modules/pubmatic/openwrap/auctionresponsehook_test.go index db94524cb85..5d1a51e682b 100644 --- a/modules/pubmatic/openwrap/auctionresponsehook_test.go +++ b/modules/pubmatic/openwrap/auctionresponsehook_test.go @@ -1921,6 +1921,7 @@ func TestAuctionResponseHookForApplovinMax(t *testing.T) { mockEngine.EXPECT().RecordPublisherResponseTimeStats(gomock.Any(), gomock.Any()) mockFeature.EXPECT().IsFscApplicable(gomock.Any(), gomock.Any(), gomock.Any()).Return(false) mockEngine.EXPECT().RecordPartnerResponseTimeStats(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + mockFeature.EXPECT().GetImpCountingMethodEnabledBidders().Return(map[string]struct{}{}) return mockEngine }, }, diff --git a/modules/pubmatic/openwrap/entrypointhook.go b/modules/pubmatic/openwrap/entrypointhook.go index c8d3fb5679d..d5b7eb6c2b0 100644 --- a/modules/pubmatic/openwrap/entrypointhook.go +++ b/modules/pubmatic/openwrap/entrypointhook.go @@ -134,7 +134,8 @@ func (m OpenWrap) handleEntrypointHook( WakandaDebug: &wakanda.Debug{ Config: m.cfg.Wakanda, }, - SendBurl: endpoint == models.EndpointAppLovinMax || getSendBurl(payload.Body), + SendBurl: endpoint == models.EndpointAppLovinMax || getSendBurl(payload.Body), + ImpCountingMethodEnabledBidders: make(map[string]struct{}), } if rCtx.IsCTVRequest { diff --git a/modules/pubmatic/openwrap/entrypointhook_test.go b/modules/pubmatic/openwrap/entrypointhook_test.go index 7455afd2bba..429d0fb3e4f 100644 --- a/modules/pubmatic/openwrap/entrypointhook_test.go +++ b/modules/pubmatic/openwrap/entrypointhook_test.go @@ -126,17 +126,18 @@ func TestOpenWrap_handleEntrypointHook(t *testing.T) { 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: "5890", - Endpoint: models.EndpointV25, - MetricsEngine: mockEngine, - SeatNonBids: make(map[string][]openrtb_ext.NonBid), - Method: "POST", - WakandaDebug: &wakanda.Debug{}, + 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: "5890", + Endpoint: models.EndpointV25, + MetricsEngine: mockEngine, + SeatNonBids: make(map[string][]openrtb_ext.NonBid), + Method: "POST", + WakandaDebug: &wakanda.Debug{}, + ImpCountingMethodEnabledBidders: make(map[string]struct{}), }, }, }, @@ -173,29 +174,30 @@ func TestOpenWrap_handleEntrypointHook(t *testing.T) { want: hookstage.HookResult[hookstage.EntrypointPayload]{ ModuleContext: hookstage.ModuleContext{ "rctx": models.RequestCtx{ - PubIDStr: "5890", - PubID: 5890, - ProfileID: 5890, - DisplayID: 1, - DisplayVersionID: 1, - SSAuction: -1, - Debug: true, - UA: "go-test", - IP: "127.0.0.1", - IsCTVRequest: false, - TrackerEndpoint: "t.pubmatic.com", - VideoErrorTrackerEndpoint: "t.pubmatic.com/error", - LoggerImpressionID: "4df09505-d0b2-4d70-94d9-dc41e8e777f7", - Aliases: make(map[string]string), - ImpBidCtx: make(map[string]models.ImpCtx), - PrebidBidderCode: make(map[string]string), - BidderResponseTimeMillis: make(map[string]int), - ProfileIDStr: "5890", - Endpoint: models.EndpointV25, - MetricsEngine: mockEngine, - SeatNonBids: make(map[string][]openrtb_ext.NonBid), - Method: "POST", - WakandaDebug: &wakanda.Debug{}, + PubIDStr: "5890", + PubID: 5890, + ProfileID: 5890, + DisplayID: 1, + DisplayVersionID: 1, + SSAuction: -1, + Debug: true, + UA: "go-test", + IP: "127.0.0.1", + IsCTVRequest: false, + TrackerEndpoint: "t.pubmatic.com", + VideoErrorTrackerEndpoint: "t.pubmatic.com/error", + LoggerImpressionID: "4df09505-d0b2-4d70-94d9-dc41e8e777f7", + Aliases: make(map[string]string), + ImpBidCtx: make(map[string]models.ImpCtx), + PrebidBidderCode: make(map[string]string), + BidderResponseTimeMillis: make(map[string]int), + ProfileIDStr: "5890", + Endpoint: models.EndpointV25, + MetricsEngine: mockEngine, + SeatNonBids: make(map[string][]openrtb_ext.NonBid), + Method: "POST", + WakandaDebug: &wakanda.Debug{}, + ImpCountingMethodEnabledBidders: make(map[string]struct{}), }, }, }, @@ -276,17 +278,18 @@ func TestOpenWrap_handleEntrypointHook(t *testing.T) { 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: "43563", - Endpoint: models.EndpointWebS2S, - MetricsEngine: mockEngine, - SeatNonBids: make(map[string][]openrtb_ext.NonBid), - Method: "POST", - WakandaDebug: &wakanda.Debug{}, + 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: "43563", + Endpoint: models.EndpointWebS2S, + MetricsEngine: mockEngine, + SeatNonBids: make(map[string][]openrtb_ext.NonBid), + Method: "POST", + WakandaDebug: &wakanda.Debug{}, + ImpCountingMethodEnabledBidders: make(map[string]struct{}), }, }, }, @@ -337,17 +340,18 @@ func TestOpenWrap_handleEntrypointHook(t *testing.T) { 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: "43563", - Endpoint: models.EndpointWebS2S, - MetricsEngine: mockEngine, - SeatNonBids: make(map[string][]openrtb_ext.NonBid), - Method: "POST", - WakandaDebug: &wakanda.Debug{}, + 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: "43563", + Endpoint: models.EndpointWebS2S, + MetricsEngine: mockEngine, + SeatNonBids: make(map[string][]openrtb_ext.NonBid), + Method: "POST", + WakandaDebug: &wakanda.Debug{}, + ImpCountingMethodEnabledBidders: make(map[string]struct{}), }, }, }, @@ -500,17 +504,18 @@ func TestOpenWrap_handleEntrypointHook(t *testing.T) { 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: "5890", - Endpoint: models.EndpointV25, - MetricsEngine: mockEngine, - SeatNonBids: make(map[string][]openrtb_ext.NonBid), - Method: "POST", - WakandaDebug: &wakanda.Debug{}, + 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: "5890", + Endpoint: models.EndpointV25, + MetricsEngine: mockEngine, + SeatNonBids: make(map[string][]openrtb_ext.NonBid), + Method: "POST", + WakandaDebug: &wakanda.Debug{}, + ImpCountingMethodEnabledBidders: make(map[string]struct{}), }, }, }, @@ -593,7 +598,8 @@ func TestOpenWrap_handleEntrypointHook(t *testing.T) { }, }, }, - SendBurl: true, + SendBurl: true, + ImpCountingMethodEnabledBidders: make(map[string]struct{}), }, }, }, diff --git a/modules/pubmatic/openwrap/models/constants.go b/modules/pubmatic/openwrap/models/constants.go index df069be9401..d55973c7bf0 100755 --- a/modules/pubmatic/openwrap/models/constants.go +++ b/modules/pubmatic/openwrap/models/constants.go @@ -357,6 +357,9 @@ const ( PixelPosAbove = "above" PixelPosBelow = "below" + //constants for tracker impCountingMethod + ImpCountingMethod = "imp_ct_mthd" + DealIDNotApplicable = "na" DealTierNotApplicable = "na" PwtDealTier = "pwtdealtier" @@ -593,6 +596,7 @@ const ( FeatureMaxFloors = 5 FeatureBidRecovery = 6 FeatureApplovinMultiFloors = 7 + FeatureImpCountingMethod = 8 ) // constants for applovinmax requests diff --git a/modules/pubmatic/openwrap/models/openwrap.go b/modules/pubmatic/openwrap/models/openwrap.go index 1dff3e33d82..05c088284ea 100644 --- a/modules/pubmatic/openwrap/models/openwrap.go +++ b/modules/pubmatic/openwrap/models/openwrap.go @@ -92,36 +92,37 @@ type RequestCtx struct { BidderResponseTimeMillis map[string]int - Endpoint string - PubIDStr, ProfileIDStr string // TODO: remove this once we completely move away from header-bidding - MetricsEngine metrics.MetricsEngine - ReturnAllBidStatus bool // ReturnAllBidStatus stores the value of request.ext.prebid.returnallbidstatus - Sshb string //Sshb query param to identify that the request executed heder-bidding or not, sshb=1(executed HB(8001)), sshb=2(reverse proxy set from HB(8001->8000)), sshb=""(direct request(8000)). - DCName string - CachePutMiss int // to be used in case of CTV JSON endpoint/amp/inapp-ott-video endpoint - CurrencyConversion func(from string, to string, value float64) (float64, error) - MatchedImpression map[string]int - CustomDimensions map[string]CustomDimension - AmpVideoEnabled bool //AmpVideoEnabled indicates whether to include a Video object in an AMP request. - IsTBFFeatureEnabled bool - VastUnwrapEnabled bool - VastUnwrapStatsEnabled bool - AppLovinMax AppLovinMax - LoggerDisabled bool - TrackerDisabled bool - ProfileType int - ProfileTypePlatform int - AppPlatform int - AppIntegrationPath *int - AppSubIntegrationPath *int - Method string - Errors []error - RedirectURL string - ResponseFormat string - WakandaDebug wakanda.WakandaDebug - PriceGranularity *openrtb_ext.PriceGranularity - IsMaxFloorsEnabled bool - SendBurl bool + Endpoint string + PubIDStr, ProfileIDStr string // TODO: remove this once we completely move away from header-bidding + MetricsEngine metrics.MetricsEngine + ReturnAllBidStatus bool // ReturnAllBidStatus stores the value of request.ext.prebid.returnallbidstatus + Sshb string //Sshb query param to identify that the request executed heder-bidding or not, sshb=1(executed HB(8001)), sshb=2(reverse proxy set from HB(8001->8000)), sshb=""(direct request(8000)). + DCName string + CachePutMiss int // to be used in case of CTV JSON endpoint/amp/inapp-ott-video endpoint + CurrencyConversion func(from string, to string, value float64) (float64, error) + MatchedImpression map[string]int + CustomDimensions map[string]CustomDimension + AmpVideoEnabled bool //AmpVideoEnabled indicates whether to include a Video object in an AMP request. + IsTBFFeatureEnabled bool + VastUnwrapEnabled bool + VastUnwrapStatsEnabled bool + AppLovinMax AppLovinMax + LoggerDisabled bool + TrackerDisabled bool + ProfileType int + ProfileTypePlatform int + AppPlatform int + AppIntegrationPath *int + AppSubIntegrationPath *int + Method string + Errors []error + RedirectURL string + ResponseFormat string + WakandaDebug wakanda.WakandaDebug + PriceGranularity *openrtb_ext.PriceGranularity + IsMaxFloorsEnabled bool + SendBurl bool + ImpCountingMethodEnabledBidders map[string]struct{} // Bidders who have enabled ImpCountingMethod feature // Adpod AdruleFlag bool diff --git a/modules/pubmatic/openwrap/models/tracker.go b/modules/pubmatic/openwrap/models/tracker.go index 4d3d6c0b3af..74822a37cb3 100644 --- a/modules/pubmatic/openwrap/models/tracker.go +++ b/modules/pubmatic/openwrap/models/tracker.go @@ -9,7 +9,7 @@ type OWTracker struct { PriceModel string PriceCurrency string BidType string `json:"-"` // video, banner, native - DspId int `json:"-"` // dsp id + IsOMEnabled bool `json:"-"` // is om enabled } // Tracker tracker url creation parameters diff --git a/modules/pubmatic/openwrap/publisherfeature/feature.go b/modules/pubmatic/openwrap/publisherfeature/feature.go index ae8fc05ee32..1c4e785cc86 100644 --- a/modules/pubmatic/openwrap/publisherfeature/feature.go +++ b/modules/pubmatic/openwrap/publisherfeature/feature.go @@ -11,4 +11,5 @@ type Feature interface { IsBidRecoveryEnabled(pubID int, profileID int) bool IsApplovinMultiFloorsEnabled(pubID int, profileID string) bool GetApplovinMultiFloors(pubID int, profileID string) models.ApplovinAdUnitFloors + GetImpCountingMethodEnabledBidders() map[string]struct{} } diff --git a/modules/pubmatic/openwrap/publisherfeature/impcountingmethod.go b/modules/pubmatic/openwrap/publisherfeature/impcountingmethod.go new file mode 100644 index 00000000000..8c51b5715ad --- /dev/null +++ b/modules/pubmatic/openwrap/publisherfeature/impcountingmethod.go @@ -0,0 +1,48 @@ +package publisherfeature + +import ( + "strings" + + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" +) + +type impCountingMethod struct { + enabledBidders [2]map[string]struct{} + index int32 +} + +func newImpCountingMethod() impCountingMethod { + return impCountingMethod{ + enabledBidders: [2]map[string]struct{}{ + make(map[string]struct{}), + make(map[string]struct{}), + }, + index: 0, + } +} + +func (fe *feature) updateImpCountingMethodEnabledBidders() { + if fe.publisherFeature == nil { + return + } + + enabledBidders := make(map[string]struct{}) + for pubID, feature := range fe.publisherFeature { + if val, ok := feature[models.FeatureImpCountingMethod]; ok && pubID == 0 && val.Enabled == 1 { + bidders := strings.Split(val.Value, ",") + for _, bidder := range bidders { + bidder = strings.TrimSpace(bidder) + if bidder != "" { + enabledBidders[bidder] = struct{}{} + } + } + } + } + + fe.impCountingMethod.enabledBidders[fe.impCountingMethod.index^1] = enabledBidders + fe.impCountingMethod.index ^= 1 +} + +func (fe *feature) GetImpCountingMethodEnabledBidders() map[string]struct{} { + return fe.impCountingMethod.enabledBidders[fe.impCountingMethod.index] +} diff --git a/modules/pubmatic/openwrap/publisherfeature/impcountingmethod_test.go b/modules/pubmatic/openwrap/publisherfeature/impcountingmethod_test.go new file mode 100644 index 00000000000..61d95b4aac9 --- /dev/null +++ b/modules/pubmatic/openwrap/publisherfeature/impcountingmethod_test.go @@ -0,0 +1,267 @@ +package publisherfeature + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/cache" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/stretchr/testify/assert" +) + +func TestFeatureUpdateImpCountingMethodEnabledBidders(t *testing.T) { + type fields struct { + cache cache.Cache + publisherFeature map[int]map[int]models.FeatureData + impCountingMethod impCountingMethod + } + tests := []struct { + name string + fields fields + wantImpCoutingMethodEnabledBidders [2]map[string]struct{} + wantImpCoutingMethodIndex int32 + }{ + { + name: "publisherFeature_map_is_nil", + fields: fields{ + cache: nil, + publisherFeature: nil, + impCountingMethod: newImpCountingMethod(), + }, + wantImpCoutingMethodEnabledBidders: [2]map[string]struct{}{ + make(map[string]struct{}), + make(map[string]struct{}), + }, + wantImpCoutingMethodIndex: 0, + }, + { + name: "publisherFeature_map_is_present_but_impCountingMethod_is_not_present_in_DB", + fields: fields{ + cache: nil, + publisherFeature: map[int]map[int]models.FeatureData{}, + impCountingMethod: newImpCountingMethod(), + }, + wantImpCoutingMethodEnabledBidders: [2]map[string]struct{}{ + {}, + {}, + }, + wantImpCoutingMethodIndex: 1, + }, + { + name: "update _imp_counting_method_enabled_bidders", + fields: fields{ + cache: nil, + publisherFeature: map[int]map[int]models.FeatureData{ + 0: { + models.FeatureImpCountingMethod: { + Enabled: 1, + Value: `appnexus,rubicon`, + }, + }, + }, + impCountingMethod: newImpCountingMethod(), + }, + wantImpCoutingMethodEnabledBidders: [2]map[string]struct{}{ + {}, + { + "appnexus": {}, + "rubicon": {}, + }, + }, + wantImpCoutingMethodIndex: 1, + }, + { + name: "update _imp_counting_method_enabled_bidders_with_bidders_in_flip_map", + fields: fields{ + cache: nil, + publisherFeature: map[int]map[int]models.FeatureData{ + 0: { + models.FeatureImpCountingMethod: { + Enabled: 1, + Value: `appnexus,rubicon`, + }, + }, + }, + impCountingMethod: impCountingMethod{ + enabledBidders: [2]map[string]struct{}{ + { + "magnite": {}, + "ix": {}, + }, + { + "pgam": {}, + "ix": {}, + }, + }, + index: 0, + }, + }, + wantImpCoutingMethodEnabledBidders: [2]map[string]struct{}{ + { + "magnite": {}, + "ix": {}, + }, + { + "appnexus": {}, + "rubicon": {}, + }, + }, + wantImpCoutingMethodIndex: 1, + }, + { + name: "update_imp_counting_method_enabled_bidders_with_space_in_value", + fields: fields{ + cache: nil, + publisherFeature: map[int]map[int]models.FeatureData{ + 0: { + models.FeatureImpCountingMethod: { + Enabled: 1, + Value: ` appnexus,rubicon `, + }, + }, + }, + impCountingMethod: newImpCountingMethod(), + }, + wantImpCoutingMethodEnabledBidders: [2]map[string]struct{}{ + {}, + { + "appnexus": {}, + "rubicon": {}, + }, + }, + wantImpCoutingMethodIndex: 1, + }, + { + name: "update_imp_counting_method_with_feature_disabled", + fields: fields{ + cache: nil, + publisherFeature: map[int]map[int]models.FeatureData{ + 0: { + models.FeatureImpCountingMethod: { + Enabled: 0, + Value: `appnexus,rubicon`, + }, + }, + }, + impCountingMethod: newImpCountingMethod(), + }, + wantImpCoutingMethodEnabledBidders: [2]map[string]struct{}{ + {}, + {}, + }, + wantImpCoutingMethodIndex: 1, + }, + { + name: "update_imp_counting_method_with_feature_disabled", + fields: fields{ + cache: nil, + publisherFeature: map[int]map[int]models.FeatureData{ + 0: { + models.FeatureImpCountingMethod: { + Enabled: 0, + Value: `appnexus,rubicon`, + }, + }, + }, + impCountingMethod: newImpCountingMethod(), + }, + wantImpCoutingMethodEnabledBidders: [2]map[string]struct{}{ + {}, + {}, + }, + wantImpCoutingMethodIndex: 1, + }, + { + name: "update_imp_counting_method_with_feature_enabled_but_empty_value", + fields: fields{ + cache: nil, + publisherFeature: map[int]map[int]models.FeatureData{ + 0: { + models.FeatureImpCountingMethod: { + Enabled: 1, + Value: ``, + }, + }, + }, + impCountingMethod: newImpCountingMethod(), + }, + wantImpCoutingMethodEnabledBidders: [2]map[string]struct{}{ + {}, + {}, + }, + wantImpCoutingMethodIndex: 1, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var fe *feature + fe = &feature{ + publisherFeature: tt.fields.publisherFeature, + impCountingMethod: tt.fields.impCountingMethod, + } + defer func() { + fe = nil + }() + fe.updateImpCountingMethodEnabledBidders() + assert.Equal(t, tt.wantImpCoutingMethodEnabledBidders, fe.impCountingMethod.enabledBidders) + assert.Equal(t, tt.wantImpCoutingMethodIndex, fe.impCountingMethod.index) + }) + } +} + +func TestFeatureGetImpCountingMethodEnabledBidders(t *testing.T) { + type fields struct { + impCountingMethod impCountingMethod + } + tests := []struct { + name string + fields fields + want map[string]struct{} + }{ + { + name: "get_imp_counting_method_enabled_bidders_when_index_is_0", + fields: fields{ + impCountingMethod: impCountingMethod{ + enabledBidders: [2]map[string]struct{}{ + { + "appnexus": {}, + "rubicon": {}, + }, + }, + index: 0, + }, + }, + want: map[string]struct{}{ + "appnexus": {}, + "rubicon": {}, + }, + }, + { + name: "get_imp_counting_method_enabled_bidders_when_index_is_1", + fields: fields{ + impCountingMethod: impCountingMethod{ + enabledBidders: [2]map[string]struct{}{ + {}, + { + "appnexus": {}, + "rubicon": {}, + }, + }, + index: 1, + }, + }, + want: map[string]struct{}{ + "appnexus": {}, + "rubicon": {}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fe := &feature{ + impCountingMethod: tt.fields.impCountingMethod, + } + got := fe.GetImpCountingMethodEnabledBidders() + assert.Equal(t, tt.want, got, tt.name) + }) + } +} diff --git a/modules/pubmatic/openwrap/publisherfeature/mock/mock.go b/modules/pubmatic/openwrap/publisherfeature/mock/mock.go index f27748a1c8e..d22eb6b28ff 100644 --- a/modules/pubmatic/openwrap/publisherfeature/mock/mock.go +++ b/modules/pubmatic/openwrap/publisherfeature/mock/mock.go @@ -47,6 +47,20 @@ func (mr *MockFeatureMockRecorder) GetApplovinMultiFloors(arg0, arg1 interface{} return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetApplovinMultiFloors", reflect.TypeOf((*MockFeature)(nil).GetApplovinMultiFloors), arg0, arg1) } +// GetImpCountingMethodEnabledBidders mocks base method +func (m *MockFeature) GetImpCountingMethodEnabledBidders() map[string]struct{} { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetImpCountingMethodEnabledBidders") + ret0, _ := ret[0].(map[string]struct{}) + return ret0 +} + +// GetImpCountingMethodEnabledBidders indicates an expected call of GetImpCountingMethodEnabledBidders +func (mr *MockFeatureMockRecorder) GetImpCountingMethodEnabledBidders() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetImpCountingMethodEnabledBidders", reflect.TypeOf((*MockFeature)(nil).GetImpCountingMethodEnabledBidders)) +} + // IsAmpMultiformatEnabled mocks base method func (m *MockFeature) IsAmpMultiformatEnabled(arg0 int) bool { m.ctrl.T.Helper() diff --git a/modules/pubmatic/openwrap/publisherfeature/reloader.go b/modules/pubmatic/openwrap/publisherfeature/reloader.go index a5ded2cc516..6aafe9beee2 100644 --- a/modules/pubmatic/openwrap/publisherfeature/reloader.go +++ b/modules/pubmatic/openwrap/publisherfeature/reloader.go @@ -28,6 +28,7 @@ type feature struct { maxFloors maxFloors bidRecovery bidRecovery appLovinMultiFloors appLovinMultiFloors + impCountingMethod impCountingMethod } var fe *feature @@ -60,6 +61,7 @@ func New(config Config) *feature { appLovinMultiFloors: appLovinMultiFloors{ enabledPublisherProfile: make(map[int]map[string]models.ApplovinAdUnitFloors), }, + impCountingMethod: newImpCountingMethod(), } }) return fe @@ -117,6 +119,7 @@ func (fe *feature) updateFeatureConfigMaps() { fe.updateAnalyticsThrottling() fe.updateBidRecoveryEnabledPublishers() fe.updateApplovinMultiFloorsFeature() + fe.updateImpCountingMethodEnabledBidders() if err != nil { glog.Error(err.Error()) diff --git a/modules/pubmatic/openwrap/publisherfeature/reloader_test.go b/modules/pubmatic/openwrap/publisherfeature/reloader_test.go index b432d95971e..2c351c5dce9 100644 --- a/modules/pubmatic/openwrap/publisherfeature/reloader_test.go +++ b/modules/pubmatic/openwrap/publisherfeature/reloader_test.go @@ -113,7 +113,7 @@ func Test_feature_Start(t *testing.T) { } } -func Test_feature_updateFeatureConfigMaps(t *testing.T) { +func TestFeatureUpdateFeatureConfigMaps(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() mockCache := mock_cache.NewMockCache(ctrl) @@ -127,6 +127,7 @@ func Test_feature_updateFeatureConfigMaps(t *testing.T) { ampMultiformat ampMultiformat bidRecovery bidRecovery appLovinMultiFloors appLovinMultiFloors + impCountingMethod impCountingMethod } tests := []struct { name string @@ -158,6 +159,13 @@ func Test_feature_updateFeatureConfigMaps(t *testing.T) { tbf: tbf{ pubProfileTraffic: map[int]map[int]int{}, }, + impCountingMethod: impCountingMethod{ + enabledBidders: [2]map[string]struct{}{ + {}, + {}, + }, + index: 0, + }, }, }, { @@ -205,6 +213,13 @@ func Test_feature_updateFeatureConfigMaps(t *testing.T) { bidRecovery: bidRecovery{ enabledPublisherProfile: map[int]map[int]struct{}{}, }, + impCountingMethod: impCountingMethod{ + enabledBidders: [2]map[string]struct{}{ + {}, + {}, + }, + index: 1, + }, }, }, { @@ -256,6 +271,13 @@ func Test_feature_updateFeatureConfigMaps(t *testing.T) { appLovinMultiFloors: appLovinMultiFloors{ enabledPublisherProfile: map[int]map[string]models.ApplovinAdUnitFloors{}, }, + impCountingMethod: impCountingMethod{ + enabledBidders: [2]map[string]struct{}{ + {}, + {}, + }, + index: 1, + }, }, }, { @@ -331,6 +353,60 @@ func Test_feature_updateFeatureConfigMaps(t *testing.T) { }, }, }, + impCountingMethod: impCountingMethod{ + enabledBidders: [2]map[string]struct{}{ + {}, + {}, + }, + index: 1, + }, + }, + }, + { + name: "fetch impcountingmethod feature data with multiple bidders", + fields: fields{ + cache: mockCache, + }, + setup: func() { + mockCache.EXPECT().GetPublisherFeatureMap().Return(map[int]map[int]models.FeatureData{ + 0: { + models.FeatureImpCountingMethod: { + Enabled: 1, + Value: "appnexus, rubicon", + }, + }, + }, nil) + mockCache.EXPECT().GetFSCThresholdPerDSP().Return(map[int]int{6: 100}, nil) + }, + want: want{ + fsc: fsc{ + disabledPublishers: map[int]struct{}{}, + thresholdsPerDsp: map[int]int{ + 6: 100, + }, + }, + ampMultiformat: ampMultiformat{ + enabledPublishers: map[int]struct{}{}, + }, + tbf: tbf{ + pubProfileTraffic: map[int]map[int]int{}, + }, + bidRecovery: bidRecovery{ + enabledPublisherProfile: map[int]map[int]struct{}{}, + }, + appLovinMultiFloors: appLovinMultiFloors{ + enabledPublisherProfile: map[int]map[string]models.ApplovinAdUnitFloors{}, + }, + impCountingMethod: impCountingMethod{ + enabledBidders: [2]map[string]struct{}{ + {}, + { + "appnexus": {}, + "rubicon": {}, + }, + }, + index: 1, + }, }, }, } @@ -349,13 +425,18 @@ func Test_feature_updateFeatureConfigMaps(t *testing.T) { ampMultiformat: ampMultiformat{ enabledPublishers: make(map[int]struct{}), }, + impCountingMethod: newImpCountingMethod(), } + defer func() { + fe = nil + }() fe.updateFeatureConfigMaps() assert.Equal(t, tt.want.fsc, fe.fsc, tt.name) assert.Equal(t, tt.want.tbf, fe.tbf, tt.name) assert.Equal(t, tt.want.ampMultiformat, fe.ampMultiformat, tt.name) assert.Equal(t, tt.want.bidRecovery, fe.bidRecovery, tt.name) assert.Equal(t, tt.want.appLovinMultiFloors, fe.appLovinMultiFloors, tt.name) + assert.Equal(t, tt.want.impCountingMethod, fe.impCountingMethod, tt.name) }) } } diff --git a/modules/pubmatic/openwrap/tracker/banner.go b/modules/pubmatic/openwrap/tracker/banner.go index b092c2e5bdb..96d6fffd8a7 100644 --- a/modules/pubmatic/openwrap/tracker/banner.go +++ b/modules/pubmatic/openwrap/tracker/banner.go @@ -16,7 +16,7 @@ func injectBannerTracker(rctx models.RequestCtx, tracker models.OWTracker, bid o var replacedTrackerStr, trackerFormat string trackerFormat = models.TrackerCallWrap - if trackerWithOM(tracker, rctx.Platform, seat) { + if tracker.IsOMEnabled { trackerFormat = models.TrackerCallWrapOMActive } replacedTrackerStr = strings.Replace(trackerFormat, "${escapedUrl}", tracker.TrackerURL, 1) @@ -40,14 +40,20 @@ func appendUPixelinBanner(adm string, universalPixel []adunitconfig.UniversalPix return adm } -// TrackerWithOM checks for OM active condition for DV360 -func trackerWithOM(tracker models.OWTracker, platform, bidderCode string) bool { - if platform == models.PLATFORM_APP && bidderCode == string(openrtb_ext.BidderPubmatic) { - if tracker.DspId == models.DspId_DV360 { - return true - } +// TrackerWithOM checks for OM active condition for DV360 with Pubmatic and other bidders +func trackerWithOM(rctx models.RequestCtx, prebidPartnerName string, dspID int) bool { + if rctx.Platform != models.PLATFORM_APP { + return false + } + + // check for OM active for DV360 with Pubmatic + if prebidPartnerName == string(openrtb_ext.BidderPubmatic) && dspID == models.DspId_DV360 { + return true } - return false + + // check for OM active for other bidders + _, isPresent := rctx.ImpCountingMethodEnabledBidders[prebidPartnerName] + return isPresent } // applyTBFFeature adds the tracker before or after the actual bid.Adm diff --git a/modules/pubmatic/openwrap/tracker/banner_test.go b/modules/pubmatic/openwrap/tracker/banner_test.go index c72f6c6b4e0..b920af9b0ef 100644 --- a/modules/pubmatic/openwrap/tracker/banner_test.go +++ b/modules/pubmatic/openwrap/tracker/banner_test.go @@ -6,10 +6,11 @@ import ( "github.com/prebid/openrtb/v20/openrtb2" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) -func Test_injectBannerTracker(t *testing.T) { +func TestInjectBannerTracker(t *testing.T) { type args struct { rctx models.RequestCtx tracker models.OWTracker @@ -17,11 +18,14 @@ func Test_injectBannerTracker(t *testing.T) { seat string pixels []adunitconfig.UniversalPixel } + type want struct { + adm string + burl string + } tests := []struct { - name string - args args - wantAdm string - wantBurl string + name string + args args + want want }{ { name: "endpoint_applovinmax", @@ -39,8 +43,10 @@ func Test_injectBannerTracker(t *testing.T) { }, seat: "pubmatic", }, - wantAdm: `
`, - wantBurl: `sample.com/track?tid=1234&owsspburl=http://burl.com`, + want: want{ + adm: ``, + burl: `sample.com/track?tid=1234&owsspburl=http://burl.com`, + }, }, { name: "app_platform", @@ -56,7 +62,9 @@ func Test_injectBannerTracker(t *testing.T) { }, seat: "test", }, - wantAdm: `sample_creative`, + want: want{ + adm: `sample_creative`, + }, }, { name: "app_platform_OM_Inactive_pubmatic", @@ -65,15 +73,17 @@ func Test_injectBannerTracker(t *testing.T) { Platform: models.PLATFORM_APP, }, tracker: models.OWTracker{ - TrackerURL: `Tracking URL`, - DspId: -1, + TrackerURL: `Tracking URL`, + IsOMEnabled: false, }, bid: openrtb2.Bid{ AdM: `sample_creative`, }, seat: models.BidderPubMatic, }, - wantAdm: `sample_creative`, + want: want{ + adm: `sample_creative`, + }, }, { name: "app_platform_OM_Active_pubmatic", @@ -82,15 +92,17 @@ func Test_injectBannerTracker(t *testing.T) { Platform: models.PLATFORM_APP, }, tracker: models.OWTracker{ - TrackerURL: `Tracking URL`, - DspId: models.DspId_DV360, + TrackerURL: `Tracking URL`, + IsOMEnabled: true, }, bid: openrtb2.Bid{ AdM: `sample_creative`, }, seat: models.BidderPubMatic, }, - wantAdm: `sample_creative`, + want: want{ + adm: `sample_creative`, + }, }, { name: "tbf_feature_enabled", @@ -107,23 +119,68 @@ func Test_injectBannerTracker(t *testing.T) { AdM: `sample_creative`, }, }, - wantAdm: `sample_creative`, + want: want{ + adm: `sample_creative`, + }, + }, + { + name: "app_platform_partner_other_than_pubmatic_imp_counting_method_enabled", + args: args{ + rctx: models.RequestCtx{ + Platform: models.PLATFORM_APP, + ImpCountingMethodEnabledBidders: map[string]struct{}{ + string(openrtb_ext.BidderIx): {}, + }, + }, + tracker: models.OWTracker{ + TrackerURL: `Tracking URL`, + IsOMEnabled: true, + }, + bid: openrtb2.Bid{ + AdM: `sample_creative`, + }, + seat: string(openrtb_ext.BidderIx), + }, + want: want{ + adm: `sample_creative`, + }, + }, + { + name: "app_platform_partner_other_than_pubmatic_imp_counting_method_disabled", + args: args{ + rctx: models.RequestCtx{ + Platform: models.PLATFORM_APP, + ImpCountingMethodEnabledBidders: map[string]struct{}{ + string(openrtb_ext.BidderIx): {}, + }, + }, + tracker: models.OWTracker{ + TrackerURL: `Tracking URL`, + }, + bid: openrtb2.Bid{ + AdM: `sample_creative`, + }, + seat: string(openrtb_ext.BidderAppnexus), + }, + want: want{ + adm: `sample_creative`, + }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gotAdm, gotBurl := injectBannerTracker(tt.args.rctx, tt.args.tracker, tt.args.bid, tt.args.seat, tt.args.pixels) - assert.Equal(t, tt.wantAdm, gotAdm) - assert.Equal(t, tt.wantBurl, gotBurl) + assert.Equal(t, tt.want.adm, gotAdm) + assert.Equal(t, tt.want.burl, gotBurl) }) } } -func Test_trackerWithOM(t *testing.T) { +func TestTrackerWithOM(t *testing.T) { type args struct { - tracker models.OWTracker - platform string - bidderCode string + rctx models.RequestCtx + prebidPartnerName string + dspID int } tests := []struct { name string @@ -131,54 +188,81 @@ func Test_trackerWithOM(t *testing.T) { want bool }{ { - name: "in-app_partner_otherthan_pubmatic", + name: "in-app_partner_other_than_pubmatic", args: args{ - tracker: models.OWTracker{ - DspId: models.DspId_DV360, + + rctx: models.RequestCtx{ + Platform: models.PLATFORM_APP, }, - platform: models.PLATFORM_APP, - bidderCode: "test", + prebidPartnerName: "test", }, want: false, }, { name: "in-app_partner_pubmatic_other_dv360", args: args{ - tracker: models.OWTracker{ - DspId: -1, + rctx: models.RequestCtx{ + Platform: models.PLATFORM_APP, }, - platform: models.PLATFORM_APP, - bidderCode: models.BidderPubMatic, + prebidPartnerName: models.BidderPubMatic, + dspID: -1, }, want: false, }, { name: "display_partner_pubmatic_dv360", args: args{ - tracker: models.OWTracker{ - DspId: models.DspId_DV360, + rctx: models.RequestCtx{ + Platform: models.PLATFORM_DISPLAY, }, - platform: models.PLATFORM_DISPLAY, - bidderCode: models.BidderPubMatic, + prebidPartnerName: models.BidderPubMatic, + dspID: models.DspId_DV360, }, want: false, }, { name: "in-app_partner_pubmatic_dv360", args: args{ - tracker: models.OWTracker{ - DspId: models.DspId_DV360, + + rctx: models.RequestCtx{ + Platform: models.PLATFORM_APP, + }, + prebidPartnerName: models.BidderPubMatic, + dspID: models.DspId_DV360, + }, + want: true, + }, + { + name: "in-app_partner_other_than_pubmatic_imp_counting_method_enabled", + args: args{ + rctx: models.RequestCtx{ + Platform: models.PLATFORM_APP, + ImpCountingMethodEnabledBidders: map[string]struct{}{ + "ix": {}, + }, }, - platform: models.PLATFORM_APP, - bidderCode: models.BidderPubMatic, + prebidPartnerName: "ix", }, want: true, }, + { + name: "in-app_partner_other_than_pubmatic_imp_counting_method_disabled", + args: args{ + rctx: models.RequestCtx{ + Platform: models.PLATFORM_APP, + ImpCountingMethodEnabledBidders: map[string]struct{}{ + "ix": {}, + }, + }, + prebidPartnerName: "magnite", + }, + want: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := trackerWithOM(tt.args.tracker, tt.args.platform, tt.args.bidderCode); got != tt.want { - t.Errorf("trackerWithOM() = %v, want %v", got, tt.want) + if got := trackerWithOM(tt.args.rctx, tt.args.prebidPartnerName, tt.args.dspID); got != tt.want { + assert.Equal(t, tt.want, got) } }) } diff --git a/modules/pubmatic/openwrap/tracker/create.go b/modules/pubmatic/openwrap/tracker/create.go index 5c1410ff3b7..9ed7bae433c 100644 --- a/modules/pubmatic/openwrap/tracker/create.go +++ b/modules/pubmatic/openwrap/tracker/create.go @@ -214,7 +214,7 @@ func createTrackers(rctx models.RequestCtx, trackers map[string]models.OWTracker PriceCurrency: bidResponse.Cur, ErrorURL: constructVideoErrorURL(rctx, rctx.VideoErrorTrackerEndpoint, bid, tracker), BidType: adformat, - DspId: dspId, + IsOMEnabled: trackerWithOM(rctx, partnerID, dspId), } } } diff --git a/modules/pubmatic/openwrap/tracker/inject.go b/modules/pubmatic/openwrap/tracker/inject.go index 06cb70c25e3..a4261c35568 100644 --- a/modules/pubmatic/openwrap/tracker/inject.go +++ b/modules/pubmatic/openwrap/tracker/inject.go @@ -7,6 +7,7 @@ import ( "golang.org/x/exp/slices" + "github.com/buger/jsonparser" "github.com/prebid/openrtb/v20/openrtb2" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" @@ -20,8 +21,10 @@ func InjectTrackers(rctx models.RequestCtx, bidResponse *openrtb2.BidResponse) ( var errs error for i, seatBid := range bidResponse.SeatBid { for j, bid := range seatBid.Bid { - var errMsg string - var err error + var ( + errMsg string + err error + ) tracker := rctx.Trackers[bid.ID] adformat := tracker.BidType if rctx.Platform == models.PLATFORM_VIDEO { @@ -32,6 +35,9 @@ func InjectTrackers(rctx models.RequestCtx, bidResponse *openrtb2.BidResponse) ( switch adformat { case models.Banner: bidResponse.SeatBid[i].Bid[j].AdM, bidResponse.SeatBid[i].Bid[j].BURL = injectBannerTracker(rctx, tracker, bid, seatBid.Seat, pixels) + if tracker.IsOMEnabled { + bidResponse.SeatBid[i].Bid[j].Ext, err = jsonparser.Set(bid.Ext, []byte(`1`), models.ImpCountingMethod) + } case models.Video: trackers := []models.OWTracker{tracker} bidResponse.SeatBid[i].Bid[j].AdM, bidResponse.SeatBid[i].Bid[j].BURL, err = injectVideoCreativeTrackers(rctx, bid, trackers) diff --git a/modules/pubmatic/openwrap/tracker/inject_test.go b/modules/pubmatic/openwrap/tracker/inject_test.go index 0eed8b3e37a..93c2a343e43 100644 --- a/modules/pubmatic/openwrap/tracker/inject_test.go +++ b/modules/pubmatic/openwrap/tracker/inject_test.go @@ -8,6 +8,7 @@ import ( 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/adunitconfig" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -343,10 +344,10 @@ func TestInjectTrackers(t *testing.T) { Platform: models.PLATFORM_APP, Trackers: map[string]models.OWTracker{ "12345": { - BidType: "banner", - TrackerURL: `Tracking URL`, - ErrorURL: `Error URL`, - DspId: -1, + BidType: "banner", + TrackerURL: `Tracking URL`, + ErrorURL: `Error URL`, + IsOMEnabled: false, }, }, }, @@ -386,10 +387,10 @@ func TestInjectTrackers(t *testing.T) { Platform: models.PLATFORM_APP, Trackers: map[string]models.OWTracker{ "12345": { - BidType: "banner", - TrackerURL: `Tracking URL`, - ErrorURL: `Error URL`, - DspId: models.DspId_DV360, + BidType: "banner", + TrackerURL: `Tracking URL`, + ErrorURL: `Error URL`, + IsOMEnabled: true, }, }, }, @@ -400,6 +401,7 @@ func TestInjectTrackers(t *testing.T) { { ID: "12345", AdM: `sample_creative`, + Ext: []byte(`{"key":"value"}`), }, }, Seat: models.BidderPubMatic, @@ -414,6 +416,7 @@ func TestInjectTrackers(t *testing.T) { { ID: "12345", AdM: `sample_creative`, + Ext: []byte(`{"key":"value","imp_ct_mthd":1}`), }, }, Seat: models.BidderPubMatic, @@ -911,6 +914,54 @@ func TestInjectTrackers(t *testing.T) { }, wantErr: false, }, + { + name: "platform_is_app_with_imp_counting_method_enabled_ix", + args: args{ + rctx: models.RequestCtx{ + Platform: models.PLATFORM_APP, + Trackers: map[string]models.OWTracker{ + "12345": { + BidType: "banner", + TrackerURL: `Tracking URL`, + ErrorURL: `Error URL`, + IsOMEnabled: true, + }, + }, + ImpCountingMethodEnabledBidders: map[string]struct{}{ + string(openrtb_ext.BidderIx): {}, + }, + }, + bidResponse: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "12345", + AdM: `sample_creative`, + Ext: []byte(`{"key":"value"}`), + }, + }, + Seat: string(openrtb_ext.BidderIx), + }, + }, + }, + }, + want: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "12345", + AdM: `sample_creative`, + Ext: []byte(`{"key":"value","imp_ct_mthd":1}`), + }, + }, + Seat: string(openrtb_ext.BidderIx), + }, + }, + }, + wantErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) {