diff --git a/analytics/pubmatic/logger.go b/analytics/pubmatic/logger.go index c90a146753c..77dc727ebed 100644 --- a/analytics/pubmatic/logger.go +++ b/analytics/pubmatic/logger.go @@ -360,8 +360,8 @@ func getPartnerRecordsByImp(ao analytics.AuctionObject, rCtx *models.RequestCtx) price := bid.Price if ao.Response.Cur != models.USD { - if bidCtx.EG != 0 { // valid-bids + dropped-bids+ default-bids - price = bidCtx.EG + if bidCtx.EN != 0 { // valid-bids + dropped-bids+ default-bids + price = bidCtx.EN } else if bidExt.OriginalBidCPMUSD != 0 { // valid non-bids price = bidExt.OriginalBidCPMUSD } @@ -403,11 +403,11 @@ func getPartnerRecordsByImp(ao analytics.AuctionObject, rCtx *models.RequestCtx) } if pr.NetECPM == 0 { - pr.NetECPM = models.GetNetEcpm(price, revShare) + pr.NetECPM = models.ToFixed(price, models.BID_PRECISION) } if pr.GrossECPM == 0 { - pr.GrossECPM = models.GetGrossEcpm(price) + pr.GrossECPM = models.GetGrossEcpmFromNetEcpm(price, revShare) } if pr.PartnerSize == "" { diff --git a/analytics/pubmatic/logger_test.go b/analytics/pubmatic/logger_test.go index c9e00314950..61457307e6a 100644 --- a/analytics/pubmatic/logger_test.go +++ b/analytics/pubmatic/logger_test.go @@ -3024,8 +3024,8 @@ func TestGetPartnerRecordsByImpForRevShareAndBidCPM(t *testing.T) { partners: map[string][]PartnerRecord{ "imp1": { { - NetECPM: 90, - GrossECPM: 100, + NetECPM: 100, + GrossECPM: 111.11, OriginalCPM: 100, OriginalCur: "USD", PartnerID: "pubmatic", @@ -3198,8 +3198,8 @@ func TestGetPartnerRecordsByImpForRevShareAndBidCPM(t *testing.T) { partners: map[string][]PartnerRecord{ "imp1": { { - NetECPM: 90, - GrossECPM: 100, + NetECPM: 100, + GrossECPM: 111.11, OriginalCPM: 200, OriginalCur: "INR", PartnerID: "pubmatic", @@ -3568,11 +3568,11 @@ func TestGetLogAuctionObjectAsURL(t *testing.T) { rCtx: &models.RequestCtx{ PubID: 5890, DeviceCtx: models.DeviceCtx{ - Ext: &models.ExtDevice{ - ExtDevice: openrtb_ext.ExtDevice{ - ATTS: ptrutil.ToPtr(openrtb_ext.IOSAppTrackingStatusRestricted), - }, - }, + Ext: func() *models.ExtDevice { + extDevice := models.ExtDevice{} + extDevice.UnmarshalJSON([]byte(`{"atts":1}`)) + return &extDevice + }(), }, }, logInfo: true, diff --git a/analytics/pubmatic/record.go b/analytics/pubmatic/record.go index 91f162434ac..6e2782602f5 100644 --- a/analytics/pubmatic/record.go +++ b/analytics/pubmatic/record.go @@ -51,9 +51,9 @@ type record struct { // Device struct for storing device information type Device struct { - Platform models.DevicePlatform `json:"plt,omitempty"` - IFAType *models.DeviceIFAType `json:"ifty,omitempty"` //OTT-416, adding device.ext.ifa_type - ATTS *openrtb_ext.IOSAppTrackingStatus `json:"atts,omitempty"` //device.ext.atts + Platform models.DevicePlatform `json:"plt,omitempty"` + IFAType *models.DeviceIFAType `json:"ifty,omitempty"` //OTT-416, adding device.ext.ifa_type + ATTS *float64 `json:"atts,omitempty"` //device.ext.atts } /* @@ -199,7 +199,7 @@ func (wlog *WloggerRecord) logDeviceObject(dvc *models.DeviceCtx) { wlog.Device.Platform = dvc.Platform wlog.Device.IFAType = dvc.IFATypeID if dvc.Ext != nil { - wlog.record.Device.ATTS = dvc.Ext.ATTS + wlog.record.Device.ATTS, _ = dvc.Ext.GetAtts() } } diff --git a/analytics/pubmatic/record_test.go b/analytics/pubmatic/record_test.go index a8b8142f959..f1bfb2eb141 100644 --- a/analytics/pubmatic/record_test.go +++ b/analytics/pubmatic/record_test.go @@ -415,9 +415,7 @@ func TestLogDeviceObject(t *testing.T) { dvc: &models.DeviceCtx{ Platform: models.DevicePlatformDesktop, IFATypeID: ptrutil.ToPtr(models.DeviceIFATypeID[models.DeviceIFATypeDPID]), - Ext: &models.ExtDevice{ - ExtDevice: openrtb_ext.ExtDevice{}, - }, + Ext: &models.ExtDevice{}, }, }, want: Device{ @@ -431,17 +429,17 @@ func TestLogDeviceObject(t *testing.T) { dvc: &models.DeviceCtx{ Platform: models.DevicePlatformDesktop, IFATypeID: ptrutil.ToPtr(models.DeviceIFATypeID[models.DeviceIFATypeDPID]), - Ext: &models.ExtDevice{ - ExtDevice: openrtb_ext.ExtDevice{ - ATTS: ptrutil.ToPtr(openrtb_ext.IOSAppTrackingStatusNotDetermined), - }, - }, + Ext: func() *models.ExtDevice { + extDevice := models.ExtDevice{} + extDevice.UnmarshalJSON([]byte(`{"atts":0}`)) + return &extDevice + }(), }, }, want: Device{ Platform: models.DevicePlatformDesktop, IFAType: ptrutil.ToPtr(models.DeviceIFATypeID[models.DeviceIFATypeDPID]), - ATTS: ptrutil.ToPtr(openrtb_ext.IOSAppTrackingStatusNotDetermined), + ATTS: ptrutil.ToPtr(float64(openrtb_ext.IOSAppTrackingStatusNotDetermined)), }, }, } diff --git a/go.mod b/go.mod index 71d1d3b6b84..bbc148ac39c 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/PubMatic-OpenWrap/prebid-server go 1.20 -replace git.pubmatic.com/vastunwrap => git.pubmatic.com/PubMatic/vastunwrap v0.0.0-20231102070946-3c5a3bc1dff5 +replace git.pubmatic.com/vastunwrap => git.pubmatic.com/PubMatic/vastunwrap v0.0.0-20240215114830-026b72bf5a2f require ( github.com/DATA-DOG/go-sqlmock v1.5.0 @@ -79,7 +79,6 @@ require ( github.com/subosito/gotenv v1.3.0 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - github.com/yudai/pp v2.0.1+incompatible // indirect golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect golang.org/x/sys v0.14.0 // indirect google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect diff --git a/go.sum b/go.sum index 2530cd440cf..6a0671527af 100644 --- a/go.sum +++ b/go.sum @@ -49,8 +49,8 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -git.pubmatic.com/PubMatic/vastunwrap v0.0.0-20231102070946-3c5a3bc1dff5 h1:nK2YP3aS8+5dwc5cMJ8IxI0Sh7yfd858LKmcvwfkOUo= -git.pubmatic.com/PubMatic/vastunwrap v0.0.0-20231102070946-3c5a3bc1dff5/go.mod h1:dgTumQ6/KYeLbpWq3HVOaqkZos6Q0QGwZmQmEIhQ3To= +git.pubmatic.com/PubMatic/vastunwrap v0.0.0-20240215114830-026b72bf5a2f h1:nCrSziQxM2ITd9w71lhqAhIyZthZvvBn78aYvdQmt8s= +git.pubmatic.com/PubMatic/vastunwrap v0.0.0-20240215114830-026b72bf5a2f/go.mod h1:dgTumQ6/KYeLbpWq3HVOaqkZos6Q0QGwZmQmEIhQ3To= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= @@ -503,7 +503,6 @@ github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FB github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI= -github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/modules/pubmatic/openwrap/auctionresponsehook.go b/modules/pubmatic/openwrap/auctionresponsehook.go index 9a8859c6edc..ef25402c093 100644 --- a/modules/pubmatic/openwrap/auctionresponsehook.go +++ b/modules/pubmatic/openwrap/auctionresponsehook.go @@ -117,12 +117,12 @@ func (m OpenWrap) handleAuctionResponseHook( // set response netecpm and logger/tracker en revShare := models.GetRevenueShare(rctx.PartnerConfigMap[partnerID]) - bidExt.NetECPM = models.GetNetEcpm(bid.Price, revShare) - eg = bid.Price + bidExt.NetECPM = models.ToFixed(bid.Price, models.BID_PRECISION) + eg = models.GetGrossEcpmFromNetEcpm(bid.Price, revShare) en = bidExt.NetECPM if payload.BidResponse.Cur != "USD" { - eg = bidExt.OriginalBidCPMUSD - en = models.GetNetEcpm(bidExt.OriginalBidCPMUSD, revShare) + eg = models.GetGrossEcpmFromNetEcpm(bidExt.OriginalBidCPMUSD, revShare) + en = bidExt.OriginalBidCPMUSD bidExt.OriginalBidCPMUSD = 0 } diff --git a/modules/pubmatic/openwrap/auctionresponsehook_test.go b/modules/pubmatic/openwrap/auctionresponsehook_test.go index a6d0ef18f9f..6b0c0e63ddf 100644 --- a/modules/pubmatic/openwrap/auctionresponsehook_test.go +++ b/modules/pubmatic/openwrap/auctionresponsehook_test.go @@ -7,6 +7,7 @@ import ( "time" "github.com/golang/mock/gomock" + "github.com/prebid/openrtb/v19/adcom1" "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/openrtb/v19/openrtb3" "github.com/prebid/prebid-server/hooks/hookstage" @@ -1563,3 +1564,342 @@ func TestAuctionResponseHookForEndpointWebS2S(t *testing.T) { }) } } + +func TestOpenWrap_handleAuctionResponseHook(t *testing.T) { + ctrl := gomock.NewController(t) + mockCache := mock_cache.NewMockCache(ctrl) + tbf.SetAndResetTBFConfig(mockCache, nil) + defer ctrl.Finish() + + type want struct { + result hookstage.HookResult[hookstage.AuctionResponsePayload] + bidResponse json.RawMessage + err error + } + type args struct { + ctx context.Context + moduleCtx hookstage.ModuleInvocationContext + payload hookstage.AuctionResponsePayload + } + tests := []struct { + name string + args args + want want + doMutate bool + setup func() *mock_metrics.MockMetricsEngine + }{ + { + name: "empty moduleContext", + args: args{ + ctx: nil, + moduleCtx: hookstage.ModuleInvocationContext{}, + payload: hookstage.AuctionResponsePayload{}, + }, + doMutate: false, + want: want{ + result: hookstage.HookResult[hookstage.AuctionResponsePayload]{ + DebugMessages: []string{"error: module-ctx not found in handleAuctionResponseHook()"}, + }, + err: nil, + }, + }, + { + name: "empty requestContext", + args: args{ + ctx: nil, + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": nil, + }, + }, + payload: hookstage.AuctionResponsePayload{}, + }, + doMutate: false, + want: want{ + result: hookstage.HookResult[hookstage.AuctionResponsePayload]{ + DebugMessages: []string{"error: request-ctx not found in handleAuctionResponseHook()"}, + }, + err: nil, + }, + }, + { + name: "requestContext is not of type RequestCtx", + args: args{ + ctx: nil, + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": "request-ctx", // request-ctx is not of type RequestCtx + }, + }, + payload: hookstage.AuctionResponsePayload{}, + }, + doMutate: false, + want: want{ + result: hookstage.HookResult[hookstage.AuctionResponsePayload]{ + DebugMessages: []string{"error: request-ctx not found in handleAuctionResponseHook()"}, + }, + err: nil, + }, + }, + { + name: "requestContext has sshb=1(request should not execute module hook)", + args: args{ + ctx: nil, + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": models.RequestCtx{ + Sshb: "1", + }, + }, + }, + payload: hookstage.AuctionResponsePayload{}, + }, + doMutate: false, + want: want{ + result: hookstage.HookResult[hookstage.AuctionResponsePayload]{}, + err: nil, + }, + }, + { + name: "empty bidResponse", + args: args{ + ctx: nil, + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": models.RequestCtx{ + Sshb: "0", + PubID: 5890, + PubIDStr: "5890", + }, + }, + }, + payload: hookstage.AuctionResponsePayload{ + BidResponse: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{}, + }, + }, + }, + doMutate: true, + setup: func() *mock_metrics.MockMetricsEngine { + mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) + mockEngine.EXPECT().RecordNobidErrPrebidServerResponse("5890") + mockEngine.EXPECT().RecordPublisherResponseTimeStats("5890", gomock.Any()) + return mockEngine + }, + want: want{ + result: hookstage.HookResult[hookstage.AuctionResponsePayload]{}, + err: nil, + bidResponse: json.RawMessage(`{"id":"","ext":{"matchedimpression":{}}}`), + }, + }, + { + name: "valid bidResponse with banner bids", + args: args{ + ctx: nil, + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": models.RequestCtx{ + PubID: 5890, + PubIDStr: "5890", + Platform: "web", + ImpBidCtx: map[string]models.ImpCtx{ + "Div1": { + Bidders: map[string]models.PartnerData{ + "pubmatic": { + PartnerID: 123, + PrebidBidderCode: "pubmatic", + }, + }, + Video: &openrtb2.Video{}, + Type: "video", + Banner: true, + }, + }, + BidderResponseTimeMillis: map[string]int{}, + SeatNonBids: map[string][]openrtb_ext.NonBid{}, + LogInfoFlag: 1, + ReturnAllBidStatus: true, + Debug: true, + ClientConfigFlag: 1, + PartnerConfigMap: map[int]map[string]string{ + 123: { + models.PARTNER_ID: "123", + models.PREBID_PARTNER_NAME: "pubmatic", + models.BidderCode: "pubmatic", + models.SERVER_SIDE_FLAG: "1", + models.KEY_GEN_PATTERN: "_AU_@_W_x_H_", + models.TIMEOUT: "200", + }, + -1: { + models.DisplayVersionID: "1", + "refreshInterval": "30", + "rev_share": "0.5", + }, + }, + }, + }, + }, + payload: hookstage.AuctionResponsePayload{ + BidResponse: &openrtb2.BidResponse{ + ID: "12345", + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "Div1", + Price: 5, + AdM: "
", + Ext: json.RawMessage(`{"bidtype":0,"deal_channel":1,"dspid":6,"origbidcpm":8,"origbidcur":"USD","prebid":{"bidid":"bb57a9e3-fdc2-4772-8071-112dd7f50a6a","meta":{"adaptercode":"pubmatic","advertiserId":4098,"agencyId":4098,"demandSource":"6","mediaType":"banner","networkId":6},"targeting":{"hb_bidder_pubmatic":"pubmatic","hb_deal_pubmatic":"PUBDEAL1","hb_pb_pubmatic":"8.00","hb_size_pubmatic":"728x90"},"type":"banner","video":{"duration":0,"primary_category":"","vasttagid":""}}}`), + }, + }, + Seat: "pubmatic", + }, + }, + Ext: json.RawMessage(`{"responsetimemillis":{"pubmatic":8}}`), + }, + }, + }, + setup: func() *mock_metrics.MockMetricsEngine { + mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) + mockEngine.EXPECT().RecordPlatformPublisherPartnerResponseStats("web", "5890", "pubmatic") + mockEngine.EXPECT().RecordPartnerResponseTimeStats("5890", "pubmatic", 8) + mockEngine.EXPECT().RecordPublisherResponseTimeStats("5890", gomock.Any()) + mockEngine.EXPECT().RecordPublisherPartnerNoCookieStats("5890", gomock.Any()).AnyTimes() + return mockEngine + }, + doMutate: true, + want: want{ + result: hookstage.HookResult[hookstage.AuctionResponsePayload]{ + DebugMessages: []string{`[{"PubID":5890,"ProfileID":0,"DisplayID":0,"VersionID":0,"DisplayVersionID":0,"SSAuction":0,"SummaryDisable":0,"LogInfoFlag":1,"SSAI":"","PartnerConfigMap":{"-1":{"displayVersionId":"1","refreshInterval":"30","rev_share":"0.5"},"123":{"bidderCode":"pubmatic","kgp":"_AU_@_W_x_H_","partnerId":"123","prebidPartnerName":"pubmatic","serverSideEnabled":"1","timeout":"200"}},"SupportDeals":false,"Platform":"web","LoggerImpressionID":"","ClientConfigFlag":1,"IP":"","TMax":0,"IsTestRequest":0,"ABTestConfig":0,"ABTestConfigApplied":0,"IsCTVRequest":false,"TrackerEndpoint":"","VideoErrorTrackerEndpoint":"","UA":"","Cookies":"","UidCookie":null,"KADUSERCookie":null,"ParsedUidCookie":null,"OriginCookie":"","Debug":true,"Trace":false,"PageURL":"","StartTime":0,"DevicePlatform":0,"Trackers":{"bid-id-1":{"Tracker":{"PubID":5890,"PageURL":"","Timestamp":0,"IID":"","ProfileID":"0","VersionID":"0","SlotID":"","Adunit":"","PartnerInfo":{"PartnerID":"pubmatic","BidderCode":"pubmatic","KGPV":"","GrossECPM":0,"NetECPM":0,"BidID":"bb57a9e3-fdc2-4772-8071-112dd7f50a6a","OrigBidID":"bid-id-1","AdSize":"0x0","AdDuration":0,"Adformat":"banner","ServerSide":1,"Advertiser":"","FloorValue":0,"FloorRuleValue":0,"DealID":"-1"},"RewardedInventory":0,"SURL":"","Platform":0,"SSAI":"","AdPodSlot":0,"TestGroup":0,"Origin":"","FloorSkippedFlag":null,"FloorModelVersion":"","FloorSource":null,"FloorType":0,"CustomDimensions":"","LoggerData":{"KGPSV":"","FloorProvider":"","FloorFetchStatus":null}},"TrackerURL":"https:?adv=\u0026af=banner\u0026aps=0\u0026au=\u0026bc=pubmatic\u0026bidid=bb57a9e3-fdc2-4772-8071-112dd7f50a6a\u0026di=-1\u0026eg=0\u0026en=0\u0026ft=0\u0026iid=\u0026kgpv=\u0026orig=\u0026origbidid=bid-id-1\u0026pdvid=0\u0026pid=0\u0026plt=0\u0026pn=pubmatic\u0026psz=0x0\u0026pubid=5890\u0026purl=\u0026sl=1\u0026slot=\u0026ss=1\u0026tgid=0\u0026tst=0","ErrorURL":"","Price":5,"PriceModel":"CPM","PriceCurrency":""}},"PrebidBidderCode":null,"ImpBidCtx":{"Div1":{"ImpID":"","TagID":"","Div":"","SlotName":"","AdUnitName":"","Secure":0,"BidFloor":0,"BidFloorCur":"","IsRewardInventory":null,"Banner":true,"Video":{"mimes":null},"Native":null,"IncomingSlots":null,"Type":"video","Bidders":{"pubmatic":{"PartnerID":123,"PrebidBidderCode":"pubmatic","MatchedSlot":"","KGP":"","KGPV":"","IsRegex":false,"Params":null,"VASTTagFlag":false,"VASTTagFlags":null}},"NonMapped":null,"NewExt":null,"BidCtx":{"bid-id-1":{"prebid":{"meta":{"adaptercode":"pubmatic","advertiserId":4098,"agencyId":4098,"demandSource":"6","mediaType":"banner","networkId":6},"type":"banner","bidid":"bb57a9e3-fdc2-4772-8071-112dd7f50a6a"},"refreshInterval":30,"crtype":"banner","dspid":6,"netecpm":5,"origbidcpm":8,"origbidcur":"USD","EG":0,"EN":0}},"BannerAdUnitCtx":{"MatchedSlot":"","IsRegex":false,"MatchedRegex":"","SelectedSlotAdUnitConfig":null,"AppliedSlotAdUnitConfig":null,"UsingDefaultConfig":false,"AllowedConnectionTypes":null},"VideoAdUnitCtx":{"MatchedSlot":"","IsRegex":false,"MatchedRegex":"","SelectedSlotAdUnitConfig":null,"AppliedSlotAdUnitConfig":null,"UsingDefaultConfig":false,"AllowedConnectionTypes":null},"BidderError":"","IsAdPodRequest":false}},"Aliases":null,"NewReqExt":null,"ResponseExt":{"responsetimemillis":{"pubmatic":8}},"MarketPlaceBidders":null,"AdapterThrottleMap":null,"AdUnitConfig":null,"Source":"","Origin":"","SendAllBids":false,"WinningBids":{"Div1":{"ID":"bid-id-1","NetEcpm":5,"BidDealTierSatisfied":false,"Nbr":null}},"DroppedBids":null,"DefaultBids":{},"SeatNonBids":{},"BidderResponseTimeMillis":{"pubmatic":8},"Endpoint":"","PubIDStr":"5890","ProfileIDStr":"","MetricsEngine":{},"ReturnAllBidStatus":true,"Sshb":"","DCName":"","CachePutMiss":0,"MatchedImpression":{"pubmatic":0},"CustomDimensions":null}]`}, + }, + err: nil, + bidResponse: json.RawMessage(`{"id":"12345","seatbid":[{"bid":[{"id":"bid-id-1","impid":"Div1","price":5,"adm":"\u003cimg src=\"http://ads.pubmatic.com/AdTag/728x90.png\"\u003e\u003c/img\u003e\u003cdiv style=\"position:absolute;left:0px;top:0px;visibility:hidden;\"\u003e\u003cimg src=\"https://t.pubmatic.com/wt?adv=\u0026af=banner\u0026aps=0\u0026au=%2F43743431%2FDMDemo\u0026bc=appnexus\u0026bidid=4033c510-6d67-4af6-b53f-682ff1a580c3\u0026di=-1\u0026eg=14\u0026en=14\u0026frv=1.57\u0026ft=0\u0026fv=1.57\u0026iid=429d469d-8cfb-495a-9f0c-5f48aa0ede40\u0026kgpv=\u0026orig=ebay.com\u0026origbidid=718825584\u0026pdvid=1\u0026pid=22503\u0026plt=1\u0026pn=appnexus\u0026psz=728x90\u0026pubid=5890\u0026purl=http%3A%2F%2Febay.com%2Finte%2Fautomation%2Fs2s_activation%2Fbanner-with-gdpr-pubmatic-denied-defaultbidder.html%3Fprofileid%3D22503%26pwtv%3D1%26pwtvc%3D1%26appnexus_banner_fixedbid%3D14%26fixedbid%3D1%26debug%3D1\u0026sl=1\u0026slot=%2F43743431%2FDMDemo\u0026ss=1\u0026tgid=0\u0026tst=1704357774\"\u003e\u003c/div\u003e\u003cdiv style=\"position:absolute;left:0px;top:0px;visibility:hidden;\"\u003e\u003cimg src=\"https:?adv=\u0026af=banner\u0026aps=0\u0026au=\u0026bc=pubmatic\u0026bidid=bb57a9e3-fdc2-4772-8071-112dd7f50a6a\u0026di=-1\u0026eg=0\u0026en=0\u0026ft=0\u0026iid=\u0026kgpv=\u0026orig=\u0026origbidid=bid-id-1\u0026pdvid=0\u0026pid=0\u0026plt=0\u0026pn=pubmatic\u0026psz=0x0\u0026pubid=5890\u0026purl=\u0026sl=1\u0026slot=\u0026ss=1\u0026tgid=0\u0026tst=0\"\u003e\u003c/div\u003e","ext":{"prebid":{"meta":{"adaptercode":"pubmatic","advertiserId":4098,"agencyId":4098,"demandSource":"6","mediaType":"banner","networkId":6},"type":"banner","bidid":"bb57a9e3-fdc2-4772-8071-112dd7f50a6a"},"refreshInterval":30,"crtype":"banner","dspid":6,"netecpm":5,"origbidcpm":8,"origbidcur":"USD"}}],"seat":"pubmatic"}],"ext":{"responsetimemillis":{"pubmatic":8},"matchedimpression":{"pubmatic":0},"loginfo":{"tracker":"?adv=\u0026af=\u0026aps=0\u0026au=%24%7BADUNIT%7D\u0026bc=%24%7BBIDDER_CODE%7D\u0026bidid=%24%7BBID_ID%7D\u0026di=\u0026eg=%24%7BG_ECPM%7D\u0026en=%24%7BN_ECPM%7D\u0026ft=0\u0026iid=\u0026kgpv=%24%7BKGPV%7D\u0026orig=\u0026origbidid=%24%7BORIGBID_ID%7D\u0026pdvid=0\u0026pid=0\u0026plt=0\u0026pn=%24%7BPARTNER_NAME%7D\u0026psz=\u0026pubid=5890\u0026purl=\u0026rwrd=%24%7BREWARDED%7D\u0026sl=1\u0026slot=%24%7BSLOT_ID%7D\u0026ss=0\u0026tgid=0\u0026tst=0"}}}`), + }, + }, + { + name: "valid bidResponse with video bids", + args: args{ + ctx: nil, + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": models.RequestCtx{ + PubID: 5890, + PubIDStr: "5890", + Platform: "web", + ImpBidCtx: map[string]models.ImpCtx{ + "Div1": { + Bidders: map[string]models.PartnerData{ + "pubmatic": { + PartnerID: 123, + PrebidBidderCode: "pubmatic", + }, + }, + Video: &openrtb2.Video{ + MaxDuration: 20, + MinDuration: 10, + SkipAfter: 2, + Skip: ptrutil.ToPtr[int8](1), + SkipMin: 1, + BAttr: []adcom1.CreativeAttribute{adcom1.CreativeAttribute(1)}, + PlaybackMethod: []adcom1.PlaybackMethod{adcom1.PlaybackPageLoadSoundOn}, + }, + Type: "video", + Banner: false, + }, + }, + BidderResponseTimeMillis: map[string]int{}, + SeatNonBids: map[string][]openrtb_ext.NonBid{}, + LogInfoFlag: 1, + ReturnAllBidStatus: true, + Debug: true, + PartnerConfigMap: map[int]map[string]string{ + 123: { + models.PARTNER_ID: "123", + models.PREBID_PARTNER_NAME: "pubmatic", + models.BidderCode: "pubmatic", + models.SERVER_SIDE_FLAG: "1", + models.KEY_GEN_PATTERN: "_AU_@_W_x_H_", + models.TIMEOUT: "200", + }, + -1: { + models.DisplayVersionID: "1", + "refreshInterval": "30", + "rev_share": "0.5", + }, + }, + }, + }, + }, + payload: hookstage.AuctionResponsePayload{ + BidResponse: &openrtb2.BidResponse{ + ID: "12345", + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "Div1", + Price: 5, + AdM: "", + Ext: json.RawMessage(`{"bidtype":0,"deal_channel":1,"dspid":6,"origbidcpm":8,"origbidcur":"USD","prebid":{"bidid":"bb57a9e3-fdc2-4772-8071-112dd7f50a6a","meta":{"adaptercode":"pubmatic","advertiserId":4098,"agencyId":4098,"demandSource":"6","mediaType":"banner","networkId":6},"targeting":{"hb_bidder_pubmatic":"pubmatic","hb_deal_pubmatic":"PUBDEAL1","hb_pb_pubmatic":"8.00","hb_size_pubmatic":"728x90"},"type":"video","video":{"duration":0,"primary_category":"","vasttagid":""}}}`), + }, + }, + Seat: "pubmatic", + }, + }, + Ext: json.RawMessage(`{"responsetimemillis":{"pubmatic":8}}`), + }, + }, + }, + setup: func() *mock_metrics.MockMetricsEngine { + mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) + mockEngine.EXPECT().RecordPlatformPublisherPartnerResponseStats("web", "5890", "pubmatic") + mockEngine.EXPECT().RecordPartnerResponseTimeStats("5890", "pubmatic", 8) + mockEngine.EXPECT().RecordPublisherResponseTimeStats("5890", gomock.Any()) + mockEngine.EXPECT().RecordPublisherPartnerNoCookieStats("5890", gomock.Any()).AnyTimes() + return mockEngine + }, + doMutate: true, + want: want{ + result: hookstage.HookResult[hookstage.AuctionResponsePayload]{}, + err: nil, + bidResponse: json.RawMessage(`{"id":"12345","seatbid":[{"bid":[{"id":"bid-id-1","impid":"Div1","price":5,"adm":"\u003cVAST version=\"3.0\"\u003e\u003cAd\u003e\u003cWrapper\u003e\u003cImpression\u003e\u003c![CDATA[https:?adv=\u0026af=video\u0026aps=0\u0026au=\u0026bc=pubmatic\u0026bidid=bb57a9e3-fdc2-4772-8071-112dd7f50a6a\u0026di=-1\u0026eg=0\u0026en=0\u0026ft=0\u0026iid=\u0026kgpv=\u0026orig=\u0026origbidid=bid-id-1\u0026pdvid=0\u0026pid=0\u0026plt=0\u0026pn=pubmatic\u0026psz=0x0\u0026pubid=5890\u0026purl=\u0026sl=1\u0026slot=\u0026ss=1\u0026tgid=0\u0026tst=0]]\u003e\u003c/Impression\u003e\u003c/Wrapper\u003e\u003c/Ad\u003e\u003c/VAST\u003e","ext":{"prebid":{"meta":{"adaptercode":"pubmatic","advertiserId":4098,"agencyId":4098,"demandSource":"6","mediaType":"banner","networkId":6},"type":"video","bidid":"bb57a9e3-fdc2-4772-8071-112dd7f50a6a"},"refreshInterval":30,"crtype":"video","video":{"minduration":10,"maxduration":20,"skip":1,"skipmin":1,"skipafter":2,"battr":[1],"playbackmethod":[1]},"dspid":6,"netecpm":5,"origbidcpm":8,"origbidcur":"USD"}}],"seat":"pubmatic"}],"ext":{"responsetimemillis":{"pubmatic":8},"matchedimpression":{"pubmatic":0},"loginfo":{"tracker":"?adv=\u0026af=\u0026aps=0\u0026au=%24%7BADUNIT%7D\u0026bc=%24%7BBIDDER_CODE%7D\u0026bidid=%24%7BBID_ID%7D\u0026di=\u0026eg=%24%7BG_ECPM%7D\u0026en=%24%7BN_ECPM%7D\u0026ft=0\u0026iid=\u0026kgpv=%24%7BKGPV%7D\u0026orig=\u0026origbidid=%24%7BORIGBID_ID%7D\u0026pdvid=0\u0026pid=0\u0026plt=0\u0026pn=%24%7BPARTNER_NAME%7D\u0026psz=\u0026pubid=5890\u0026purl=\u0026rwrd=%24%7BREWARDED%7D\u0026sl=1\u0026slot=%24%7BSLOT_ID%7D\u0026ss=0\u0026tgid=0\u0026tst=0"}}}`), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var mockEngine *mock_metrics.MockMetricsEngine + if tt.setup != nil { + mockEngine = tt.setup() + } + m := OpenWrap{ + cache: mockCache, + metricEngine: mockEngine, + } + moduleCtx, ok := tt.args.moduleCtx.ModuleContext["rctx"] + if ok { + rCtx, ok := moduleCtx.(models.RequestCtx) + if ok { + rCtx.MetricsEngine = mockEngine + tt.args.moduleCtx.ModuleContext["rctx"] = rCtx + } + } + hookResult, err := m.handleAuctionResponseHook(tt.args.ctx, tt.args.moduleCtx, tt.args.payload) + assert.Equal(t, tt.want.err, err, tt.name) + if tt.doMutate { + mutations := hookResult.ChangeSet.Mutations() + assert.NotEmpty(t, mutations, tt.name) + for _, mut := range mutations { + result, err := mut.Apply(tt.args.payload) + gotBidResponse, _ := json.Marshal(result.BidResponse) + assert.Nil(t, err, tt.name) + assert.Equal(t, tt.want.bidResponse, json.RawMessage(gotBidResponse), tt.name) + } + return + } + assert.Equal(t, tt.want.result.DebugMessages, hookResult.DebugMessages, tt.name) + }) + } +} diff --git a/modules/pubmatic/openwrap/beforevalidationhook.go b/modules/pubmatic/openwrap/beforevalidationhook.go index 660bc789fb6..6e5faa38033 100644 --- a/modules/pubmatic/openwrap/beforevalidationhook.go +++ b/modules/pubmatic/openwrap/beforevalidationhook.go @@ -48,14 +48,6 @@ func (m OpenWrap) handleBeforeValidationHook( } }() - // return prebid validation error - if len(payload.BidRequest.Imp) == 0 || (payload.BidRequest.Site == nil && payload.BidRequest.App == nil) { - result.Reject = false - m.metricEngine.RecordBadRequests(rCtx.Endpoint, getPubmaticErrorCode(nbr.InvalidRequestExt)) - m.metricEngine.RecordNobidErrPrebidServerRequests(rCtx.PubIDStr, nbr.InvalidRequestExt) - return result, nil - } - //Do not execute the module for requests processed in SSHB(8001) if rCtx.Sshb == "1" { result.Reject = false @@ -68,6 +60,14 @@ func (m OpenWrap) handleBeforeValidationHook( return result, nil } + // return prebid validation error + if len(payload.BidRequest.Imp) == 0 || (payload.BidRequest.Site == nil && payload.BidRequest.App == nil) { + result.Reject = false + m.metricEngine.RecordBadRequests(rCtx.Endpoint, getPubmaticErrorCode(nbr.InvalidRequestExt)) + m.metricEngine.RecordNobidErrPrebidServerRequests(rCtx.PubIDStr, nbr.InvalidRequestExt) + return result, nil + } + pubID, err := getPubID(*payload.BidRequest) if err != nil { result.NbrCode = nbr.InvalidPublisherID @@ -114,6 +114,7 @@ func (m OpenWrap) handleBeforeValidationHook( err = errors.New("failed to get profile data: received empty data") } result.Errors = append(result.Errors, err.Error()) + rCtx.ImpBidCtx = getDefaultImpBidCtx(*payload.BidRequest) // for wrapper logger sz m.metricEngine.RecordPublisherInvalidProfileRequests(rCtx.Endpoint, rCtx.PubIDStr, rCtx.ProfileIDStr) m.metricEngine.RecordPublisherInvalidProfileImpressions(rCtx.PubIDStr, rCtx.ProfileIDStr, len(payload.BidRequest.Imp)) return result, err @@ -128,6 +129,7 @@ func (m OpenWrap) handleBeforeValidationHook( result.NbrCode = nbr.InvalidPlatform err = errors.New("failed to get platform data") result.Errors = append(result.Errors, err.Error()) + rCtx.ImpBidCtx = getDefaultImpBidCtx(*payload.BidRequest) // for wrapper logger sz m.metricEngine.RecordPublisherInvalidProfileRequests(rCtx.Endpoint, rCtx.PubIDStr, rCtx.ProfileIDStr) m.metricEngine.RecordPublisherInvalidProfileImpressions(rCtx.PubIDStr, rCtx.ProfileIDStr, len(payload.BidRequest.Imp)) return result, err @@ -135,7 +137,6 @@ func (m OpenWrap) handleBeforeValidationHook( rCtx.Platform = platform rCtx.DeviceCtx.Platform = getDevicePlatform(rCtx, payload.BidRequest) rCtx.SendAllBids = isSendAllBids(rCtx) - rCtx.TMax = m.setTimeout(rCtx, payload.BidRequest) m.metricEngine.RecordPublisherRequests(rCtx.Endpoint, rCtx.PubIDStr, rCtx.Platform) @@ -145,6 +146,9 @@ func (m OpenWrap) handleBeforeValidationHook( result.Warnings = append(result.Warnings, "update the rCtx.PartnerConfigMap with ABTest data") } + //TMax should be updated after ABTest processing + rCtx.TMax = m.setTimeout(rCtx, payload.BidRequest) + var allPartnersThrottledFlag bool rCtx.AdapterThrottleMap, allPartnersThrottledFlag = GetAdapterThrottleMap(rCtx.PartnerConfigMap) if allPartnersThrottledFlag { @@ -177,6 +181,7 @@ func (m OpenWrap) handleBeforeValidationHook( isAdPodRequest := false disabledSlots := 0 serviceSideBidderPresent := false + requestExt.Prebid.BidAdjustmentFactors = map[string]float64{} aliasgvlids := make(map[string]uint16) for i := 0; i < len(payload.BidRequest.Imp); i++ { @@ -192,6 +197,7 @@ func (m OpenWrap) handleBeforeValidationHook( result.NbrCode = nbr.InternalError err = errors.New("failed to parse imp.ext: " + imp.ID) result.Errors = append(result.Errors, err.Error()) + rCtx.ImpBidCtx = map[string]models.ImpCtx{} // do not create "s" object in owlogger return result, err } } @@ -202,6 +208,7 @@ func (m OpenWrap) handleBeforeValidationHook( result.NbrCode = nbr.InvalidImpressionTagID err = errors.New("tagid missing for imp: " + imp.ID) result.Errors = append(result.Errors, err.Error()) + rCtx.ImpBidCtx = map[string]models.ImpCtx{} // do not create "s" object in owlogger return result, err } @@ -358,6 +365,8 @@ func (m OpenWrap) handleBeforeValidationHook( updateAliasGVLIds(aliasgvlids, bidderCode, partnerConfig) } + revShare := models.GetRevenueShare(rCtx.PartnerConfigMap[partnerID]) + requestExt.Prebid.BidAdjustmentFactors[bidderCode] = models.GetBidAdjustmentValue(revShare) serviceSideBidderPresent = true } // for(rctx.PartnerConfigMap @@ -841,7 +850,7 @@ func getPageURL(bidRequest *openrtb2.BidRequest) string { func getVASTEventMacros(rctx models.RequestCtx) map[string]string { macros := map[string]string{ string(models.MacroProfileID): fmt.Sprintf("%d", rctx.ProfileID), - string(models.MacroProfileVersionID): fmt.Sprintf("%d", rctx.DisplayID), + string(models.MacroProfileVersionID): fmt.Sprintf("%d", rctx.DisplayVersionID), string(models.MacroUnixTimeStamp): fmt.Sprintf("%d", rctx.StartTime), string(models.MacroPlatform): fmt.Sprintf("%d", rctx.DeviceCtx.Platform), string(models.MacroWrapperImpressionID): rctx.LoggerImpressionID, diff --git a/modules/pubmatic/openwrap/beforevalidationhook_test.go b/modules/pubmatic/openwrap/beforevalidationhook_test.go index 071654738f0..9a947eccbd4 100644 --- a/modules/pubmatic/openwrap/beforevalidationhook_test.go +++ b/modules/pubmatic/openwrap/beforevalidationhook_test.go @@ -165,7 +165,7 @@ func TestGetVASTEventMacros(t *testing.T) { args: args{ rctx: models.RequestCtx{ ProfileID: 1234, - DisplayID: 1234, + DisplayVersionID: 1234, StartTime: 1234, LoggerImpressionID: "1234", SSAI: "", @@ -187,7 +187,7 @@ func TestGetVASTEventMacros(t *testing.T) { args: args{ rctx: models.RequestCtx{ ProfileID: 1234, - DisplayID: 1234, + DisplayVersionID: 1234, StartTime: 1234, LoggerImpressionID: "1234", SSAI: "1234", @@ -2481,7 +2481,6 @@ func TestOpenWrap_handleBeforeValidationHook(t *testing.T) { "adunit@700x900": "1232433543534543", }, }) - //prometheus metrics mockEngine.EXPECT().RecordPublisherProfileRequests("5890", "1234") mockEngine.EXPECT().RecordPublisherRequests(rctx.Endpoint, "5890", rctx.Platform) @@ -2494,12 +2493,12 @@ func TestOpenWrap_handleBeforeValidationHook(t *testing.T) { NbrCode: 0, Message: "", ChangeSet: hookstage.ChangeSet[hookstage.BeforeValidationRequestPayload]{}, - DebugMessages: []string{`new imp: {"123":{"ImpID":"123","TagID":"adunit","Div":"","SlotName":"adunit","AdUnitName":"adunit","Secure":0,"BidFloor":0,"BidFloorCur":"","IsRewardInventory":null,"Banner":true,"Video":{"mimes":["video/mp4","video/mpeg"],"w":640,"h":480},"Native":{"request":""},"IncomingSlots":["640x480v","700x900","728x90","300x250"],"Type":"video","Bidders":{"appnexus":{"PartnerID":2,"PrebidBidderCode":"appnexus","MatchedSlot":"adunit@700x900","KGP":"_AU_@_W_x_H_","KGPV":"","IsRegex":false,"Params":{"placementId":0,"site":"12313","adtag":"45343"},"VASTTagFlag":false,"VASTTagFlags":null}},"NonMapped":{},"NewExt":{"data":{"pbadslot":"adunit"},"prebid":{"bidder":{"appnexus":{"placementId":0,"site":"12313","adtag":"45343"}}}},"BidCtx":{},"BannerAdUnitCtx":{"MatchedSlot":"adunit@700x900","IsRegex":false,"MatchedRegex":"","SelectedSlotAdUnitConfig":{"banner":{"enabled":false}},"AppliedSlotAdUnitConfig":{"banner":{"enabled":false}},"UsingDefaultConfig":false,"AllowedConnectionTypes":null},"VideoAdUnitCtx":{"MatchedSlot":"adunit@640x480","IsRegex":false,"MatchedRegex":"","SelectedSlotAdUnitConfig":{"video":{"enabled":false}},"AppliedSlotAdUnitConfig":{"video":{"enabled":false}},"UsingDefaultConfig":false,"AllowedConnectionTypes":null},"BidderError":"","IsAdPodRequest":false}}`, `new request.ext: {"prebid":{"bidderparams":{"pubmatic":{"wiid":""}},"debug":true,"floors":{"enforcement":{"enforcepbs":true},"enabled":true},"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":5,"increment":0.05},{"min":5,"max":10,"increment":0.1},{"min":10,"max":20,"increment":0.5}]},"mediatypepricegranularity":{},"includewinners":true,"includebidderkeys":true},"macros":{"[PLATFORM]":"3","[PROFILE_ID]":"1234","[PROFILE_VERSION]":"1","[UNIX_TIMESTAMP]":"0","[WRAPPER_IMPRESSION_ID]":""}}}`}, + DebugMessages: []string{`new imp: {"123":{"ImpID":"123","TagID":"adunit","Div":"","SlotName":"adunit","AdUnitName":"adunit","Secure":0,"BidFloor":0,"BidFloorCur":"","IsRewardInventory":null,"Banner":true,"Video":{"mimes":["video/mp4","video/mpeg"],"w":640,"h":480},"Native":{"request":""},"IncomingSlots":["640x480v","700x900","728x90","300x250"],"Type":"video","Bidders":{"appnexus":{"PartnerID":2,"PrebidBidderCode":"appnexus","MatchedSlot":"adunit@700x900","KGP":"_AU_@_W_x_H_","KGPV":"","IsRegex":false,"Params":{"placementId":0,"site":"12313","adtag":"45343"},"VASTTagFlag":false,"VASTTagFlags":null}},"NonMapped":{},"NewExt":{"data":{"pbadslot":"adunit"},"prebid":{"bidder":{"appnexus":{"placementId":0,"site":"12313","adtag":"45343"}}}},"BidCtx":{},"BannerAdUnitCtx":{"MatchedSlot":"adunit@700x900","IsRegex":false,"MatchedRegex":"","SelectedSlotAdUnitConfig":{"banner":{"enabled":false}},"AppliedSlotAdUnitConfig":{"banner":{"enabled":false}},"UsingDefaultConfig":false,"AllowedConnectionTypes":null},"VideoAdUnitCtx":{"MatchedSlot":"adunit@640x480","IsRegex":false,"MatchedRegex":"","SelectedSlotAdUnitConfig":{"video":{"enabled":false}},"AppliedSlotAdUnitConfig":{"video":{"enabled":false}},"UsingDefaultConfig":false,"AllowedConnectionTypes":null},"BidderError":"","IsAdPodRequest":false}}`, `new request.ext: {"prebid":{"bidadjustmentfactors":{"appnexus":1},"bidderparams":{"pubmatic":{"wiid":""}},"debug":true,"floors":{"enforcement":{"enforcepbs":true},"enabled":true},"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":5,"increment":0.05},{"min":5,"max":10,"increment":0.1},{"min":10,"max":20,"increment":0.5}]},"mediatypepricegranularity":{},"includewinners":true,"includebidderkeys":true},"macros":{"[PLATFORM]":"3","[PROFILE_ID]":"1234","[PROFILE_VERSION]":"1","[UNIX_TIMESTAMP]":"0","[WRAPPER_IMPRESSION_ID]":""}}}`}, AnalyticsTags: hookanalytics.Analytics{Activities: nil}, }, wantErr: false, doMutate: true, - wantBidRequest: json.RawMessage(`{"id":"123-456-789","imp":[{"id":"123","native":{"request":""},"tagid":"adunit","ext":{"data":{"pbadslot":"adunit"},"prebid":{"bidder":{"appnexus":{"placementId":0,"site":"12313","adtag":"45343"}}}}}],"site":{"domain":"test.com","page":"www.test.com","publisher":{"id":"5890"}},"device":{"ua":"Mozilla/5.0(X11;Linuxx86_64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/52.0.2743.82Safari/537.36","ip":"127.0.0.1"},"user":{"id":"119208432","buyeruid":"1rwe432","yob":1980,"gender":"F","customdata":"7D75D25F-FAC9-443D-B2D1-B17FEE11E027","geo":{"country":"US","region":"CA","metro":"90001","city":"Alamo"}},"wseat":["Wseat_0","Wseat_1"],"bseat":["Bseat_0","Bseat_1"],"cur":["cur_0","cur_1"],"wlang":["Wlang_0","Wlang_1"],"bcat":["bcat_0","bcat_1"],"badv":["badv_0","badv_1"],"bapp":["bapp_0","bapp_1"],"source":{"tid":"123-456-789","ext":{"omidpn":"MyIntegrationPartner","omidpv":"7.1"}},"ext":{"prebid":{"bidderparams":{"pubmatic":{"wiid":""}},"debug":true,"floors":{"enforcement":{"enforcepbs":true},"enabled":true},"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":5,"increment":0.05},{"min":5,"max":10,"increment":0.1},{"min":10,"max":20,"increment":0.5}]},"mediatypepricegranularity":{},"includewinners":true,"includebidderkeys":true},"macros":{"[PLATFORM]":"3","[PROFILE_ID]":"1234","[PROFILE_VERSION]":"1","[UNIX_TIMESTAMP]":"0","[WRAPPER_IMPRESSION_ID]":""}}}}`), + wantBidRequest: json.RawMessage(`{"id":"123-456-789","imp":[{"id":"123","native":{"request":""},"tagid":"adunit","ext":{"data":{"pbadslot":"adunit"},"prebid":{"bidder":{"appnexus":{"placementId":0,"site":"12313","adtag":"45343"}}}}}],"site":{"domain":"test.com","page":"www.test.com","publisher":{"id":"5890"}},"device":{"ua":"Mozilla/5.0(X11;Linuxx86_64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/52.0.2743.82Safari/537.36","ip":"127.0.0.1"},"user":{"id":"119208432","buyeruid":"1rwe432","yob":1980,"gender":"F","customdata":"7D75D25F-FAC9-443D-B2D1-B17FEE11E027","geo":{"country":"US","region":"CA","metro":"90001","city":"Alamo"}},"wseat":["Wseat_0","Wseat_1"],"bseat":["Bseat_0","Bseat_1"],"cur":["cur_0","cur_1"],"wlang":["Wlang_0","Wlang_1"],"bcat":["bcat_0","bcat_1"],"badv":["badv_0","badv_1"],"bapp":["bapp_0","bapp_1"],"source":{"tid":"123-456-789","ext":{"omidpn":"MyIntegrationPartner","omidpv":"7.1"}},"ext":{"prebid":{"bidadjustmentfactors":{"appnexus":1},"bidderparams":{"pubmatic":{"wiid":""}},"debug":true,"floors":{"enforcement":{"enforcepbs":true},"enabled":true},"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":5,"increment":0.05},{"min":5,"max":10,"increment":0.1},{"min":10,"max":20,"increment":0.5}]},"mediatypepricegranularity":{},"includewinners":true,"includebidderkeys":true},"macros":{"[PLATFORM]":"3","[PROFILE_ID]":"1234","[PROFILE_VERSION]":"1","[UNIX_TIMESTAMP]":"0","[WRAPPER_IMPRESSION_ID]":""}}}}`), }, { name: "no_serviceSideBidderPresent", @@ -2678,10 +2677,10 @@ func TestOpenWrap_handleBeforeValidationHook(t *testing.T) { Reject: false, NbrCode: 0, ChangeSet: hookstage.ChangeSet[hookstage.BeforeValidationRequestPayload]{}, - DebugMessages: []string{`new imp: {"123":{"ImpID":"123","TagID":"adunit","Div":"","SlotName":"adunit","AdUnitName":"adunit","Secure":0,"BidFloor":4.3,"BidFloorCur":"USD","IsRewardInventory":null,"Banner":true,"Video":{"mimes":["video/mp4","video/mpeg"],"w":640,"h":480},"Native":null,"IncomingSlots":["300x250","640x480v","700x900","728x90"],"Type":"video","Bidders":{"appnexus":{"PartnerID":2,"PrebidBidderCode":"appnexus","MatchedSlot":"adunit@700x900","KGP":"_AU_@_W_x_H_","KGPV":"","IsRegex":false,"Params":{"placementId":0,"site":"12313","adtag":"45343"},"VASTTagFlag":false,"VASTTagFlags":null},"dm-alias":{"PartnerID":3,"PrebidBidderCode":"districtm","MatchedSlot":"adunit@700x900","KGP":"_AU_@_W_x_H_","KGPV":"","IsRegex":false,"Params":{"placementId":0,"site":"12313","adtag":"45343"},"VASTTagFlag":false,"VASTTagFlags":null},"pub2-alias":{"PartnerID":1,"PrebidBidderCode":"pubmatic2","MatchedSlot":"adunit@700x900","KGP":"_AU_@_W_x_H_","KGPV":"","IsRegex":false,"Params":{"publisherId":"5890","adSlot":"adunit@700x900","wrapper":{"version":1,"profile":1234}},"VASTTagFlag":false,"VASTTagFlags":null}},"NonMapped":{},"NewExt":{"data":{"pbadslot":"adunit"},"prebid":{"bidder":{"appnexus":{"placementId":0,"site":"12313","adtag":"45343"},"dm-alias":{"placementId":0,"site":"12313","adtag":"45343"},"pub2-alias":{"publisherId":"5890","adSlot":"adunit@700x900","wrapper":{"version":1,"profile":1234}}}}},"BidCtx":{},"BannerAdUnitCtx":{"MatchedSlot":"","IsRegex":false,"MatchedRegex":"","SelectedSlotAdUnitConfig":null,"AppliedSlotAdUnitConfig":null,"UsingDefaultConfig":false,"AllowedConnectionTypes":null},"VideoAdUnitCtx":{"MatchedSlot":"","IsRegex":false,"MatchedRegex":"","SelectedSlotAdUnitConfig":null,"AppliedSlotAdUnitConfig":null,"UsingDefaultConfig":false,"AllowedConnectionTypes":null},"BidderError":"","IsAdPodRequest":false}}`, `new request.ext: {"prebid":{"aliases":{"dm-alias":"appnexus","pub2-alias":"pubmatic"},"aliasgvlids":{"dm-alias":99,"pub2-alias":130},"bidderparams":{"pubmatic":{"wiid":""}},"debug":true,"floors":{"enforcement":{"enforcepbs":true},"enabled":true},"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":5,"increment":0.05},{"min":5,"max":10,"increment":0.1},{"min":10,"max":20,"increment":0.5}]},"mediatypepricegranularity":{},"includewinners":true,"includebidderkeys":true},"macros":{"[PLATFORM]":"3","[PROFILE_ID]":"1234","[PROFILE_VERSION]":"1","[UNIX_TIMESTAMP]":"0","[WRAPPER_IMPRESSION_ID]":""}}}`}, + DebugMessages: []string{`new imp: {"123":{"ImpID":"123","TagID":"adunit","Div":"","SlotName":"adunit","AdUnitName":"adunit","Secure":0,"BidFloor":4.3,"BidFloorCur":"USD","IsRewardInventory":null,"Banner":true,"Video":{"mimes":["video/mp4","video/mpeg"],"w":640,"h":480},"Native":null,"IncomingSlots":["640x480v","700x900","728x90","300x250"],"Type":"video","Bidders":{"appnexus":{"PartnerID":2,"PrebidBidderCode":"appnexus","MatchedSlot":"adunit@700x900","KGP":"_AU_@_W_x_H_","KGPV":"","IsRegex":false,"Params":{"placementId":0,"adtag":"45343","site":"12313"},"VASTTagFlag":false,"VASTTagFlags":null},"dm-alias":{"PartnerID":3,"PrebidBidderCode":"districtm","MatchedSlot":"adunit@700x900","KGP":"_AU_@_W_x_H_","KGPV":"","IsRegex":false,"Params":{"placementId":0,"site":"12313","adtag":"45343"},"VASTTagFlag":false,"VASTTagFlags":null},"pub2-alias":{"PartnerID":1,"PrebidBidderCode":"pubmatic2","MatchedSlot":"adunit@700x900","KGP":"_AU_@_W_x_H_","KGPV":"","IsRegex":false,"Params":{"publisherId":"5890","adSlot":"adunit@700x900","wrapper":{"version":1,"profile":1234}},"VASTTagFlag":false,"VASTTagFlags":null}},"NonMapped":{},"NewExt":{"data":{"pbadslot":"adunit"},"prebid":{"bidder":{"appnexus":{"placementId":0,"adtag":"45343","site":"12313"},"dm-alias":{"placementId":0,"site":"12313","adtag":"45343"},"pub2-alias":{"publisherId":"5890","adSlot":"adunit@700x900","wrapper":{"version":1,"profile":1234}}}}},"BidCtx":{},"BannerAdUnitCtx":{"MatchedSlot":"","IsRegex":false,"MatchedRegex":"","SelectedSlotAdUnitConfig":null,"AppliedSlotAdUnitConfig":null,"UsingDefaultConfig":false,"AllowedConnectionTypes":null},"VideoAdUnitCtx":{"MatchedSlot":"","IsRegex":false,"MatchedRegex":"","SelectedSlotAdUnitConfig":null,"AppliedSlotAdUnitConfig":null,"UsingDefaultConfig":false,"AllowedConnectionTypes":null},"BidderError":"","IsAdPodRequest":false}}`, `new request.ext: {"prebid":{"aliases":{"dm-alias":"appnexus","pub2-alias":"pubmatic"},"aliasgvlids":{"dm-alias":99,"pub2-alias":130},"bidadjustmentfactors":{"appnexus":1,"dm-alias":1,"pub2-alias":1},"bidderparams":{"pubmatic":{"wiid":""}},"debug":true,"floors":{"enforcement":{"enforcepbs":true},"enabled":true},"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":5,"increment":0.05},{"min":5,"max":10,"increment":0.1},{"min":10,"max":20,"increment":0.5}]},"mediatypepricegranularity":{},"includewinners":true,"includebidderkeys":true},"macros":{"[PLATFORM]":"3","[PROFILE_ID]":"1234","[PROFILE_VERSION]":"1","[UNIX_TIMESTAMP]":"0","[WRAPPER_IMPRESSION_ID]":""}}}`}, AnalyticsTags: hookanalytics.Analytics{}, }, - wantBidRequest: json.RawMessage(`{"id":"123-456-789","imp":[{"id":"123","banner":{"format":[{"w":728,"h":90},{"w":300,"h":250}],"w":700,"h":900},"video":{"mimes":["video/mp4","video/mpeg"],"w":640,"h":480},"tagid":"adunit","bidfloor":4.3,"bidfloorcur":"USD","ext":{"data":{"pbadslot":"adunit"},"prebid":{"bidder":{"appnexus":{"placementId":0,"site":"12313","adtag":"45343"},"dm-alias":{"placementId":0,"adtag":"45343","site":"12313"},"pub2-alias":{"publisherId":"5890","adSlot":"adunit@700x900","wrapper":{"version":1,"profile":1234}}}}}}],"site":{"domain":"test.com","page":"www.test.com","publisher":{"id":"5890"}},"device":{"ua":"Mozilla/5.0(X11;Linuxx86_64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/52.0.2743.82Safari/537.36","ip":"127.0.0.1"},"user":{"id":"119208432","buyeruid":"1rwe432","yob":1980,"gender":"F","customdata":"7D75D25F-FAC9-443D-B2D1-B17FEE11E027","geo":{"country":"US","region":"CA","metro":"90001","city":"Alamo"}},"wseat":["Wseat_0","Wseat_1"],"bseat":["Bseat_0","Bseat_1"],"cur":["cur_0","cur_1"],"wlang":["Wlang_0","Wlang_1"],"bcat":["bcat_0","bcat_1"],"badv":["badv_0","badv_1"],"bapp":["bapp_0","bapp_1"],"source":{"tid":"123-456-789","ext":{"omidpn":"MyIntegrationPartner","omidpv":"7.1"}},"ext":{"prebid":{"aliases":{"dm-alias":"appnexus","pub2-alias":"pubmatic"},"aliasgvlids":{"dm-alias":99,"pub2-alias":130},"bidderparams":{"pubmatic":{"wiid":""}},"debug":true,"floors":{"enforcement":{"enforcepbs":true},"enabled":true},"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":5,"increment":0.05},{"min":5,"max":10,"increment":0.1},{"min":10,"max":20,"increment":0.5}]},"mediatypepricegranularity":{},"includewinners":true,"includebidderkeys":true},"macros":{"[PLATFORM]":"3","[PROFILE_ID]":"1234","[PROFILE_VERSION]":"1","[UNIX_TIMESTAMP]":"0","[WRAPPER_IMPRESSION_ID]":""}}}}`), + wantBidRequest: json.RawMessage(`{"id":"123-456-789","imp":[{"id":"123","banner":{"format":[{"w":728,"h":90},{"w":300,"h":250}],"w":700,"h":900},"video":{"mimes":["video/mp4","video/mpeg"],"w":640,"h":480},"tagid":"adunit","bidfloor":4.3,"bidfloorcur":"USD","ext":{"data":{"pbadslot":"adunit"},"prebid":{"bidder":{"appnexus":{"placementId":0,"adtag":"45343","site":"12313"},"dm-alias":{"placementId":0,"site":"12313","adtag":"45343"},"pub2-alias":{"publisherId":"5890","adSlot":"adunit@700x900","wrapper":{"version":1,"profile":1234}}}}}}],"site":{"domain":"test.com","page":"www.test.com","publisher":{"id":"5890"}},"device":{"ua":"Mozilla/5.0(X11;Linuxx86_64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/52.0.2743.82Safari/537.36","ip":"127.0.0.1"},"user":{"id":"119208432","buyeruid":"1rwe432","yob":1980,"gender":"F","customdata":"7D75D25F-FAC9-443D-B2D1-B17FEE11E027","geo":{"country":"US","region":"CA","metro":"90001","city":"Alamo"}},"wseat":["Wseat_0","Wseat_1"],"bseat":["Bseat_0","Bseat_1"],"cur":["cur_0","cur_1"],"wlang":["Wlang_0","Wlang_1"],"bcat":["bcat_0","bcat_1"],"badv":["badv_0","badv_1"],"bapp":["bapp_0","bapp_1"],"source":{"tid":"123-456-789","ext":{"omidpn":"MyIntegrationPartner","omidpv":"7.1"}},"ext":{"prebid":{"aliases":{"dm-alias":"appnexus","pub2-alias":"pubmatic"},"aliasgvlids":{"dm-alias":99,"pub2-alias":130},"bidadjustmentfactors":{"appnexus":1,"dm-alias":1,"pub2-alias":1},"bidderparams":{"pubmatic":{"wiid":""}},"debug":true,"floors":{"enforcement":{"enforcepbs":true},"enabled":true},"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":5,"increment":0.05},{"min":5,"max":10,"increment":0.1},{"min":10,"max":20,"increment":0.5}]},"mediatypepricegranularity":{},"includewinners":true,"includebidderkeys":true},"macros":{"[PLATFORM]":"3","[PROFILE_ID]":"1234","[PROFILE_VERSION]":"1","[UNIX_TIMESTAMP]":"0","[WRAPPER_IMPRESSION_ID]":""}}}}`), wantErr: false, doMutate: true, }, @@ -2768,10 +2767,10 @@ func TestOpenWrap_handleBeforeValidationHook(t *testing.T) { Reject: false, NbrCode: 0, ChangeSet: hookstage.ChangeSet[hookstage.BeforeValidationRequestPayload]{}, - DebugMessages: []string{`new imp: {"123":{"ImpID":"123","TagID":"adunit","Div":"","SlotName":"adunit","AdUnitName":"adunit","Secure":0,"BidFloor":4.3,"BidFloorCur":"USD","IsRewardInventory":null,"Banner":true,"Video":{"mimes":["video/mp4","video/mpeg"],"w":640,"h":480},"Native":null,"IncomingSlots":["700x900","728x90","300x250","640x480v"],"Type":"video","Bidders":{"appnexus":{"PartnerID":2,"PrebidBidderCode":"appnexus","MatchedSlot":"adunit@700x900","KGP":"_AU_@_W_x_H_","KGPV":"","IsRegex":false,"Params":{"placementId":0,"site":"12313","adtag":"45343"},"VASTTagFlag":false,"VASTTagFlags":null}},"NonMapped":{},"NewExt":{"data":{"pbadslot":"adunit"},"prebid":{"bidder":{"appnexus":{"placementId":0,"site":"12313","adtag":"45343"}}}},"BidCtx":{},"BannerAdUnitCtx":{"MatchedSlot":"","IsRegex":false,"MatchedRegex":"","SelectedSlotAdUnitConfig":null,"AppliedSlotAdUnitConfig":null,"UsingDefaultConfig":false,"AllowedConnectionTypes":null},"VideoAdUnitCtx":{"MatchedSlot":"","IsRegex":false,"MatchedRegex":"","SelectedSlotAdUnitConfig":null,"AppliedSlotAdUnitConfig":null,"UsingDefaultConfig":false,"AllowedConnectionTypes":null},"BidderError":"","IsAdPodRequest":false}}`, `new request.ext: {"prebid":{"bidderparams":{"pubmatic":{"wiid":""}},"debug":true,"floors":{"enforcement":{"enforcepbs":true},"enabled":true},"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":5,"increment":0.05},{"min":5,"max":10,"increment":0.1},{"min":10,"max":20,"increment":0.5}]},"mediatypepricegranularity":{},"includewinners":true,"includebidderkeys":true},"macros":{"[PLATFORM]":"3","[PROFILE_ID]":"1234","[PROFILE_VERSION]":"1","[UNIX_TIMESTAMP]":"0","[WRAPPER_IMPRESSION_ID]":""}}}`}, + DebugMessages: []string{`new imp: {"123":{"ImpID":"123","TagID":"adunit","Div":"","SlotName":"adunit","AdUnitName":"adunit","Secure":0,"BidFloor":4.3,"BidFloorCur":"USD","IsRewardInventory":null,"Banner":true,"Video":{"mimes":["video/mp4","video/mpeg"],"w":640,"h":480},"Native":null,"IncomingSlots":["300x250","640x480v","700x900","728x90"],"Type":"video","Bidders":{"appnexus":{"PartnerID":2,"PrebidBidderCode":"appnexus","MatchedSlot":"adunit@700x900","KGP":"_AU_@_W_x_H_","KGPV":"","IsRegex":false,"Params":{"placementId":0,"site":"12313","adtag":"45343"},"VASTTagFlag":false,"VASTTagFlags":null}},"NonMapped":{},"NewExt":{"data":{"pbadslot":"adunit"},"prebid":{"bidder":{"appnexus":{"placementId":0,"site":"12313","adtag":"45343"}}}},"BidCtx":{},"BannerAdUnitCtx":{"MatchedSlot":"","IsRegex":false,"MatchedRegex":"","SelectedSlotAdUnitConfig":null,"AppliedSlotAdUnitConfig":null,"UsingDefaultConfig":false,"AllowedConnectionTypes":null},"VideoAdUnitCtx":{"MatchedSlot":"","IsRegex":false,"MatchedRegex":"","SelectedSlotAdUnitConfig":null,"AppliedSlotAdUnitConfig":null,"UsingDefaultConfig":false,"AllowedConnectionTypes":null},"BidderError":"","IsAdPodRequest":false}}`, `new request.ext: {"prebid":{"bidadjustmentfactors":{"appnexus":1},"bidderparams":{"pubmatic":{"wiid":""}},"debug":true,"floors":{"enforcement":{"enforcepbs":true},"enabled":true},"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":5,"increment":0.05},{"min":5,"max":10,"increment":0.1},{"min":10,"max":20,"increment":0.5}]},"mediatypepricegranularity":{},"includewinners":true,"includebidderkeys":true},"macros":{"[PLATFORM]":"3","[PROFILE_ID]":"1234","[PROFILE_VERSION]":"1","[UNIX_TIMESTAMP]":"0","[WRAPPER_IMPRESSION_ID]":""}}}`}, AnalyticsTags: hookanalytics.Analytics{}, }, - wantBidRequest: json.RawMessage(`{"id":"123-456-789","imp":[{"id":"123","banner":{"format":[{"w":728,"h":90},{"w":300,"h":250}],"w":700,"h":900},"video":{"mimes":["video/mp4","video/mpeg"],"w":640,"h":480},"tagid":"adunit","bidfloor":4.3,"bidfloorcur":"USD","ext":{"data":{"pbadslot":"adunit"},"prebid":{"bidder":{"appnexus":{"placementId":0,"adtag":"45343","site":"12313"}}}}}],"site":{"domain":"test.com","page":"www.test.com","publisher":{"id":"5890"}},"device":{"ua":"Mozilla/5.0(X11;Linuxx86_64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/52.0.2743.82Safari/537.36","ip":"127.0.0.1"},"user":{"id":"119208432","buyeruid":"1rwe432","yob":1980,"gender":"F","customdata":"7D75D25F-FAC9-443D-B2D1-B17FEE11E027","geo":{"country":"US","region":"CA","metro":"90001","city":"Alamo"}},"wseat":["Wseat_0","Wseat_1"],"bseat":["Bseat_0","Bseat_1"],"cur":["cur_0","cur_1"],"wlang":["Wlang_0","Wlang_1"],"bcat":["bcat_0","bcat_1"],"badv":["badv_0","badv_1"],"bapp":["bapp_0","bapp_1"],"source":{"tid":"123-456-789","ext":{"omidpn":"MyIntegrationPartner","omidpv":"7.1"}},"ext":{"prebid":{"bidderparams":{"pubmatic":{"wiid":""}},"debug":true,"floors":{"enforcement":{"enforcepbs":true},"enabled":true},"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":5,"increment":0.05},{"min":5,"max":10,"increment":0.1},{"min":10,"max":20,"increment":0.5}]},"mediatypepricegranularity":{},"includewinners":true,"includebidderkeys":true},"macros":{"[PLATFORM]":"3","[PROFILE_ID]":"1234","[PROFILE_VERSION]":"1","[UNIX_TIMESTAMP]":"0","[WRAPPER_IMPRESSION_ID]":""}}}}`), + wantBidRequest: json.RawMessage(`{"id":"123-456-789","imp":[{"id":"123","banner":{"format":[{"w":728,"h":90},{"w":300,"h":250}],"w":700,"h":900},"video":{"mimes":["video/mp4","video/mpeg"],"w":640,"h":480},"tagid":"adunit","bidfloor":4.3,"bidfloorcur":"USD","ext":{"data":{"pbadslot":"adunit"},"prebid":{"bidder":{"appnexus":{"placementId":0,"site":"12313","adtag":"45343"}}}}}],"site":{"domain":"test.com","page":"www.test.com","publisher":{"id":"5890"}},"device":{"ua":"Mozilla/5.0(X11;Linuxx86_64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/52.0.2743.82Safari/537.36","ip":"127.0.0.1"},"user":{"id":"119208432","buyeruid":"1rwe432","yob":1980,"gender":"F","customdata":"7D75D25F-FAC9-443D-B2D1-B17FEE11E027","geo":{"country":"US","region":"CA","metro":"90001","city":"Alamo"}},"wseat":["Wseat_0","Wseat_1"],"bseat":["Bseat_0","Bseat_1"],"cur":["cur_0","cur_1"],"wlang":["Wlang_0","Wlang_1"],"bcat":["bcat_0","bcat_1"],"badv":["badv_0","badv_1"],"bapp":["bapp_0","bapp_1"],"source":{"tid":"123-456-789","ext":{"omidpn":"MyIntegrationPartner","omidpv":"7.1"}},"ext":{"prebid":{"bidadjustmentfactors":{"appnexus":1},"bidderparams":{"pubmatic":{"wiid":""}},"debug":true,"floors":{"enforcement":{"enforcepbs":true},"enabled":true},"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":5,"increment":0.05},{"min":5,"max":10,"increment":0.1},{"min":10,"max":20,"increment":0.5}]},"mediatypepricegranularity":{},"includewinners":true,"includebidderkeys":true},"macros":{"[PLATFORM]":"3","[PROFILE_ID]":"1234","[PROFILE_VERSION]":"1","[UNIX_TIMESTAMP]":"0","[WRAPPER_IMPRESSION_ID]":""}}}}`), wantErr: false, doMutate: true, }, @@ -2978,6 +2977,299 @@ func TestUserAgent_handleBeforeValidationHook(t *testing.T) { } } +func TestImpBidCtx_handleBeforeValidationHook(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockCache := mock_cache.NewMockCache(ctrl) + mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) + type fields struct { + cfg config.Config + cache cache.Cache + metricEngine metrics.MetricsEngine + } + type args struct { + ctx context.Context + moduleCtx hookstage.ModuleInvocationContext + payload hookstage.BeforeValidationRequestPayload + bidrequest json.RawMessage + } + type want struct { + rctx *models.RequestCtx + error bool + } + tests := []struct { + name string + fields fields + args args + want want + setup func() + }{ + { + name: "default_impctx_if_getProfileData_fails", + args: args{ + ctx: context.Background(), + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": rctx, + }, + }, + bidrequest: json.RawMessage(`{"id":"123-456-789","imp":[{"id":"123","video":{"mimes":["video/mp4","video/mpeg"],"w":640,"h":480},"tagid":"adunit","ext":{"wrapper":{"div":"div"},"bidder":{"pubmatic":{"keywords":[{"key":"pmzoneid","value":["val1","val2"]}]}},"prebid":{}}}],"site":{"domain":"test.com","page":"www.test.com","publisher":{"id":"5890"}},"device":{"ua":"Mozilla/5.0(X11;Linuxx86_64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/52.0.2743.82Safari/537.36","ip":"123.145.167.10"},"user":{"id":"119208432","buyeruid":"1rwe432","yob":1980,"gender":"F","geo":{"country":"US","region":"CA","metro":"90001","city":"Alamo"}},"wseat":["Wseat_0","Wseat_1"],"bseat":["Bseat_0","Bseat_1"],"cur":["cur_0","cur_1"],"wlang":["Wlang_0","Wlang_1"],"bcat":["bcat_0","bcat_1"],"badv":["badv_0","badv_1"],"bapp":["bapp_0","bapp_1"],"source":{"ext":{"omidpn":"MyIntegrationPartner","omidpv":"7.1"}},"ext":{"prebid":{},"wrapper":{"test":123,"profileid":123,"versionid":1,"wiid":"test_display_wiid"}}}`), + }, + fields: fields{ + cache: mockCache, + metricEngine: mockEngine, + }, + setup: func() { + mockCache.EXPECT().GetPartnerConfigMap(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(map[int]map[string]string{ + 2: { + models.PARTNER_ID: "2", + models.PREBID_PARTNER_NAME: "appnexus", + models.BidderCode: "appnexus", + models.SERVER_SIDE_FLAG: "1", + models.KEY_GEN_PATTERN: "_AU_@_W_x_H_", + models.TIMEOUT: "200", + }, + -1: { + models.PLATFORM_KEY: models.PLATFORM_APP, + models.DisplayVersionID: "1", + }, + }, errors.New("test")) + mockEngine.EXPECT().RecordPublisherProfileRequests("5890", "1234") + mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, getPubmaticErrorCode(nbr.InvalidProfileConfiguration)) + mockEngine.EXPECT().RecordNobidErrPrebidServerRequests("5890", nbr.InvalidProfileConfiguration) + mockEngine.EXPECT().RecordPublisherInvalidProfileRequests(rctx.Endpoint, "5890", rctx.ProfileIDStr) + mockEngine.EXPECT().RecordPublisherInvalidProfileImpressions("5890", rctx.ProfileIDStr, gomock.Any()) + }, + want: want{ + rctx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "123": { + IncomingSlots: []string{ + "640x480v", + }, + SlotName: "adunit", + AdUnitName: "adunit", + }, + }, + }, + error: true, + }, + }, + { + name: "default_impctx_if_platform_is_missing", + args: args{ + ctx: context.Background(), + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": rctx, + }, + }, + bidrequest: json.RawMessage(`{"id":"123-456-789","imp":[{"id":"123","video":{"mimes":["video/mp4","video/mpeg"],"w":640,"h":480},"tagid":"adunit","ext":{"wrapper":{"div":"div"},"bidder":{"pubmatic":{"keywords":[{"key":"pmzoneid","value":["val1","val2"]}]}},"prebid":{}}}],"site":{"domain":"test.com","page":"www.test.com","publisher":{"id":"5890"}},"device":{"ua":"Mozilla/5.0(X11;Linuxx86_64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/52.0.2743.82Safari/537.36","ip":"123.145.167.10"},"user":{"id":"119208432","buyeruid":"1rwe432","yob":1980,"gender":"F","geo":{"country":"US","region":"CA","metro":"90001","city":"Alamo"}},"wseat":["Wseat_0","Wseat_1"],"bseat":["Bseat_0","Bseat_1"],"cur":["cur_0","cur_1"],"wlang":["Wlang_0","Wlang_1"],"bcat":["bcat_0","bcat_1"],"badv":["badv_0","badv_1"],"bapp":["bapp_0","bapp_1"],"source":{"ext":{"omidpn":"MyIntegrationPartner","omidpv":"7.1"}},"ext":{"prebid":{},"wrapper":{"test":123,"profileid":123,"versionid":1,"wiid":"test_display_wiid"}}}`), + }, + fields: fields{ + cache: mockCache, + metricEngine: mockEngine, + }, + setup: func() { + mockCache.EXPECT().GetPartnerConfigMap(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(map[int]map[string]string{ + 2: { + models.PARTNER_ID: "2", + models.PREBID_PARTNER_NAME: "appnexus", + models.BidderCode: "appnexus", + models.SERVER_SIDE_FLAG: "1", + models.KEY_GEN_PATTERN: "_AU_@_W_x_H_", + models.TIMEOUT: "200", + }, + -1: { + models.DisplayVersionID: "1", + }, + }, nil) + //prometheus metrics + mockEngine.EXPECT().RecordPublisherProfileRequests("5890", "1234") + mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, getPubmaticErrorCode(nbr.InvalidPlatform)) + mockEngine.EXPECT().RecordNobidErrPrebidServerRequests("5890", nbr.InvalidPlatform) + mockEngine.EXPECT().RecordPublisherInvalidProfileRequests(rctx.Endpoint, "5890", rctx.ProfileIDStr) + mockEngine.EXPECT().RecordPublisherInvalidProfileImpressions("5890", rctx.ProfileIDStr, gomock.Any()) + }, + want: want{ + rctx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "123": { + IncomingSlots: []string{ + "640x480v", + }, + SlotName: "adunit", + AdUnitName: "adunit", + }, + }, + }, + error: true, + }, + }, + { + name: "default_impctx_if_all_partners_throttled", + args: args{ + ctx: context.Background(), + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": rctx, + }, + }, + bidrequest: json.RawMessage(`{"id":"123-456-789","imp":[{"id":"123","video":{"mimes":["video/mp4","video/mpeg"],"w":640,"h":480},"tagid":"adunit","ext":{"wrapper":{"div":"div"},"bidder":{"pubmatic":{"keywords":[{"key":"pmzoneid","value":["val1","val2"]}]}},"prebid":{}}}],"site":{"domain":"test.com","page":"www.test.com","publisher":{"id":"5890"}},"device":{"ua":"Mozilla/5.0(X11;Linuxx86_64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/52.0.2743.82Safari/537.36","ip":"123.145.167.10"},"user":{"id":"119208432","buyeruid":"1rwe432","yob":1980,"gender":"F","geo":{"country":"US","region":"CA","metro":"90001","city":"Alamo"}},"wseat":["Wseat_0","Wseat_1"],"bseat":["Bseat_0","Bseat_1"],"cur":["cur_0","cur_1"],"wlang":["Wlang_0","Wlang_1"],"bcat":["bcat_0","bcat_1"],"badv":["badv_0","badv_1"],"bapp":["bapp_0","bapp_1"],"source":{"ext":{"omidpn":"MyIntegrationPartner","omidpv":"7.1"}},"ext":{"prebid":{},"wrapper":{"test":123,"profileid":123,"versionid":1,"wiid":"test_display_wiid"}}}`), + }, + fields: fields{ + cache: mockCache, + metricEngine: mockEngine, + }, + setup: func() { + mockCache.EXPECT().GetPartnerConfigMap(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(map[int]map[string]string{ + 2: { + models.PARTNER_ID: "2", + models.PREBID_PARTNER_NAME: "appnexus", + models.BidderCode: "appnexus", + models.SERVER_SIDE_FLAG: "1", + models.KEY_GEN_PATTERN: "_AU_@_W_x_H_", + models.TIMEOUT: "200", + models.THROTTLE: "0", + }, + -1: { + models.DisplayVersionID: "1", + models.PLATFORM_KEY: models.PLATFORM_APP, + }, + }, nil) + //prometheus metrics + mockEngine.EXPECT().RecordPublisherProfileRequests("5890", "1234") + mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, getPubmaticErrorCode(nbr.AllPartnerThrottled)) + mockEngine.EXPECT().RecordNobidErrPrebidServerRequests("5890", nbr.AllPartnerThrottled) + mockEngine.EXPECT().RecordPublisherRequests(rctx.Endpoint, "5890", rctx.Platform) + }, + want: want{ + error: false, + rctx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{ + "123": { + IncomingSlots: []string{ + "640x480v", + }, + SlotName: "adunit", + AdUnitName: "adunit", + }, + }, + }, + }, + }, + { + name: "empty_impctx_if_TagID_not_present_in_imp", + args: args{ + ctx: context.Background(), + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": rctx, + }, + }, + bidrequest: json.RawMessage(`{"id":"123-456-789","imp":[{"id":"123","video":{"mimes":["video/mp4","video/mpeg"],"w":640,"h":480},"ext":{"wrapper":{"div":"div"},"bidder":{"pubmatic":{"keywords":[{"key":"pmzoneid","value":["val1","val2"]}]}},"prebid":{}}},{"id":"456","video":{"mimes":["video/mp4","video/mpeg"],"w":640,"h":480},"ext":{"wrapper":{"div":"div"},"bidder":{"pubmatic":{"keywords":[{"key":"pmzoneid","value":["val1","val2"]}]}},"prebid":{}}}],"site":{"domain":"test.com","page":"www.test.com","publisher":{"id":"5890"}},"device":{"ua":"Mozilla/5.0(X11;Linuxx86_64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/52.0.2743.82Safari/537.36","ip":"123.145.167.10"},"user":{"id":"119208432"},"source":{"ext":{"omidpn":"MyIntegrationPartner","omidpv":"7.1"}},"ext":{"prebid":{},"wrapper":{"test":123,"profileid":123,"versionid":1,"wiid":"test_display_wiid"}}}`), + }, + fields: fields{ + cache: mockCache, + metricEngine: mockEngine, + }, + setup: func() { + mockCache.EXPECT().GetPartnerConfigMap(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(map[int]map[string]string{ + 2: { + models.PARTNER_ID: "2", + models.PREBID_PARTNER_NAME: "appnexus", + models.BidderCode: "appnexus", + models.SERVER_SIDE_FLAG: "1", + models.KEY_GEN_PATTERN: "_AU_@_W_x_H_", + models.TIMEOUT: "200", + }, + -1: { + models.DisplayVersionID: "1", + models.PLATFORM_KEY: models.PLATFORM_APP, + }, + }, nil) + mockCache.EXPECT().GetAdunitConfigFromCache(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&adunitconfig.AdUnitConfig{}) + //prometheus metrics + mockEngine.EXPECT().RecordPublisherProfileRequests("5890", "1234") + mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, getPubmaticErrorCode(nbr.InvalidImpressionTagID)) + mockEngine.EXPECT().RecordNobidErrPrebidServerRequests("5890", nbr.InvalidImpressionTagID) + mockEngine.EXPECT().RecordPublisherRequests(rctx.Endpoint, "5890", rctx.Platform) + }, + want: want{ + rctx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{}, + }, + error: true, + }, + }, + { + name: "empty_impctx_if_imp_ext_parse_fails", + args: args{ + ctx: context.Background(), + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": rctx, + }, + }, + bidrequest: json.RawMessage(`{"id":"123-456-789","imp":[{"id":"123","video":{"mimes":["video/mp4","video/mpeg"],"w":640,"h":480},"ext":{"wrapper":"invalid","bidder":{"pubmatic":{"keywords":[{"key":"pmzoneid","value":["val1","val2"]}]}},"prebid":{}}},{"id":"456","video":{"mimes":["video/mp4","video/mpeg"],"w":640,"h":480},"ext":{"wrapper":{"div":"div"},"bidder":{"pubmatic":{"keywords":[{"key":"pmzoneid","value":["val1","val2"]}]}},"prebid":{}}}],"site":{"domain":"test.com","page":"www.test.com","publisher":{"id":"5890"}},"device":{"ua":"Mozilla/5.0(X11;Linuxx86_64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/52.0.2743.82Safari/537.36","ip":"123.145.167.10"},"user":{"id":"119208432"},"source":{"ext":{"omidpn":"MyIntegrationPartner","omidpv":"7.1"}},"ext":{"prebid":{},"wrapper":{"test":123,"profileid":123,"versionid":1,"wiid":"test_display_wiid"}}}`), + }, + fields: fields{ + cache: mockCache, + metricEngine: mockEngine, + }, + setup: func() { + mockCache.EXPECT().GetPartnerConfigMap(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(map[int]map[string]string{ + 2: { + models.PARTNER_ID: "2", + models.PREBID_PARTNER_NAME: "appnexus", + models.BidderCode: "appnexus", + models.SERVER_SIDE_FLAG: "1", + models.KEY_GEN_PATTERN: "_AU_@_W_x_H_", + models.TIMEOUT: "200", + }, + -1: { + models.DisplayVersionID: "1", + models.PLATFORM_KEY: models.PLATFORM_APP, + }, + }, nil) + mockCache.EXPECT().GetAdunitConfigFromCache(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&adunitconfig.AdUnitConfig{}) + //prometheus metrics + mockEngine.EXPECT().RecordPublisherProfileRequests("5890", "1234") + mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, getPubmaticErrorCode(nbr.InternalError)) + mockEngine.EXPECT().RecordNobidErrPrebidServerRequests("5890", nbr.InternalError) + mockEngine.EXPECT().RecordPublisherRequests(rctx.Endpoint, "5890", rctx.Platform) + }, + want: want{ + rctx: &models.RequestCtx{ + ImpBidCtx: map[string]models.ImpCtx{}, + }, + error: true, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } + adapters.InitBidders("./static/bidder-params/") + m := OpenWrap{ + cfg: tt.fields.cfg, + cache: tt.fields.cache, + metricEngine: tt.fields.metricEngine, + } + tt.args.payload.BidRequest = &openrtb2.BidRequest{} + json.Unmarshal(tt.args.bidrequest, tt.args.payload.BidRequest) + + _, err := m.handleBeforeValidationHook(tt.args.ctx, tt.args.moduleCtx, tt.args.payload) + assert.Equal(t, tt.want.error, err != nil, "mismatched error") + iRctx := tt.args.moduleCtx.ModuleContext["rctx"] + gotRctx := iRctx.(models.RequestCtx) + assert.Equal(t, tt.want.rctx.ImpBidCtx, gotRctx.ImpBidCtx, "mismatched rctx.ImpBidCtx") + }) + } +} + func TestGetSlotName(t *testing.T) { type args struct { tagId string diff --git a/modules/pubmatic/openwrap/device.go b/modules/pubmatic/openwrap/device.go index 6b608c23451..1e5ae197851 100644 --- a/modules/pubmatic/openwrap/device.go +++ b/modules/pubmatic/openwrap/device.go @@ -1,7 +1,6 @@ package openwrap import ( - "encoding/json" "strings" "github.com/prebid/openrtb/v19/openrtb2" @@ -21,7 +20,7 @@ func populateDeviceContext(dvc *models.DeviceCtx, device *openrtb2.Device) { //unmarshal device ext var deviceExt models.ExtDevice - if err := json.Unmarshal(device.Ext, &deviceExt); err != nil { + if err := deviceExt.UnmarshalJSON(device.Ext); err != nil { return } dvc.Ext = &deviceExt @@ -36,29 +35,45 @@ func updateDeviceIFADetails(dvc *models.DeviceCtx) { } deviceExt := dvc.Ext - deviceExt.IFAType = strings.TrimSpace(deviceExt.IFAType) - deviceExt.SessionID = strings.TrimSpace(deviceExt.SessionID) - - //refactor below condition - if deviceExt.IFAType != "" { - if dvc.DeviceIFA != "" { - if _, ok := models.DeviceIFATypeID[strings.ToLower(deviceExt.IFAType)]; !ok { - deviceExt.IFAType = "" - } - } else if deviceExt.SessionID != "" { - dvc.DeviceIFA = deviceExt.SessionID - deviceExt.IFAType = models.DeviceIFATypeSESSIONID - } else { - deviceExt.IFAType = "" + extIFATypeStr, _ := deviceExt.GetIFAType() + extSessionIDStr, _ := deviceExt.GetSessionID() + + if extIFATypeStr == "" { + if extSessionIDStr == "" { + deviceExt.DeleteIFAType() + deviceExt.DeleteSessionID() + return + } + dvc.DeviceIFA = extSessionIDStr + extIFATypeStr = models.DeviceIFATypeSESSIONID + } + if dvc.DeviceIFA != "" { + if _, ok := models.DeviceIFATypeID[strings.ToLower(extIFATypeStr)]; !ok { + extIFATypeStr = "" } - } else if deviceExt.SessionID != "" { - dvc.DeviceIFA = deviceExt.SessionID - deviceExt.IFAType = models.DeviceIFATypeSESSIONID + } else if extSessionIDStr != "" { + dvc.DeviceIFA = extSessionIDStr + extIFATypeStr = models.DeviceIFATypeSESSIONID + + } else { + extIFATypeStr = "" } - if ifaTypeID, ok := models.DeviceIFATypeID[strings.ToLower(deviceExt.IFAType)]; ok { + if ifaTypeID, ok := models.DeviceIFATypeID[strings.ToLower(extIFATypeStr)]; ok { dvc.IFATypeID = &ifaTypeID } + + if extIFATypeStr == "" { + deviceExt.DeleteIFAType() + } else { + deviceExt.SetIFAType(extIFATypeStr) + } + + if extSessionIDStr == "" { + deviceExt.DeleteSessionID() + } else { + deviceExt.SetSessionID(extSessionIDStr) + } } func amendDeviceObject(device *openrtb2.Device, dvc *models.DeviceCtx) { @@ -73,6 +88,6 @@ func amendDeviceObject(device *openrtb2.Device, dvc *models.DeviceCtx) { //update device extension if dvc.Ext != nil { - device.Ext, _ = json.Marshal(dvc.Ext) + device.Ext, _ = dvc.Ext.MarshalJSON() } } diff --git a/modules/pubmatic/openwrap/device_test.go b/modules/pubmatic/openwrap/device_test.go index 633f8d9333e..9cbfd4877ff 100644 --- a/modules/pubmatic/openwrap/device_test.go +++ b/modules/pubmatic/openwrap/device_test.go @@ -7,7 +7,6 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/prebid-server/modules/pubmatic/openwrap/models" - "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/util/ptrutil" "github.com/stretchr/testify/assert" ) @@ -68,7 +67,11 @@ func TestPopulateDeviceExt(t *testing.T) { want: want{ deviceCtx: models.DeviceCtx{ DeviceIFA: `test_ifa`, - Ext: &models.ExtDevice{}, + Ext: func() *models.ExtDevice { + deviceExt := &models.ExtDevice{} + deviceExt.UnmarshalJSON([]byte(`{"anykey": "anyval"}`)) + return deviceExt + }(), }, }, }, @@ -82,6 +85,7 @@ func TestPopulateDeviceExt(t *testing.T) { want: want{ deviceCtx: models.DeviceCtx{ DeviceIFA: `test_ifa`, + Ext: models.NewExtDevice(), }, }, }, @@ -97,7 +101,7 @@ func TestPopulateDeviceExt(t *testing.T) { /* removed_invalid_ifatype */ deviceCtx: models.DeviceCtx{ DeviceIFA: `test_ifa`, - Ext: &models.ExtDevice{}, + Ext: models.NewExtDevice(), }, }, }, @@ -113,11 +117,11 @@ func TestPopulateDeviceExt(t *testing.T) { deviceCtx: models.DeviceCtx{ DeviceIFA: `test_ifa`, IFATypeID: ptrutil.ToPtr(models.DeviceIFATypeID[models.DeviceIFATypeDPID]), - Ext: &models.ExtDevice{ - ExtDevice: openrtb_ext.ExtDevice{ - IFAType: `DpId`, - }, - }, + Ext: func() *models.ExtDevice { + deviceExt := &models.ExtDevice{} + deviceExt.SetIFAType("DpId") + return deviceExt + }(), }, }, }, @@ -133,11 +137,11 @@ func TestPopulateDeviceExt(t *testing.T) { deviceCtx: models.DeviceCtx{ DeviceIFA: `test_ifa`, IFATypeID: ptrutil.ToPtr(models.DeviceIFATypeID[models.DeviceIFATypeSESSIONID]), - Ext: &models.ExtDevice{ - ExtDevice: openrtb_ext.ExtDevice{ - IFAType: `sessionid`, - }, - }, + Ext: func() *models.ExtDevice { + deviceExt := &models.ExtDevice{} + deviceExt.SetIFAType("sessionid") + return deviceExt + }(), }, }, }, @@ -150,7 +154,7 @@ func TestPopulateDeviceExt(t *testing.T) { }, want: want{ deviceCtx: models.DeviceCtx{ - Ext: &models.ExtDevice{}, + Ext: models.NewExtDevice(), }, }, }, @@ -165,6 +169,11 @@ func TestPopulateDeviceExt(t *testing.T) { want: want{ deviceCtx: models.DeviceCtx{ DeviceIFA: `test_ifa`, + Ext: func() *models.ExtDevice { + deviceExt := &models.ExtDevice{} + deviceExt.UnmarshalJSON([]byte(`{"atts":"invalid_value"}`)) + return deviceExt + }(), }, }, }, @@ -177,11 +186,11 @@ func TestPopulateDeviceExt(t *testing.T) { }, want: want{ deviceCtx: models.DeviceCtx{ - Ext: &models.ExtDevice{ - ExtDevice: openrtb_ext.ExtDevice{ - ATTS: ptrutil.ToPtr(openrtb_ext.IOSAppTrackingStatusRestricted), - }, - }, + Ext: func() *models.ExtDevice { + deviceExt := &models.ExtDevice{} + deviceExt.UnmarshalJSON([]byte(`{"atts":1}`)) + return deviceExt + }(), }, }, }, @@ -197,12 +206,12 @@ func TestPopulateDeviceExt(t *testing.T) { deviceCtx: models.DeviceCtx{ DeviceIFA: `test_ifa`, IFATypeID: ptrutil.ToPtr(models.DeviceIFATypeID[models.DeviceIFATypeSESSIONID]), - Ext: &models.ExtDevice{ - ExtDevice: openrtb_ext.ExtDevice{ - IFAType: `sessionid`, - ATTS: ptrutil.ToPtr(openrtb_ext.IOSAppTrackingStatusRestricted), - }, - }, + Ext: func() *models.ExtDevice { + deviceExt := &models.ExtDevice{} + deviceExt.UnmarshalJSON([]byte(`{"atts":1}`)) + deviceExt.SetIFAType("sessionid") + return deviceExt + }(), }, }, }, @@ -259,15 +268,15 @@ func TestUpdateDeviceIFADetails(t *testing.T) { name: `ifa_type_present_ifa_missing`, args: args{ dvc: &models.DeviceCtx{ - Ext: &models.ExtDevice{ - ExtDevice: openrtb_ext.ExtDevice{ - IFAType: models.DeviceIFATypeDPID, - }, - }, + Ext: func() *models.ExtDevice { + ext := &models.ExtDevice{} + ext.SetIFAType(models.DeviceIFATypeDPID) + return ext + }(), }, }, want: &models.DeviceCtx{ - Ext: &models.ExtDevice{}, + Ext: models.NewExtDevice(), }, }, { @@ -275,18 +284,16 @@ func TestUpdateDeviceIFADetails(t *testing.T) { args: args{ dvc: &models.DeviceCtx{ DeviceIFA: `sample_ifa_value`, - Ext: &models.ExtDevice{ - ExtDevice: openrtb_ext.ExtDevice{ - IFAType: `wrong_ifa_type`, - }, - }, + Ext: func() *models.ExtDevice { + ext := &models.ExtDevice{} + ext.SetIFAType("wrong_ifa_type") + return ext + }(), }, }, want: &models.DeviceCtx{ DeviceIFA: `sample_ifa_value`, - Ext: &models.ExtDevice{ - ExtDevice: openrtb_ext.ExtDevice{}, - }, + Ext: models.NewExtDevice(), }, }, { @@ -294,21 +301,21 @@ func TestUpdateDeviceIFADetails(t *testing.T) { args: args{ dvc: &models.DeviceCtx{ DeviceIFA: `sample_ifa_value`, - Ext: &models.ExtDevice{ - ExtDevice: openrtb_ext.ExtDevice{ - IFAType: models.DeviceIFATypeDPID, - }, - }, + Ext: func() *models.ExtDevice { + ext := &models.ExtDevice{} + ext.SetIFAType(models.DeviceIFATypeDPID) + return ext + }(), }, }, want: &models.DeviceCtx{ DeviceIFA: `sample_ifa_value`, IFATypeID: ptrutil.ToPtr(models.DeviceIFATypeID[models.DeviceIFATypeDPID]), - Ext: &models.ExtDevice{ - ExtDevice: openrtb_ext.ExtDevice{ - IFAType: models.DeviceIFATypeDPID, - }, - }, + Ext: func() *models.ExtDevice { + ext := &models.ExtDevice{} + ext.SetIFAType(models.DeviceIFATypeDPID) + return ext + }(), }, }, { @@ -316,61 +323,59 @@ func TestUpdateDeviceIFADetails(t *testing.T) { args: args{ dvc: &models.DeviceCtx{ DeviceIFA: `sample_ifa_value`, - Ext: &models.ExtDevice{ - ExtDevice: openrtb_ext.ExtDevice{ - IFAType: strings.ToUpper(models.DeviceIFATypeDPID), - }, - }, + Ext: func() *models.ExtDevice { + ext := &models.ExtDevice{} + ext.SetIFAType(strings.ToUpper(models.DeviceIFATypeDPID)) + return ext + }(), }, }, want: &models.DeviceCtx{ DeviceIFA: `sample_ifa_value`, IFATypeID: ptrutil.ToPtr(models.DeviceIFATypeID[models.DeviceIFATypeDPID]), - Ext: &models.ExtDevice{ - ExtDevice: openrtb_ext.ExtDevice{ - IFAType: strings.ToUpper(models.DeviceIFATypeDPID), - }, - }, + Ext: func() *models.ExtDevice { + ext := &models.ExtDevice{} + ext.SetIFAType(strings.ToUpper(models.DeviceIFATypeDPID)) + return ext + }(), }, }, { name: `ifa_type_present_session_id_present`, args: args{ dvc: &models.DeviceCtx{ - Ext: &models.ExtDevice{ - ExtDevice: openrtb_ext.ExtDevice{ - IFAType: models.DeviceIFATypeDPID, - }, - SessionID: `sample_session_id`, - }, + Ext: func() *models.ExtDevice { + ext := &models.ExtDevice{} + ext.SetIFAType(strings.ToUpper(models.DeviceIFATypeDPID)) + ext.SetSessionID(`sample_session_id`) + return ext + }(), }, }, want: &models.DeviceCtx{ DeviceIFA: `sample_session_id`, IFATypeID: ptrutil.ToPtr(models.DeviceIFATypeID[models.DeviceIFATypeSESSIONID]), - Ext: &models.ExtDevice{ - ExtDevice: openrtb_ext.ExtDevice{ - IFAType: models.DeviceIFATypeSESSIONID, - }, - SessionID: `sample_session_id`, - }, + Ext: func() *models.ExtDevice { + ext := &models.ExtDevice{} + ext.SetIFAType(models.DeviceIFATypeSESSIONID) + ext.SetSessionID(`sample_session_id`) + return ext + }(), }, }, { name: `ifa_type_present_session_id_missing`, args: args{ dvc: &models.DeviceCtx{ - Ext: &models.ExtDevice{ - ExtDevice: openrtb_ext.ExtDevice{ - IFAType: models.DeviceIFATypeDPID, - }, - }, + Ext: func() *models.ExtDevice { + deviceExt := &models.ExtDevice{} + deviceExt.SetIFAType(models.DeviceIFATypeDPID) + return deviceExt + }(), }, }, want: &models.DeviceCtx{ - Ext: &models.ExtDevice{ - ExtDevice: openrtb_ext.ExtDevice{}, - }, + Ext: models.NewExtDevice(), }, }, { @@ -378,21 +383,22 @@ func TestUpdateDeviceIFADetails(t *testing.T) { args: args{ dvc: &models.DeviceCtx{ DeviceIFA: `existing_ifa_id`, - Ext: &models.ExtDevice{ - ExtDevice: openrtb_ext.ExtDevice{}, - SessionID: `sample_session_id`, - }, + Ext: func() *models.ExtDevice { + deviceExt := &models.ExtDevice{} + deviceExt.SetSessionID(`sample_session_id`) + return deviceExt + }(), }, }, want: &models.DeviceCtx{ DeviceIFA: `sample_session_id`, IFATypeID: ptrutil.ToPtr(models.DeviceIFATypeID[models.DeviceIFATypeSESSIONID]), - Ext: &models.ExtDevice{ - ExtDevice: openrtb_ext.ExtDevice{ - IFAType: models.DeviceIFATypeSESSIONID, - }, - SessionID: `sample_session_id`, - }, + Ext: func() *models.ExtDevice { + deviceExt := &models.ExtDevice{} + deviceExt.SetSessionID(`sample_session_id`) + deviceExt.SetIFAType(models.DeviceIFATypeSESSIONID) + return deviceExt + }(), }, }, // TODO: Add test cases. @@ -460,9 +466,11 @@ func TestAmendDeviceObject(t *testing.T) { }, dvc: &models.DeviceCtx{ DeviceIFA: `new_ifa`, - Ext: &models.ExtDevice{ - SessionID: `sample_session`, - }, + Ext: func() *models.ExtDevice { + deviceExt := &models.ExtDevice{} + deviceExt.SetSessionID("sample_session") + return deviceExt + }(), }, }, want: &openrtb2.Device{ @@ -481,9 +489,11 @@ func TestAmendDeviceObject(t *testing.T) { }, dvc: &models.DeviceCtx{ DeviceIFA: `new_ifa`, - Ext: &models.ExtDevice{ - SessionID: `sample_session`, - }, + Ext: func() *models.ExtDevice { + deviceExt := &models.ExtDevice{} + deviceExt.SetSessionID("sample_session") + return deviceExt + }(), }, }, want: &openrtb2.Device{ @@ -497,6 +507,7 @@ func TestAmendDeviceObject(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { amendDeviceObject(tt.args.device, tt.args.dvc) + assert.Equal(t, tt.args.device, tt.want, "mismatched device object") }) } } diff --git a/modules/pubmatic/openwrap/endpoints/legacy/openrtb/v25/video.go b/modules/pubmatic/openwrap/endpoints/legacy/openrtb/v25/video.go index 17dd0d81b2f..c0015b20743 100644 --- a/modules/pubmatic/openwrap/endpoints/legacy/openrtb/v25/video.go +++ b/modules/pubmatic/openwrap/endpoints/legacy/openrtb/v25/video.go @@ -217,13 +217,11 @@ func ConvertVideoToAuctionRequest(payload hookstage.EntrypointPayload, result *h var deviceExt models.ExtDevice if session_id := GetValueFromRequest(values, redirectQueryParams, models.DeviceExtSessionID); session_id != nil { - deviceExt.SessionID = GetString(session_id) + deviceExt.SetSessionID(GetString(session_id)) } if ifaType := GetValueFromRequest(values, redirectQueryParams, models.DeviceExtIfaType); ifaType != nil { - deviceExt.ExtDevice = openrtb_ext.ExtDevice{ - IFAType: GetString(ifaType), - } + deviceExt.SetIFAType(GetString(ifaType)) } bidRequest.Device.Ext, _ = json.Marshal(deviceExt) } else { diff --git a/modules/pubmatic/openwrap/entrypointhook.go b/modules/pubmatic/openwrap/entrypointhook.go index 3c8b789c3df..51d493f0b0f 100644 --- a/modules/pubmatic/openwrap/entrypointhook.go +++ b/modules/pubmatic/openwrap/entrypointhook.go @@ -53,51 +53,16 @@ func (m OpenWrap) handleEntrypointHook( if queryParams.Get("sshb") == "1" { return result, nil } - - switch payload.Request.URL.Path { - // Direct call to 8000 port - case hookexecution.EndpointAuction: - switch source { - case "pbjs": - endpoint = models.EndpointWebS2S - requestExtWrapper, err = models.GetRequestExtWrapper(payload.Body) - case "owsdk": - requestExtWrapper, err = models.GetRequestExtWrapper(payload.Body, "ext", "wrapper") - endpoint = models.EndpointV25 - default: - rCtx.Endpoint = models.EndpointHybrid - return result, nil - } - // call to 8001 port and here via reverse proxy - case OpenWrapAuction: // legacy hybrid api should not execute module - // m.metricEngine.RecordPBSAuctionRequestsStats() //TODO: uncomment after hybrid call through module + endpoint = GetEndpoint(payload.Request.URL.Path, source) + if endpoint == models.EndpointHybrid { rCtx.Endpoint = models.EndpointHybrid return result, nil - case OpenWrapV25: - requestExtWrapper, err = models.GetRequestExtWrapper(payload.Body, "ext", "wrapper") - endpoint = models.EndpointV25 - case OpenWrapV25Video: - requestExtWrapper, err = v25.ConvertVideoToAuctionRequest(payload, &result) - endpoint = models.EndpointVideo - case OpenWrapAmp: - requestExtWrapper, pubid, err = models.GetQueryParamRequestExtWrapper(payload.Request) - endpoint = models.EndpointAMP - case OpenWrapOpenRTBVideo: - requestExtWrapper, err = models.GetRequestExtWrapper(payload.Body, "ext", "wrapper") - endpoint = models.EndpointVideo - case OpenWrapVAST: - requestExtWrapper, err = models.GetRequestExtWrapper(payload.Body, "ext", "wrapper") - endpoint = models.EndpointVAST - case OpenWrapJSON: - requestExtWrapper, err = models.GetRequestExtWrapper(payload.Body, "ext", "wrapper") - endpoint = models.EndpointJson - default: - // we should return from here } // init default for all modules result.Reject = true + requestExtWrapper, err = GetRequestWrapper(payload, result, endpoint) if err != nil { result.NbrCode = nbr.InvalidRequestWrapperExtension result.Errors = append(result.Errors, err.Error()) @@ -169,3 +134,53 @@ func (m OpenWrap) handleEntrypointHook( result.Reject = false return result, nil } + +func GetRequestWrapper(payload hookstage.EntrypointPayload, result hookstage.HookResult[hookstage.EntrypointPayload], endpoint string) (models.RequestExtWrapper, error) { + var requestExtWrapper models.RequestExtWrapper + var err error + switch endpoint { + case models.EndpintInappVideo: + requestExtWrapper, err = v25.ConvertVideoToAuctionRequest(payload, &result) + case models.EndpointAMP: + requestExtWrapper, err = models.GetQueryParamRequestExtWrapper(payload.Request) + case models.EndpointV25: + fallthrough + case models.EndpointVideo, models.EndpointVAST, models.EndpointJson: + requestExtWrapper, err = models.GetRequestExtWrapper(payload.Body, "ext", "wrapper") + case models.EndpointWebS2S: + fallthrough + default: + requestExtWrapper, err = models.GetRequestExtWrapper(payload.Body) + } + + return requestExtWrapper, err +} + +func GetEndpoint(path, source string) string { + switch path { + case hookexecution.EndpointAuction: + switch source { + case "pbjs": + return models.EndpointWebS2S + case "owsdk": + return models.EndpointV25 + default: + return models.EndpointHybrid + } + case OpenWrapAuction: + return models.EndpointHybrid + case OpenWrapV25: + return models.EndpointV25 + case OpenWrapV25Video: + return models.EndpintInappVideo + case OpenWrapAmp: + return models.EndpointAMP + case OpenWrapOpenRTBVideo: + return models.EndpointVideo + case OpenWrapVAST: + return models.EndpointVAST + case OpenWrapJSON: + return models.EndpointJson + } + return "" +} diff --git a/modules/pubmatic/openwrap/entrypointhook_test.go b/modules/pubmatic/openwrap/entrypointhook_test.go index bfb48f3826b..aee2e5af9bd 100644 --- a/modules/pubmatic/openwrap/entrypointhook_test.go +++ b/modules/pubmatic/openwrap/entrypointhook_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/golang/mock/gomock" + "github.com/prebid/prebid-server/hooks/hookexecution" "github.com/prebid/prebid-server/hooks/hookstage" "github.com/prebid/prebid-server/modules/pubmatic/openwrap/cache" "github.com/prebid/prebid-server/modules/pubmatic/openwrap/config" @@ -529,3 +530,148 @@ func TestOpenWrap_handleEntrypointHook(t *testing.T) { }) } } + +func TestGetRequestWrapper(t *testing.T) { + type args struct { + payload hookstage.EntrypointPayload + result hookstage.HookResult[hookstage.EntrypointPayload] + endpoint string + } + tests := []struct { + name string + args args + want models.RequestExtWrapper + wantErr bool + }{ + { + name: "EndpointWebS2S", + args: args{ + payload: hookstage.EntrypointPayload{ + Body: []byte(`{"ext":{"wrapper":{"profileid":5890,"versionid":1},"prebid":{"bidderparams":{"pubmatic":{"wrapper":{"profileid":100,"versionid":2}}}}}}`), + }, + result: hookstage.HookResult[hookstage.EntrypointPayload]{}, + endpoint: models.EndpointWebS2S, + }, + want: models.RequestExtWrapper{ + SSAuctionFlag: -1, + ProfileId: 100, + VersionId: 2, + }, + wantErr: false, + }, + { + name: "EndpointV25", + args: args{ + payload: hookstage.EntrypointPayload{ + Body: []byte(`{"ext":{"wrapper":{"profileid":5890,"versionid":1},"prebid":{"bidderparams":{"pubmatic":{"wrapper":{"profileid":100,"versionid":2}}}}}}`), + }, + result: hookstage.HookResult[hookstage.EntrypointPayload]{}, + endpoint: models.EndpointV25, + }, + want: models.RequestExtWrapper{ + SSAuctionFlag: -1, + ProfileId: 5890, + VersionId: 1, + }, + }, + //TODO: Add test cases for other endpoints after migration(AMP, Video, VAST, JSON, InappVideo) + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GetRequestWrapper(tt.args.payload, tt.args.result, tt.args.endpoint) + assert.Equal(t, err != nil, tt.wantErr) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestGetEndpoint(t *testing.T) { + type args struct { + path string + source string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "EndpointAuction Activation", + args: args{ + path: hookexecution.EndpointAuction, + source: "pbjs", + }, + want: models.EndpointWebS2S, + }, + { + name: "EndpointAuction inapp", + args: args{ + path: hookexecution.EndpointAuction, + source: "owsdk", + }, + want: models.EndpointV25, + }, + { + name: "EndpointAuction default", + args: args{ + path: hookexecution.EndpointAuction, + }, + want: models.EndpointHybrid, + }, + { + name: "OpenWrapAuction", + args: args{ + path: OpenWrapAuction, + }, + want: models.EndpointHybrid, + }, + { + name: "OpenWrapV25", + args: args{ + path: OpenWrapV25, + }, + want: models.EndpointV25, + }, + { + name: "OpenWrapV25Video", + args: args{ + path: OpenWrapV25Video, + }, + want: models.EndpintInappVideo, + }, + { + name: "OpenWrapAmp", + args: args{ + path: OpenWrapAmp, + }, + want: models.EndpointAMP, + }, + { + name: "OpenWrapOpenRTBVideo", + args: args{ + path: OpenWrapOpenRTBVideo, + }, + want: models.EndpointVideo, + }, + { + name: "OpenWrapVAST", + args: args{ + path: OpenWrapVAST, + }, + want: models.EndpointVAST, + }, + { + name: "OpenWrapJSON", + args: args{ + path: OpenWrapJSON, + }, + want: models.EndpointJson, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := GetEndpoint(tt.args.path, tt.args.source) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/modules/pubmatic/openwrap/logger_test.go b/modules/pubmatic/openwrap/logger_test.go new file mode 100644 index 00000000000..cad1a487354 --- /dev/null +++ b/modules/pubmatic/openwrap/logger_test.go @@ -0,0 +1,145 @@ +package openwrap + +import ( + "testing" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/util/ptrutil" + "github.com/stretchr/testify/assert" +) + +func Test_getIncomingSlots(t *testing.T) { + type args struct { + imp openrtb2.Imp + } + tests := []struct { + name string + args args + want []string + }{ + { + name: "only_native_slot", + args: args{ + imp: openrtb2.Imp{ + ID: "1", + Native: &openrtb2.Native{ + Request: `{"ver":"1.2"}`, + }, + }, + }, + want: []string{"1x1"}, + }, + { + name: "native_with_other_slots_then_do_not_consider_native", + args: args{ + imp: openrtb2.Imp{ + ID: "1", + Native: &openrtb2.Native{ + Request: `{"ver":"1.2"}`, + }, + Banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](300), + H: ptrutil.ToPtr[int64](250), + }, + }, + }, + want: []string{"300x250"}, + }, + { + name: "only_banner_slot", + args: args{ + imp: openrtb2.Imp{ + ID: "1", + Banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](300), + H: ptrutil.ToPtr[int64](250), + }, + }, + }, + want: []string{"300x250"}, + }, + { + name: "banner_with_format", + args: args{ + imp: openrtb2.Imp{ + ID: "1", + Banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](300), + H: ptrutil.ToPtr[int64](250), + Format: []openrtb2.Format{ + { + W: 400, + H: 300, + }, + }, + }, + }, + }, + want: []string{"300x250", "400x300"}, + }, + { + name: "only_video_slot", + args: args{ + imp: openrtb2.Imp{ + ID: "1", + Video: &openrtb2.Video{ + W: 300, + H: 250, + }, + }, + }, + want: []string{"300x250v"}, + }, + { + name: "all_slots", + args: args{ + imp: openrtb2.Imp{ + ID: "1", + Native: &openrtb2.Native{ + Request: `{"ver":"1.2"}`, + }, + Banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](300), + H: ptrutil.ToPtr[int64](250), + Format: []openrtb2.Format{ + { + W: 400, + H: 300, + }, + }, + }, + Video: &openrtb2.Video{ + W: 300, + H: 250, + }, + }, + }, + want: []string{"300x250", "400x300", "300x250v"}, + }, + { + name: "duplicate_slot", + args: args{ + imp: openrtb2.Imp{ + ID: "1", + Banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](300), + H: ptrutil.ToPtr[int64](250), + Format: []openrtb2.Format{ + { + W: 300, + H: 250, + }, + }, + }, + }, + }, + want: []string{"300x250"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + slots := getIncomingSlots(tt.args.imp) + assert.ElementsMatch(t, tt.want, slots, "mismatched slots") + }) + } +} diff --git a/modules/pubmatic/openwrap/models/adunitconfig/adunitconfig.go b/modules/pubmatic/openwrap/models/adunitconfig/adunitconfig.go index 7e8ccc78917..bbe3bdb4807 100644 --- a/modules/pubmatic/openwrap/models/adunitconfig/adunitconfig.go +++ b/modules/pubmatic/openwrap/models/adunitconfig/adunitconfig.go @@ -72,13 +72,14 @@ type AdConfig struct { BidFloorCur *string `json:"bidfloorcur,omitempty"` Floors *openrtb_ext.PriceFloorRules `json:"floors,omitempty"` - Exp *int `json:"exp,omitempty"` - Banner *Banner `json:"banner,omitempty"` - Native *Native `json:"native,omitempty"` - Video *Video `json:"video,omitempty"` - App *openrtb2.App `json:"app,omitempty"` - Device *openrtb2.Device `json:"device,omitempty"` - Transparency *Transparency `json:"transparency,omitempty"` - Regex *bool `json:"regex,omitempty"` - UniversalPixel []UniversalPixel `json:"universalpixel,omitempty"` + Exp *int `json:"exp,omitempty"` + Banner *Banner `json:"banner,omitempty"` + Native *Native `json:"native,omitempty"` + Video *Video `json:"video,omitempty"` + App *openrtb2.App `json:"app,omitempty"` + Device *openrtb2.Device `json:"device,omitempty"` + Transparency *Transparency `json:"transparency,omitempty"` + Regex *bool `json:"regex,omitempty"` + UniversalPixel []UniversalPixel `json:"universalpixel,omitempty"` + EnableGAMUrlLookup bool `json:"enablegamurllookup,omitempty"` } diff --git a/modules/pubmatic/openwrap/models/amp.go b/modules/pubmatic/openwrap/models/amp.go index 1cd1a513b93..38e886ea19d 100644 --- a/modules/pubmatic/openwrap/models/amp.go +++ b/modules/pubmatic/openwrap/models/amp.go @@ -5,15 +5,15 @@ import ( "strconv" ) -func GetQueryParamRequestExtWrapper(request *http.Request) (RequestExtWrapper, int, error) { +func GetQueryParamRequestExtWrapper(request *http.Request) (RequestExtWrapper, error) { extWrapper := RequestExtWrapper{ SSAuctionFlag: -1, } values := request.URL.Query() - pubid, _ := strconv.Atoi(values.Get(PUBID_KEY)) + extWrapper.PubId, _ = strconv.Atoi(values.Get(PUBID_KEY)) extWrapper.ProfileId, _ = strconv.Atoi(values.Get(PROFILEID_KEY)) extWrapper.VersionId, _ = strconv.Atoi(values.Get(VERSION_KEY)) - return extWrapper, pubid, nil + return extWrapper, nil } diff --git a/modules/pubmatic/openwrap/models/constants.go b/modules/pubmatic/openwrap/models/constants.go index 935bf4f92d1..78be2effa15 100755 --- a/modules/pubmatic/openwrap/models/constants.go +++ b/modules/pubmatic/openwrap/models/constants.go @@ -31,6 +31,7 @@ const ( GDPR_ENABLED = "gdpr" PLATFORM_KEY = "platform" SendAllBidsKey = "sendAllBids" + VastUnwrapperEnableKey = "enableVastUnwrapper" SSTimeoutKey = "ssTimeout" PWC = "awc" MAX_SLOT_COUNT = 5000 @@ -422,20 +423,21 @@ const ( ) const ( - EndpointV25 = "v25" - EndpointAMP = "amp" - EndpointVideo = "video" - EndpointJson = "json" - EndpointORTB = "ortb" - EndpointVAST = "vast" - EndpointWebS2S = "webs2s" - EndPointCTV = "ctv" - EndpointHybrid = "hybrid" - Openwrap = "openwrap" - ImpTypeBanner = "banner" - ImpTypeVideo = "video" - ContentTypeSite = "site" - ContentTypeApp = "app" + EndpointV25 = "v25" + EndpointAMP = "amp" + EndpintInappVideo = "inappvideo" + EndpointVideo = "video" + EndpointJson = "json" + EndpointORTB = "ortb" + EndpointVAST = "vast" + EndpointWebS2S = "webs2s" + EndPointCTV = "ctv" + EndpointHybrid = "hybrid" + Openwrap = "openwrap" + ImpTypeBanner = "banner" + ImpTypeVideo = "video" + ContentTypeSite = "site" + ContentTypeApp = "app" ) const ( diff --git a/modules/pubmatic/openwrap/models/device.go b/modules/pubmatic/openwrap/models/device.go index 109c1a467f7..a6955241e5d 100644 --- a/modules/pubmatic/openwrap/models/device.go +++ b/modules/pubmatic/openwrap/models/device.go @@ -1,6 +1,9 @@ package models -import "github.com/prebid/prebid-server/openrtb_ext" +import ( + "encoding/json" + "strings" +) const ( //Device.DeviceType values as per OpenRTB-API-Specification-Version-2-5 @@ -56,8 +59,96 @@ const ( DeviceIFATypeSESSIONID = "sessionid" ) +// device.ext related keys +const ( + ExtDeviceIFAType = "ifa_type" + ExtDeviceSessionID = "session_id" + ExtDeviceAtts = "atts" +) + +// ExtDevice will store device.ext parameters type ExtDevice struct { - openrtb_ext.ExtDevice - SessionID string `json:"session_id,omitempty"` - IDFV string `json:"idfv,omitempty"` + data map[string]any +} + +func NewExtDevice() *ExtDevice { + return &ExtDevice{ + data: make(map[string]any), + } +} + +func (e *ExtDevice) UnmarshalJSON(data []byte) error { + var intermediate map[string]interface{} + if err := json.Unmarshal(data, &intermediate); err != nil { + return err + } + e.data = intermediate + return nil +} + +func (e *ExtDevice) MarshalJSON() ([]byte, error) { + return json.Marshal(e.data) +} + +func (e *ExtDevice) getStringValue(key string) (value string, found bool) { + if e.data == nil { + return value, found + } + val, found := e.data[key] + if !found { + return "", found + } + value, found = val.(string) + return strings.TrimSpace(value), found +} + +func (e *ExtDevice) GetIFAType() (value string, found bool) { + return e.getStringValue(ExtDeviceIFAType) +} + +func (e *ExtDevice) GetSessionID() (value string, found bool) { + return e.getStringValue(ExtDeviceSessionID) +} + +func (e *ExtDevice) getFloatValue(key string) (value float64, found bool) { + if e.data == nil { + return value, found + } + val, found := e.data[key] + if !found { + return 0, found + } + value, found = val.(float64) + return value, found +} + +func (e *ExtDevice) GetAtts() (value *float64, found bool) { + val, ok := e.getFloatValue(ExtDeviceAtts) + if !ok { + return nil, ok + } + return &val, ok +} + +func (e *ExtDevice) setStringValue(key, value string) { + if e.data == nil { + e.data = make(map[string]any) + } + e.data[key] = value +} + +func (e *ExtDevice) SetIFAType(ifaType string) { + e.setStringValue(ExtDeviceIFAType, ifaType) +} + +func (e *ExtDevice) SetSessionID(sessionID string) { + e.setStringValue(ExtDeviceSessionID, sessionID) +} + +func (e *ExtDevice) DeleteIFAType() { + delete(e.data, ExtDeviceIFAType) +} + +func (e *ExtDevice) DeleteSessionID() { + delete(e.data, ExtDeviceSessionID) } diff --git a/modules/pubmatic/openwrap/models/request.go b/modules/pubmatic/openwrap/models/request.go index c2ed52b042a..ef2c4059c2c 100644 --- a/modules/pubmatic/openwrap/models/request.go +++ b/modules/pubmatic/openwrap/models/request.go @@ -106,6 +106,7 @@ type RequestExtWrapper struct { ABTestConfig int `json:"abtest,omitempty"` LoggerImpressionID string `json:"wiid,omitempty"` SSAI string `json:"ssai,omitempty"` + PubId int `json:"-"` } type BidderWrapper struct { diff --git a/modules/pubmatic/openwrap/models/tracker.go b/modules/pubmatic/openwrap/models/tracker.go index cbf73f63120..0b007471663 100644 --- a/modules/pubmatic/openwrap/models/tracker.go +++ b/modules/pubmatic/openwrap/models/tracker.go @@ -1,7 +1,5 @@ package models -import "github.com/prebid/prebid-server/openrtb_ext" - // OWTracker vast video parameters to be injected type OWTracker struct { Tracker Tracker @@ -39,7 +37,7 @@ type Tracker struct { FloorSource *int FloorType int CustomDimensions string - ATTS *openrtb_ext.IOSAppTrackingStatus + ATTS *float64 LoggerData LoggerData // need this in logger to avoid duplicate computation ImpID string `json:"-"` diff --git a/modules/pubmatic/openwrap/models/utils.go b/modules/pubmatic/openwrap/models/utils.go index 5e1864eb98a..8281827b8f6 100644 --- a/modules/pubmatic/openwrap/models/utils.go +++ b/modules/pubmatic/openwrap/models/utils.go @@ -139,19 +139,14 @@ func GetRevenueShare(partnerConfig map[string]string) float64 { func GetNetEcpm(price float64, revShare float64) float64 { if revShare == 0 { - return toFixed(price, BID_PRECISION) + return ToFixed(price, BID_PRECISION) } price = price * (1 - revShare/100) - return toFixed(price, BID_PRECISION) + return ToFixed(price, BID_PRECISION) } func GetGrossEcpm(price float64) float64 { - return toFixed(price, BID_PRECISION) -} - -func toFixed(num float64, precision int) float64 { - output := math.Pow(10, float64(precision)) - return float64(round(num*output)) / output + return ToFixed(price, BID_PRECISION) } func round(num float64) int { @@ -413,3 +408,21 @@ func GetFloorsDetails(responseExt openrtb_ext.ExtBidResponse) (floorDetails Floo } return floorDetails } + +func GetGrossEcpmFromNetEcpm(netEcpm float64, revShare float64) float64 { + + if revShare == 100 { + return 0 + } + originalBidPrice := netEcpm / (1 - revShare/100) + return ToFixed(originalBidPrice, BID_PRECISION) +} + +func GetBidAdjustmentValue(revShare float64) float64 { + return (1 - revShare/100) +} + +func ToFixed(num float64, precision int) float64 { + output := math.Pow(10, float64(precision)) + return float64(round(num*output)) / output +} diff --git a/modules/pubmatic/openwrap/models/utils_test.go b/modules/pubmatic/openwrap/models/utils_test.go index fe3870580f4..77ba2bb2f3b 100644 --- a/modules/pubmatic/openwrap/models/utils_test.go +++ b/modules/pubmatic/openwrap/models/utils_test.go @@ -1331,3 +1331,107 @@ func TestGetKGPSV(t *testing.T) { }) } } + +func TestGetGrossEcpmFromNetEcpm(t *testing.T) { + type args struct { + netEcpm float64 + revShare float64 + } + tests := []struct { + name string + args args + want float64 + }{ + { + name: "When netcpm is 100 and revShare is 0", + args: args{ + netEcpm: 100, + revShare: 0, + }, + want: 100, + }, + { + name: "When netcpm is 0 and revShare is 100", + args: args{ + netEcpm: 0, + revShare: 100, + }, + want: 0, + }, + { + name: "When netcpm is 100 and revShare is 50", + args: args{ + netEcpm: 100, + revShare: 50, + }, + want: 200, + }, + { + name: "When netcpm is 80 and revShare is 20", + args: args{ + netEcpm: 80, + revShare: 20, + }, + want: 100, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetGrossEcpmFromNetEcpm(tt.args.netEcpm, tt.args.revShare); got != tt.want { + t.Errorf("GetGrossEcpmFromNetEcpm() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestToFixed(t *testing.T) { + type args struct { + num float64 + precision int + } + tests := []struct { + name string + args args + want float64 + }{ + { + name: "Rounding of 0.1", + args: args{ + num: 0.1, + precision: 2, + }, + want: 0.10, + }, + { + name: "Rounding of 0.1101", + args: args{ + num: 0.1101, + precision: 2, + }, + want: 0.11, + }, + { + name: "Rounding of 0.10000000149011612", + args: args{ + num: 0.10000000149011612, + precision: 2, + }, + want: 0.10, + }, + { + name: "Rounding of 0.10000000149011612", + args: args{ + num: 0.10000000149011612, + precision: 3, + }, + want: 0.100, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ToFixed(tt.args.num, tt.args.precision); got != tt.want { + t.Errorf("toFixed() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/modules/pubmatic/openwrap/openwrap.go b/modules/pubmatic/openwrap/openwrap.go index e29ea26f007..0193bb75ff0 100644 --- a/modules/pubmatic/openwrap/openwrap.go +++ b/modules/pubmatic/openwrap/openwrap.go @@ -8,6 +8,8 @@ import ( "strings" "time" + "sync" + "github.com/golang/glog" gocache "github.com/patrickmn/go-cache" "github.com/prebid/prebid-server/currency" @@ -35,7 +37,10 @@ type OpenWrap struct { currencyConversion currency.Conversions } +var ow *OpenWrap + func initOpenWrap(rawCfg json.RawMessage, moduleDeps moduledeps.ModuleDeps) (OpenWrap, error) { + var once sync.Once cfg := config.Config{} err := json.Unmarshal(rawCfg, &cfg) @@ -75,12 +80,15 @@ func initOpenWrap(rawCfg json.RawMessage, moduleDeps moduledeps.ModuleDeps) (Ope // Init TBF (tracking-beacon-first) feature related services tbf.Init(cfg.Cache.CacheDefaultExpiry, owCache) - return OpenWrap{ - cfg: cfg, - cache: owCache, - metricEngine: &metricEngine, - currencyConversion: moduleDeps.CurrencyConversion, - }, nil + once.Do(func() { + ow = &OpenWrap{ + cfg: cfg, + cache: owCache, + metricEngine: &metricEngine, + currencyConversion: moduleDeps.CurrencyConversion, + } + }) + return *ow, nil } func open(driverName string, cfg config.Database) (*sql.DB, error) { diff --git a/modules/pubmatic/openwrap/openwrap_sshb.go b/modules/pubmatic/openwrap/openwrap_sshb.go index 057cc9d2bbb..6fbe8482c1c 100644 --- a/modules/pubmatic/openwrap/openwrap_sshb.go +++ b/modules/pubmatic/openwrap/openwrap_sshb.go @@ -1,9 +1,16 @@ package openwrap import ( + "github.com/prebid/openrtb/v19/openrtb2" cache "github.com/prebid/prebid-server/modules/pubmatic/openwrap/cache" "github.com/prebid/prebid-server/modules/pubmatic/openwrap/config" metrics "github.com/prebid/prebid-server/modules/pubmatic/openwrap/metrics" + "github.com/prebid/prebid-server/modules/pubmatic/openwrap/models" + vastmodels "github.com/prebid/prebid-server/modules/pubmatic/vastunwrap/models" +) + +const ( + VastUnwrapperEnableValue = "1" ) // GetConfig Temporary function to expose config to SSHB @@ -36,3 +43,19 @@ func (ow *OpenWrap) SetCache(c cache.Cache) { func (ow *OpenWrap) SetMetricEngine(m metrics.MetricsEngine) { ow.metricEngine = m } + +// GetVastUnwrapEnabled function return vastunwrap flag from the database +func GetVastUnwrapEnabled(rctx vastmodels.RequestCtx) bool { + rCtx := models.RequestCtx{ + Endpoint: rctx.Endpoint, + PubID: rctx.PubID, + ProfileID: rctx.ProfileID, + DisplayID: rctx.DisplayID, + } + partnerConfigMap, err := ow.getProfileData(rCtx, openrtb2.BidRequest{}) + if err != nil || len(partnerConfigMap) == 0 { + return false + } + rCtx.PartnerConfigMap = partnerConfigMap + return models.GetVersionLevelPropertyFromPartnerConfig(rCtx.PartnerConfigMap, models.VastUnwrapperEnableKey) == VastUnwrapperEnableValue +} diff --git a/modules/pubmatic/openwrap/tracker/create.go b/modules/pubmatic/openwrap/tracker/create.go index a2a84f4aaaf..7d79ecb52f9 100644 --- a/modules/pubmatic/openwrap/tracker/create.go +++ b/modules/pubmatic/openwrap/tracker/create.go @@ -87,7 +87,7 @@ func createTrackers(rctx models.RequestCtx, trackers map[string]models.OWTracker ) if rctx.DeviceCtx.Ext != nil { - tracker.ATTS = rctx.DeviceCtx.Ext.ATTS + tracker.ATTS, _ = rctx.DeviceCtx.Ext.GetAtts() } if impCtx, ok := rctx.ImpBidCtx[bid.ImpID]; ok { diff --git a/modules/pubmatic/openwrap/tracker/create_test.go b/modules/pubmatic/openwrap/tracker/create_test.go index f5f38ec92d6..d47edc3f349 100644 --- a/modules/pubmatic/openwrap/tracker/create_test.go +++ b/modules/pubmatic/openwrap/tracker/create_test.go @@ -110,11 +110,11 @@ func Test_createTrackers(t *testing.T) { rctx: func() models.RequestCtx { testRctx := rctx testRctx.StartTime = startTime - testRctx.DeviceCtx.Ext = &models.ExtDevice{ - ExtDevice: openrtb_ext.ExtDevice{ - ATTS: ptrutil.ToPtr(openrtb_ext.IOSAppTrackingStatusRestricted), - }, - } + testRctx.DeviceCtx.Ext = func() *models.ExtDevice { + extDevice := models.ExtDevice{} + extDevice.UnmarshalJSON([]byte(`{"atts":1}`)) + return &extDevice + }() return testRctx }(), bidResponse: &openrtb2.BidResponse{ @@ -176,7 +176,7 @@ func Test_createTrackers(t *testing.T) { KGPSV: "adunit-1@250x300", }, CustomDimensions: "author=henry", - ATTS: ptrutil.ToPtr(openrtb_ext.IOSAppTrackingStatusRestricted), + ATTS: ptrutil.ToPtr(float64(openrtb_ext.IOSAppTrackingStatusRestricted)), }, TrackerURL: "https:?adv=domain.com&af=banner&aps=0&atts=1&au=adunit-1&bc=pubmatic&bidid=bidID-1&cds=author%3Dhenry&di=deal-id-1&dur=20&eg=8.7&en=8.7&frv=4.4&ft=0&fv=6.4&iid=loggerIID&kgpv=adunit-1%40250x300&orig=publisher.com&origbidid=bidID-1&pdvid=1&pid=1234&plt=5&pn=prebidBidderCode&psz=250x300&pubid=5890&purl=abc.com&sl=1&slot=impID-1_adunit-1&ss=1&ssai=mediatailor&tgid=1&tst=" + strconv.FormatInt(startTime, 10), Price: 8.7, diff --git a/modules/pubmatic/openwrap/tracker/tracker.go b/modules/pubmatic/openwrap/tracker/tracker.go index 19ae629a190..1ea29ffe0c1 100644 --- a/modules/pubmatic/openwrap/tracker/tracker.go +++ b/modules/pubmatic/openwrap/tracker/tracker.go @@ -13,7 +13,7 @@ func GetTrackerInfo(rCtx models.RequestCtx, responseExt openrtb_ext.ExtBidRespon tracker := models.Tracker{ PubID: rCtx.PubID, ProfileID: fmt.Sprintf("%d", rCtx.ProfileID), - VersionID: fmt.Sprintf("%d", rCtx.DisplayID), + VersionID: fmt.Sprintf("%d", rCtx.DisplayVersionID), PageURL: rCtx.PageURL, Timestamp: rCtx.StartTime, IID: rCtx.LoggerImpressionID, @@ -27,7 +27,7 @@ func GetTrackerInfo(rCtx models.RequestCtx, responseExt openrtb_ext.ExtBidRespon } if rCtx.DeviceCtx.Ext != nil { - tracker.ATTS = rCtx.DeviceCtx.Ext.ATTS + tracker.ATTS, _ = rCtx.DeviceCtx.Ext.GetAtts() } constructedURLString := constructTrackerURL(rCtx, tracker) diff --git a/modules/pubmatic/openwrap/tracker/tracker_test.go b/modules/pubmatic/openwrap/tracker/tracker_test.go index e6289411af1..e6e78fe0a32 100644 --- a/modules/pubmatic/openwrap/tracker/tracker_test.go +++ b/modules/pubmatic/openwrap/tracker/tracker_test.go @@ -1,6 +1,7 @@ package tracker import ( + "encoding/json" "strconv" "testing" "time" @@ -8,6 +9,7 @@ import ( "github.com/prebid/prebid-server/modules/pubmatic/openwrap/models" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/util/ptrutil" + "github.com/stretchr/testify/assert" ) func TestGetTrackerInfo(t *testing.T) { @@ -28,6 +30,7 @@ func TestGetTrackerInfo(t *testing.T) { TrackerEndpoint: "localhost:8080/wt", PubID: 123, ProfileID: 1, + DisplayVersionID: 2, VersionID: 1, PageURL: "www.test.com", LoggerImpressionID: "iid123", @@ -36,16 +39,16 @@ func TestGetTrackerInfo(t *testing.T) { ABTestConfigApplied: 1, DeviceCtx: models.DeviceCtx{ Platform: models.DevicePlatformMobileAppAndroid, - Ext: &models.ExtDevice{ - ExtDevice: openrtb_ext.ExtDevice{ - ATTS: ptrutil.ToPtr(openrtb_ext.IOSAppTrackingStatusRestricted), - }, - }, + Ext: func() *models.ExtDevice { + extDevice := models.ExtDevice{} + json.Unmarshal([]byte(`{"atts":1}`), &extDevice) + return &extDevice + }(), }, }, responseExt: openrtb_ext.ExtBidResponse{}, }, - want: "localhost:8080/wt?adv=&af=&aps=0&atts=1&au=%24%7BADUNIT%7D&bc=%24%7BBIDDER_CODE%7D&bidid=%24%7BBID_ID%7D&di=&eg=%24%7BG_ECPM%7D&en=%24%7BN_ECPM%7D&ft=0&iid=iid123&kgpv=%24%7BKGPV%7D&orig=www.publisher.com&origbidid=%24%7BORIGBID_ID%7D&pdvid=0&pid=1&plt=5&pn=%24%7BPARTNER_NAME%7D&psz=&pubid=123&purl=www.test.com&rwrd=%24%7BREWARDED%7D&sl=1&slot=%24%7BSLOT_ID%7D&ss=0&tgid=1&tst=" + strconv.FormatInt(startTime, 10), + want: "localhost:8080/wt?adv=&af=&aps=0&atts=1&au=%24%7BADUNIT%7D&bc=%24%7BBIDDER_CODE%7D&bidid=%24%7BBID_ID%7D&di=&eg=%24%7BG_ECPM%7D&en=%24%7BN_ECPM%7D&ft=0&iid=iid123&kgpv=%24%7BKGPV%7D&orig=www.publisher.com&origbidid=%24%7BORIGBID_ID%7D&pdvid=2&pid=1&plt=5&pn=%24%7BPARTNER_NAME%7D&psz=&pubid=123&purl=www.test.com&rwrd=%24%7BREWARDED%7D&sl=1&slot=%24%7BSLOT_ID%7D&ss=0&tgid=1&tst=" + strconv.FormatInt(startTime, 10), }, { name: "all_tracker_info_with_floors", @@ -55,6 +58,7 @@ func TestGetTrackerInfo(t *testing.T) { PubID: 123, ProfileID: 1, VersionID: 1, + DisplayVersionID: 2, PageURL: "www.test.com", LoggerImpressionID: "iid123", StartTime: startTime, @@ -83,14 +87,13 @@ func TestGetTrackerInfo(t *testing.T) { }, }, }, - want: "localhost:8080/wt?adv=&af=&aps=0&au=%24%7BADUNIT%7D&bc=%24%7BBIDDER_CODE%7D&bidid=%24%7BBID_ID%7D&di=&eg=%24%7BG_ECPM%7D&en=%24%7BN_ECPM%7D&fmv=version+1&fskp=1&fsrc=2&ft=1&iid=iid123&kgpv=%24%7BKGPV%7D&orig=www.publisher.com&origbidid=%24%7BORIGBID_ID%7D&pdvid=0&pid=1&plt=5&pn=%24%7BPARTNER_NAME%7D&psz=&pubid=123&purl=www.test.com&rwrd=%24%7BREWARDED%7D&sl=1&slot=%24%7BSLOT_ID%7D&ss=0&tgid=1&tst=" + strconv.FormatInt(startTime, 10), + want: "localhost:8080/wt?adv=&af=&aps=0&au=%24%7BADUNIT%7D&bc=%24%7BBIDDER_CODE%7D&bidid=%24%7BBID_ID%7D&di=&eg=%24%7BG_ECPM%7D&en=%24%7BN_ECPM%7D&fmv=version+1&fskp=1&fsrc=2&ft=1&iid=iid123&kgpv=%24%7BKGPV%7D&orig=www.publisher.com&origbidid=%24%7BORIGBID_ID%7D&pdvid=2&pid=1&plt=5&pn=%24%7BPARTNER_NAME%7D&psz=&pubid=123&purl=www.test.com&rwrd=%24%7BREWARDED%7D&sl=1&slot=%24%7BSLOT_ID%7D&ss=0&tgid=1&tst=" + strconv.FormatInt(startTime, 10), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := GetTrackerInfo(tt.args.rCtx, tt.args.responseExt); got != tt.want { - t.Errorf("GetTrackerInfo() = %v, want %v", got, tt.want) - } + got := GetTrackerInfo(tt.args.rCtx, tt.args.responseExt) + assert.Equal(t, tt.want, got) }) } } diff --git a/modules/pubmatic/openwrap/util.go b/modules/pubmatic/openwrap/util.go index 7c992ec89d5..26900e92ca8 100644 --- a/modules/pubmatic/openwrap/util.go +++ b/modules/pubmatic/openwrap/util.go @@ -27,7 +27,9 @@ var ( ctvRegex *regexp.Regexp ) -const test = "_test" +const ( + test = "_test" +) func init() { widthRegEx = regexp.MustCompile(models.MACRO_WIDTH) diff --git a/modules/pubmatic/vastunwrap/constant.go b/modules/pubmatic/vastunwrap/constant.go index d850198dc72..23bd1b38391 100644 --- a/modules/pubmatic/vastunwrap/constant.go +++ b/modules/pubmatic/vastunwrap/constant.go @@ -14,4 +14,8 @@ const ( ContentType = "Content-Type" UnwrapTimeout = "unwrap-timeout" MediaTypeVideo = "video" + ProfileId = "profileID" + VersionId = "versionID" + DisplayId = "DisplayID" + Endpoint = "Endpoint" ) diff --git a/modules/pubmatic/vastunwrap/entryhook.go b/modules/pubmatic/vastunwrap/entryhook.go index 6b9bcbaf462..8cb30effeab 100644 --- a/modules/pubmatic/vastunwrap/entryhook.go +++ b/modules/pubmatic/vastunwrap/entryhook.go @@ -2,11 +2,14 @@ package vastunwrap import ( "context" + "fmt" "math/rand" "runtime/debug" "github.com/golang/glog" "github.com/prebid/prebid-server/hooks/hookstage" + "github.com/prebid/prebid-server/modules/pubmatic/openwrap" + ow_models "github.com/prebid/prebid-server/modules/pubmatic/openwrap/models" "github.com/prebid/prebid-server/modules/pubmatic/vastunwrap/models" ) @@ -14,30 +17,52 @@ var getRandomNumber = func() int { return rand.Intn(100) } +// supportedEndpoints holds the list of endpoints which supports VAST-unwrap feature +var supportedEndpoints = map[string]struct{}{ + ow_models.EndpointVAST: {}, + ow_models.EndpointVideo: {}, + ow_models.EndpointJson: {}, + ow_models.EndpointV25: {}, +} + func getVastUnwrapperEnable(ctx context.Context, field string) bool { vastEnableUnwrapper, _ := ctx.Value(field).(string) return vastEnableUnwrapper == "1" } - func handleEntrypointHook( _ context.Context, _ hookstage.ModuleInvocationContext, - payload hookstage.EntrypointPayload, config VastUnwrapModule, + payload hookstage.EntrypointPayload, ) (hookstage.HookResult[hookstage.EntrypointPayload], error) { defer func() { if r := recover(); r != nil { glog.Errorf("body:[%s] Error:[%v] stacktrace:[%s]", string(payload.Body), r, string(debug.Stack())) } }() - result := hookstage.HookResult[hookstage.EntrypointPayload]{} - vastRequestContext := models.RequestCtx{ - VastUnwrapEnabled: getVastUnwrapperEnable(payload.Request.Context(), VastUnwrapEnabled), + result := hookstage.HookResult[hookstage.EntrypointPayload]{ + ModuleContext: make(hookstage.ModuleContext), } - - if !vastRequestContext.VastUnwrapEnabled { - vastRequestContext.VastUnwrapStatsEnabled = getRandomNumber() < config.StatTrafficPercentage + vastRequestContext := models.RequestCtx{} + queryParams := payload.Request.URL.Query() + source := queryParams.Get("source") + if queryParams.Get("sshb") == "1" { + vastRequestContext = models.RequestCtx{ + VastUnwrapEnabled: getVastUnwrapperEnable(payload.Request.Context(), VastUnwrapEnabled), + Redirect: true, + } + } else { + endpoint := openwrap.GetEndpoint(payload.Request.URL.Path, source) + if _, ok := supportedEndpoints[endpoint]; !ok { + result.DebugMessages = append(result.DebugMessages, fmt.Sprintf("%s endpoint does not support vast-unwrap feature", endpoint)) + return result, nil + } + requestExtWrapper, _ := openwrap.GetRequestWrapper(payload, result, endpoint) + vastRequestContext = models.RequestCtx{ + ProfileID: requestExtWrapper.ProfileId, + DisplayID: requestExtWrapper.VersionId, + Endpoint: endpoint, + } } - result.ModuleContext = make(hookstage.ModuleContext) result.ModuleContext[RequestContext] = vastRequestContext return result, nil } diff --git a/modules/pubmatic/vastunwrap/entryhook_test.go b/modules/pubmatic/vastunwrap/entryhook_test.go index 0ef9d120397..f3e6a71b359 100644 --- a/modules/pubmatic/vastunwrap/entryhook_test.go +++ b/modules/pubmatic/vastunwrap/entryhook_test.go @@ -3,65 +3,161 @@ package vastunwrap import ( "context" "net/http" - "reflect" "testing" "github.com/prebid/prebid-server/hooks/hookstage" + ow_models "github.com/prebid/prebid-server/modules/pubmatic/openwrap/models" "github.com/prebid/prebid-server/modules/pubmatic/vastunwrap/models" + "github.com/stretchr/testify/assert" ) func TestHandleEntrypointHook(t *testing.T) { type args struct { payload hookstage.EntrypointPayload - config VastUnwrapModule } tests := []struct { - name string - args args - randomNum int - want hookstage.HookResult[hookstage.EntrypointPayload] + name string + args args + want hookstage.HookResult[hookstage.EntrypointPayload] }{ { - name: "Disable Vast Unwrapper", + name: "Disable Vast Unwrapper for CTV video/openrtb request", args: args{ payload: hookstage.EntrypointPayload{ Request: func() *http.Request { ctx := context.WithValue(context.Background(), VastUnwrapEnabled, "0") - r, _ := http.NewRequestWithContext(ctx, "", "", nil) + ctx = context.WithValue(ctx, ProfileId, 0) + ctx = context.WithValue(ctx, VersionId, 0) + ctx = context.WithValue(ctx, DisplayId, 0) + ctx = context.WithValue(ctx, Endpoint, "") + r, _ := http.NewRequestWithContext(ctx, "POST", "http://localhost/video/openrtb?sshb=1", nil) return r }(), }, - config: VastUnwrapModule{ - TrafficPercentage: 2, - Enabled: false, + }, + want: hookstage.HookResult[hookstage.EntrypointPayload]{ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: false, Redirect: true}}}, + }, + { + name: "Enable Vast Unwrapper for CTV video/openrtb request", + args: args{ + payload: hookstage.EntrypointPayload{ + Request: func() *http.Request { + ctx := context.WithValue(context.Background(), VastUnwrapEnabled, "1") + ctx = context.WithValue(ctx, ProfileId, 0) + ctx = context.WithValue(ctx, VersionId, 0) + ctx = context.WithValue(ctx, DisplayId, 0) + ctx = context.WithValue(ctx, Endpoint, "") + r, _ := http.NewRequestWithContext(ctx, "POST", "http://localhost/video/openrtb?sshb=1", nil) + return r + }(), + }, + }, + want: hookstage.HookResult[hookstage.EntrypointPayload]{ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: true, Redirect: true}}}, + }, + { + name: "Vast Unwrapper for IN-APP openrtb2/auction request", + args: args{ + payload: hookstage.EntrypointPayload{ + Request: func() *http.Request { + r, _ := http.NewRequest("POST", "http://localhost/openrtb2/auction?source=owsdk", nil) + return r + }(), + Body: []byte(`{"ext":{"wrapper":{"profileid":5890,"versionid":1}}}`), }, }, - want: hookstage.HookResult[hookstage.EntrypointPayload]{ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: false}}}, + want: hookstage.HookResult[hookstage.EntrypointPayload]{ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: false, Redirect: false, ProfileID: 5890, DisplayID: 1, Endpoint: ow_models.EndpointV25}}}, }, { - name: "Enable Vast Unwrapper", + name: "Vast Unwrapper for IN-APP /openrtb/2.5 request coming from SSHB", args: args{ payload: hookstage.EntrypointPayload{ Request: func() *http.Request { ctx := context.WithValue(context.Background(), VastUnwrapEnabled, "1") - r, _ := http.NewRequestWithContext(ctx, "", "", nil) + r, _ := http.NewRequestWithContext(ctx, "POST", "http://localhost/openrtb2/auction?sshb=1", nil) return r }(), + Body: []byte(`{"ext":{"wrapper":{"profileid":5890,"versionid":1}}}`), }, - config: VastUnwrapModule{ - TrafficPercentage: 2, + }, + want: hookstage.HookResult[hookstage.EntrypointPayload]{ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: true, Redirect: true, ProfileID: 0, DisplayID: 0, Endpoint: ""}}}, + }, + { + name: "Vast Unwrapper for IN-APP /openrtb/2.5 request directly coming to prebid", + args: args{ + payload: hookstage.EntrypointPayload{ + Request: func() *http.Request { + ctx := context.WithValue(context.Background(), VastUnwrapEnabled, "1") + r, _ := http.NewRequestWithContext(ctx, "POST", "http://localhost/openrtb/2.5", nil) + return r + }(), + Body: []byte(`{"ext":{"wrapper":{"profileid":5890,"versionid":1}}}`), + }, + }, + want: hookstage.HookResult[hookstage.EntrypointPayload]{ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: false, Redirect: false, ProfileID: 5890, DisplayID: 1, Endpoint: ow_models.EndpointV25}}}, + }, + { + name: "Vast Unwrapper for WebS2S activation request", + args: args{ + payload: hookstage.EntrypointPayload{ + Request: func() *http.Request { + ctx := context.WithValue(context.Background(), VastUnwrapEnabled, "1") + r, _ := http.NewRequestWithContext(ctx, "POST", "http://localhost/openrtb2/auction?source=pbjs", nil) + return r + }(), + Body: []byte(`{"ext":{"wrapper":{"profileid":5890,"versionid":1}}}`), + }, + }, + want: hookstage.HookResult[hookstage.EntrypointPayload]{ + ModuleContext: hookstage.ModuleContext{}, + DebugMessages: []string{"webs2s endpoint does not support vast-unwrap feature"}, + }, + }, + { + name: "Vast Unwrapper for Hybrid request", + args: args{ + payload: hookstage.EntrypointPayload{ + Request: func() *http.Request { + ctx := context.WithValue(context.Background(), VastUnwrapEnabled, "1") + r, _ := http.NewRequestWithContext(ctx, "POST", "http://localhost:8001/pbs/openrtb2/auction", nil) + return r + }(), + Body: []byte(`{"ext":{"wrapper":{"profileid":5890,"versionid":1}}}`), + }, + }, + want: hookstage.HookResult[hookstage.EntrypointPayload]{ + ModuleContext: hookstage.ModuleContext{}, + DebugMessages: []string{"hybrid endpoint does not support vast-unwrap feature"}, + }, + }, + { + name: "Vast Unwrapper for AMP request coming from SSHB", + args: args{ + payload: hookstage.EntrypointPayload{ + Request: func() *http.Request { + r, _ := http.NewRequest("GET", "http://localhost:8001/amp?sshb=1&v=1&w=300&h=250", nil) + return r + }(), + }, + }, + want: hookstage.HookResult[hookstage.EntrypointPayload]{ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: false, Redirect: true, Endpoint: ""}}}, + }, + { + name: "Vast Unwrapper for IN-APP OTT request coming from SSHB", + args: args{ + payload: hookstage.EntrypointPayload{ + Request: func() *http.Request { + r, _ := http.NewRequest("GET", "http://localhost:8001/openrtb/2.5/video?sshb=1&owLoggerDebug=1&pubId=5890&profId=2543", nil) + return r + }(), }, }, - randomNum: 1, - want: hookstage.HookResult[hookstage.EntrypointPayload]{ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: true}}}, + want: hookstage.HookResult[hookstage.EntrypointPayload]{ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: false, Redirect: true, Endpoint: ""}}}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, _ := handleEntrypointHook(nil, hookstage.ModuleInvocationContext{}, tt.args.payload, tt.args.config) - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("handleEntrypointHook() = %v, want %v", got, tt.want) - } + got, _ := handleEntrypointHook(nil, hookstage.ModuleInvocationContext{}, tt.args.payload) + assert.Equal(t, tt.want, got, "mismatched handleEntrypointHook output") }) } } diff --git a/modules/pubmatic/vastunwrap/hook_raw_bidder_response.go b/modules/pubmatic/vastunwrap/hook_raw_bidder_response.go index 0d09dba143b..42149ca02e3 100644 --- a/modules/pubmatic/vastunwrap/hook_raw_bidder_response.go +++ b/modules/pubmatic/vastunwrap/hook_raw_bidder_response.go @@ -1,6 +1,8 @@ package vastunwrap import ( + "fmt" + "strconv" "sync" "github.com/prebid/prebid-server/adapters" @@ -19,8 +21,21 @@ func (m VastUnwrapModule) handleRawBidderResponseHook( result.DebugMessages = append(result.DebugMessages, "error: request-ctx not found in handleRawBidderResponseHook()") return result, nil } + vastUnwrapEnabled := vastRequestContext.VastUnwrapEnabled + if !vastRequestContext.Redirect { + pubId, _ := strconv.Atoi(miCtx.AccountID) + vastRequestContext.PubID = pubId + vastUnwrapEnabled = getRandomNumber() < m.TrafficPercentage && m.getVastUnwrapEnable(vastRequestContext) + result.DebugMessages = append(result.DebugMessages, + fmt.Sprintf("found request without sshb=1 in handleRawBidderResponseHook() for pubid:[%d]", vastRequestContext.PubID)) + } + + vastRequestContext.VastUnwrapEnabled = vastUnwrapEnabled + vastRequestContext.VastUnwrapStatsEnabled = getRandomNumber() < m.StatTrafficPercentage + if !vastRequestContext.VastUnwrapEnabled && !vastRequestContext.VastUnwrapStatsEnabled { - result.DebugMessages = append(result.DebugMessages, "error: vast unwrap flag is not enabled in handleRawBidderResponseHook()") + result.DebugMessages = append(result.DebugMessages, + fmt.Sprintf("error: vast unwrap flag is not enabled in handleRawBidderResponseHook() for pubid:[%d]", vastRequestContext.PubID)) return result, nil } diff --git a/modules/pubmatic/vastunwrap/hook_raw_bidder_response_test.go b/modules/pubmatic/vastunwrap/hook_raw_bidder_response_test.go index 74edcadc0e2..5ac49374be4 100644 --- a/modules/pubmatic/vastunwrap/hook_raw_bidder_response_test.go +++ b/modules/pubmatic/vastunwrap/hook_raw_bidder_response_test.go @@ -23,21 +23,25 @@ func TestHandleRawBidderResponseHook(t *testing.T) { mockMetricsEngine := mock_stats.NewMockMetricsEngine(ctrl) VastUnWrapModule := VastUnwrapModule{Cfg: config.VastUnWrapCfg{MaxWrapperSupport: 5, StatConfig: unWrapCfg.StatConfig{Endpoint: "http://10.172.141.13:8080", RefershIntervalInSec: 1}, APPConfig: config.AppConfig{UnwrapDefaultTimeout: 1500}}, MetricsEngine: mockMetricsEngine} type args struct { - module VastUnwrapModule - payload hookstage.RawBidderResponsePayload - moduleInvocationCtx hookstage.ModuleInvocationContext - unwrapTimeout int - url string - wantAdM bool + module VastUnwrapModule + payload hookstage.RawBidderResponsePayload + moduleInvocationCtx hookstage.ModuleInvocationContext + unwrapTimeout int + url string + wantAdM bool + randomNumber int + trafficPercentage int + statTrafficPercentage int } tests := []struct { - name string - args args - wantResult hookstage.HookResult[hookstage.RawBidderResponsePayload] - expectedBids []*adapters.TypedBid - setup func() - wantErr bool - unwrapRequest func(w http.ResponseWriter, req *http.Request) + name string + args args + wantResult hookstage.HookResult[hookstage.RawBidderResponsePayload] + expectedBids []*adapters.TypedBid + setup func() + wantErr bool + unwrapRequest func(w http.ResponseWriter, req *http.Request) + getVastUnwrapEnable func(rctx models.RequestCtx) bool }{ { name: "Empty Request Context", @@ -65,7 +69,7 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, BidType: "video", }}}, - moduleInvocationCtx: hookstage.ModuleInvocationContext{ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: false}}}, + moduleInvocationCtx: hookstage.ModuleInvocationContext{ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: false, Redirect: true}}}, }, wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, wantErr: false, @@ -90,7 +94,9 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }}, Bidder: "pubmatic", }, - moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: false, VastUnwrapStatsEnabled: true}}}, + moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: false, VastUnwrapStatsEnabled: true, Redirect: true}}}, + statTrafficPercentage: 100, + randomNumber: 10, }, wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, setup: func() { @@ -128,7 +134,7 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, Bidder: "pubmatic", }, - moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: true}}}, + moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: true, Redirect: true}}}, url: UnwrapURL, }, wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, @@ -164,7 +170,7 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, Bidder: "pubmatic", }, - moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: true}}}, + moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: true, Redirect: true}}}, url: UnwrapURL, }, wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, @@ -214,9 +220,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }}, Bidder: "pubmatic", }, - moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: true}}}, + moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: true, Redirect: true}}}, url: UnwrapURL, wantAdM: true, + randomNumber: 10, + trafficPercentage: 100, }, wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, setup: func() { @@ -265,7 +273,7 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }}, Bidder: "pubmatic", }, - moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: true}}}, + moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: true, Redirect: true}}}, url: UnwrapURL, }, wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, @@ -308,17 +316,66 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, wantErr: false, }, + + { + name: "Set Vast Unwrapper to true in request context with type video and source owsdk", + args: args{ + module: VastUnWrapModule, + 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, + }, + BidType: "video", + }, + }, + Bidder: "pubmatic", + }, + moduleInvocationCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{Redirect: false, ProfileID: 5890, DisplayID: 1, Endpoint: "/openrtb/video"}}}, + url: UnwrapURL, + }, + wantResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: false}, + setup: func() { + mockMetricsEngine.EXPECT().RecordRequestStatus("5890", "pubmatic", "0").AnyTimes() + mockMetricsEngine.EXPECT().RecordWrapperCount("5890", "pubmatic", "1").AnyTimes() + mockMetricsEngine.EXPECT().RecordRequestTime("5890", "pubmatic", gomock.Any()).AnyTimes() + mockMetricsEngine.EXPECT().RecordUnwrapRespTime("5890", "1", gomock.Any()).AnyTimes() + }, + unwrapRequest: func(w http.ResponseWriter, req *http.Request) { + w.Header().Add("unwrap-status", "0") + w.Header().Add("unwrap-count", "1") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(inlineXMLAdM)) + }, + getVastUnwrapEnable: func(rctx models.RequestCtx) bool { + return true + }, + wantErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.setup != nil { tt.setup() } + getRandomNumber = func() int { + return tt.args.randomNumber + } m := VastUnwrapModule{ - Cfg: tt.args.module.Cfg, - Enabled: true, - MetricsEngine: mockMetricsEngine, - unwrapRequest: tt.unwrapRequest, + Cfg: tt.args.module.Cfg, + Enabled: true, + MetricsEngine: mockMetricsEngine, + unwrapRequest: tt.unwrapRequest, + getVastUnwrapEnable: tt.getVastUnwrapEnable, + TrafficPercentage: tt.args.trafficPercentage, + StatTrafficPercentage: tt.args.statTrafficPercentage, } _, err := m.handleRawBidderResponseHook(tt.args.moduleInvocationCtx, tt.args.payload, "test") if !assert.NoError(t, err, tt.wantErr) { diff --git a/modules/pubmatic/vastunwrap/models/request.go b/modules/pubmatic/vastunwrap/models/request.go index 60dc655a600..fcc9ed4fac9 100644 --- a/modules/pubmatic/vastunwrap/models/request.go +++ b/modules/pubmatic/vastunwrap/models/request.go @@ -1,7 +1,10 @@ package models type RequestCtx struct { - UA string - VastUnwrapEnabled bool - VastUnwrapStatsEnabled bool + UA string + VastUnwrapEnabled bool + PubID, ProfileID, DisplayID int + Endpoint string + VastUnwrapStatsEnabled bool + Redirect bool } diff --git a/modules/pubmatic/vastunwrap/module.go b/modules/pubmatic/vastunwrap/module.go index e98d5cf58b4..9e98fe348b6 100644 --- a/modules/pubmatic/vastunwrap/module.go +++ b/modules/pubmatic/vastunwrap/module.go @@ -13,6 +13,8 @@ import ( "github.com/golang/glog" "github.com/prebid/prebid-server/hooks/hookstage" "github.com/prebid/prebid-server/modules/moduledeps" + openwrap "github.com/prebid/prebid-server/modules/pubmatic/openwrap" + "github.com/prebid/prebid-server/modules/pubmatic/vastunwrap/models" metrics "github.com/prebid/prebid-server/modules/pubmatic/vastunwrap/stats" ) @@ -23,6 +25,7 @@ type VastUnwrapModule struct { Enabled bool `mapstructure:"enabled" json:"enabled"` MetricsEngine metrics.MetricsEngine unwrapRequest func(w http.ResponseWriter, r *http.Request) + getVastUnwrapEnable func(rctx models.RequestCtx) bool } func Builder(rawCfg json.RawMessage, deps moduledeps.ModuleDeps) (interface{}, error) { @@ -49,6 +52,7 @@ func initVastUnwrap(rawCfg json.RawMessage, deps moduledeps.ModuleDeps) (VastUnw Enabled: vastUnwrapModuleCfg.Enabled, MetricsEngine: metricEngine, unwrapRequest: vastunwrap.UnwrapRequest, + getVastUnwrapEnable: openwrap.GetVastUnwrapEnabled, }, nil } @@ -71,7 +75,7 @@ func (m VastUnwrapModule) HandleEntrypointHook( payload hookstage.EntrypointPayload, ) (hookstage.HookResult[hookstage.EntrypointPayload], error) { if m.Enabled { - return handleEntrypointHook(ctx, miCtx, payload, m) + return handleEntrypointHook(ctx, miCtx, payload) } return hookstage.HookResult[hookstage.EntrypointPayload]{}, nil } diff --git a/modules/pubmatic/vastunwrap/module_test.go b/modules/pubmatic/vastunwrap/module_test.go index 8269a386b5e..cb4d7804313 100644 --- a/modules/pubmatic/vastunwrap/module_test.go +++ b/modules/pubmatic/vastunwrap/module_test.go @@ -53,15 +53,15 @@ func TestVastUnwrapModuleHandleEntrypointHook(t *testing.T) { }, TrafficPercentage: 2}}, args: args{ - miCtx: hookstage.ModuleInvocationContext{ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: true}}}, + miCtx: hookstage.ModuleInvocationContext{ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: true, Redirect: true}}}, payload: hookstage.EntrypointPayload{ Request: func() *http.Request { ctx := context.WithValue(context.Background(), VastUnwrapEnabled, "1") - r, _ := http.NewRequestWithContext(ctx, "", "", nil) + r, _ := http.NewRequestWithContext(ctx, "POST", "http://localhost/video/openrtb?sshb=1", nil) return r }(), }}, - want: hookstage.HookResult[hookstage.EntrypointPayload]{ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: true}}}, + want: hookstage.HookResult[hookstage.EntrypointPayload]{ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: true, Redirect: true}}}, }, { name: "Vast unwrap is disabled in the config", @@ -78,7 +78,7 @@ func TestVastUnwrapModuleHandleEntrypointHook(t *testing.T) { payload: hookstage.EntrypointPayload{ Request: func() *http.Request { ctx := context.WithValue(context.Background(), VastUnwrapEnabled, "1") - r, _ := http.NewRequestWithContext(ctx, "", "", nil) + r, _ := http.NewRequestWithContext(ctx, "POST", "http://localhost/video/openrtb?sshb=1", nil) return r }(), }}, @@ -111,10 +111,11 @@ func TestVastUnwrapModuleHandleRawBidderResponseHook(t *testing.T) { cfg VastUnwrapModule } type args struct { - in0 context.Context - miCtx hookstage.ModuleInvocationContext - payload hookstage.RawBidderResponsePayload - wantAdM bool + in0 context.Context + miCtx hookstage.ModuleInvocationContext + payload hookstage.RawBidderResponsePayload + wantAdM bool + randomNumber int } tests := []struct { name string @@ -134,9 +135,9 @@ func TestVastUnwrapModuleHandleRawBidderResponseHook(t *testing.T) { StatConfig: unWrapCfg.StatConfig{Endpoint: "http://10.172.141.13:8080", RefershIntervalInSec: 1}, ServerConfig: unWrapCfg.ServerConfig{ServerName: "", DCName: "OW_DC"}, }, - TrafficPercentage: 2}}, + TrafficPercentage: 100}}, args: args{ - miCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: true}}}, + miCtx: hookstage.ModuleInvocationContext{AccountID: "5890", ModuleContext: hookstage.ModuleContext{"rctx": models.RequestCtx{VastUnwrapEnabled: true, Redirect: true}}}, payload: hookstage.RawBidderResponsePayload{ Bids: []*adapters.TypedBid{ { @@ -153,7 +154,8 @@ func TestVastUnwrapModuleHandleRawBidderResponseHook(t *testing.T) { }}, Bidder: "pubmatic", }, - wantAdM: true, + wantAdM: true, + randomNumber: 10, }, setup: func() { mockMetricsEngine.EXPECT().RecordRequestStatus("5890", "pubmatic", "0") @@ -205,11 +207,15 @@ func TestVastUnwrapModuleHandleRawBidderResponseHook(t *testing.T) { if tt.setup != nil { tt.setup() } + getRandomNumber = func() int { + return tt.args.randomNumber + } m := VastUnwrapModule{ - Cfg: tt.fields.cfg.Cfg, - Enabled: tt.fields.cfg.Enabled, - MetricsEngine: mockMetricsEngine, - unwrapRequest: tt.unwrapRequest, + Cfg: tt.fields.cfg.Cfg, + Enabled: tt.fields.cfg.Enabled, + MetricsEngine: mockMetricsEngine, + unwrapRequest: tt.unwrapRequest, + TrafficPercentage: tt.fields.cfg.TrafficPercentage, } _, err := m.HandleRawBidderResponseHook(tt.args.in0, tt.args.miCtx, tt.args.payload) if !assert.NoError(t, err, tt.wantErr) {